From 751d36e7392b0b1637f17988cfc1ef0d7cd95753 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 20 Dec 2022 19:10:42 -0600 Subject: [PATCH] input_common: Add support for joycon ring controller --- src/input_common/CMakeLists.txt | 2 + src/input_common/helpers/joycon_driver.cpp | 43 +++++- src/input_common/helpers/joycon_driver.h | 3 + .../helpers/joycon_protocol/calibration.cpp | 22 +++ .../helpers/joycon_protocol/calibration.h | 10 ++ .../helpers/joycon_protocol/poller.cpp | 22 ++- .../helpers/joycon_protocol/poller.h | 4 +- .../helpers/joycon_protocol/ringcon.cpp | 132 ++++++++++++++++++ .../helpers/joycon_protocol/ringcon.h | 38 +++++ 9 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.cpp create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.h diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 4ab1ccbfb..addecc9b3 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -66,6 +66,8 @@ if (ENABLE_SDL2) helpers/joycon_protocol/joycon_types.h helpers/joycon_protocol/poller.cpp helpers/joycon_protocol/poller.h + helpers/joycon_protocol/ringcon.cpp + helpers/joycon_protocol/ringcon.h helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.h ) diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index 5d0aeabf5..c0a03fe2e 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp @@ -52,12 +52,18 @@ DriverResult JoyconDriver::InitializeDevice() { error_counter = 0; hidapi_handle->packet_counter = 0; + // Reset external device status + starlink_connected = false; + ring_connected = false; + amiibo_detected = false; + // Set HW default configuration vibration_enabled = true; motion_enabled = true; hidbus_enabled = false; nfc_enabled = false; passive_enabled = false; + irs_enabled = false; gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; gyro_performance = Joycon::GyroPerformance::HZ833; accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; @@ -66,6 +72,7 @@ DriverResult JoyconDriver::InitializeDevice() { // Initialize HW Protocols calibration_protocol = std::make_unique(hidapi_handle); generic_protocol = std::make_unique(hidapi_handle); + ring_protocol = std::make_unique(hidapi_handle); rumble_protocol = std::make_unique(hidapi_handle); // Get fixed joycon info @@ -172,9 +179,23 @@ void JoyconDriver::OnNewData(std::span buffer) { .accelerometer_sensitivity = accelerometer_sensitivity, }; + // TODO: Remove this when calibration is properly loaded and not calculated + if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) { + InputReportActive data{}; + memcpy(&data, buffer.data(), sizeof(InputReportActive)); + calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); + } + + const RingStatus ring_status{ + .is_enabled = ring_connected, + .default_value = ring_calibration.default_value, + .max_value = ring_calibration.max_value, + .min_value = ring_calibration.min_value, + }; + switch (report_mode) { case InputReport::STANDARD_FULL_60HZ: - joycon_poller->ReadActiveMode(buffer, motion_status); + joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); break; case InputReport::NFC_IR_MODE_60HZ: joycon_poller->ReadNfcIRMode(buffer, motion_status); @@ -204,6 +225,26 @@ void JoyconDriver::SetPollingMode() { generic_protocol->EnableImu(false); } + if (ring_protocol->IsEnabled()) { + ring_connected = false; + ring_protocol->DisableRingCon(); + } + + if (hidbus_enabled && supported_features.hidbus) { + auto result = ring_protocol->EnableRingCon(); + if (result == DriverResult::Success) { + result = ring_protocol->StartRingconPolling(); + } + if (result == DriverResult::Success) { + ring_connected = true; + disable_input_thread = false; + return; + } + ring_connected = false; + ring_protocol->DisableRingCon(); + LOG_ERROR(Input, "Error enabling Ringcon"); + } + if (passive_enabled && supported_features.passive) { const auto result = generic_protocol->EnablePassiveMode(); if (result == DriverResult::Success) { diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index 48ba859f4..dc5d60221 100644 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h @@ -12,6 +12,7 @@ #include "input_common/helpers/joycon_protocol/generic_functions.h" #include "input_common/helpers/joycon_protocol/joycon_types.h" #include "input_common/helpers/joycon_protocol/poller.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" #include "input_common/helpers/joycon_protocol/rumble.h" namespace InputCommon::Joycon { @@ -86,6 +87,7 @@ private: std::unique_ptr calibration_protocol = nullptr; std::unique_ptr generic_protocol = nullptr; std::unique_ptr joycon_poller = nullptr; + std::unique_ptr ring_protocol = nullptr; std::unique_ptr rumble_protocol = nullptr; // Connection status @@ -118,6 +120,7 @@ private: JoyStickCalibration left_stick_calibration{}; JoyStickCalibration right_stick_calibration{}; MotionCalibration motion_calibration{}; + RingCalibration ring_calibration{}; // Fixed joycon info FirmwareVersion version{}; diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp index 5c29af545..ce1ff7061 100644 --- a/src/input_common/helpers/joycon_protocol/calibration.cpp +++ b/src/input_common/helpers/joycon_protocol/calibration.cpp @@ -128,6 +128,28 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati return result; } +DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration, + s16 current_value) { + // TODO: Get default calibration form ring itself + if (ring_data_max == 0 && ring_data_min == 0) { + ring_data_max = current_value + 800; + ring_data_min = current_value - 800; + ring_data_default = current_value; + } + if (ring_data_max < current_value) { + ring_data_max = current_value; + } + if (ring_data_min > current_value) { + ring_data_min = current_value; + } + calibration = { + .default_value = ring_data_default, + .max_value = ring_data_max, + .min_value = ring_data_min, + }; + return DriverResult::Success; +} + void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) { constexpr u16 DefaultStickCenter{2048}; constexpr u16 DefaultStickRange{1740}; diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h index 38214eed4..32ddef4b8 100644 --- a/src/input_common/helpers/joycon_protocol/calibration.h +++ b/src/input_common/helpers/joycon_protocol/calibration.h @@ -46,9 +46,19 @@ public: */ DriverResult GetImuCalibration(MotionCalibration& calibration); + /** + * Calculates on run time the proper calibration of the ring controller + * @returns RingCalibration of the ring sensor + */ + DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value); + private: void ValidateCalibration(JoyStickCalibration& calibration); void ValidateCalibration(MotionCalibration& calibration); + + s16 ring_data_max = 0; + s16 ring_data_default = 0; + s16 ring_data_min = 0; }; } // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp index 341479c0c..cb76e1e06 100644 --- a/src/input_common/helpers/joycon_protocol/poller.cpp +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -16,7 +16,8 @@ void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) { callbacks = std::move(callbacks_); } -void JoyconPoller::ReadActiveMode(std::span buffer, const MotionStatus& motion_status) { +void JoyconPoller::ReadActiveMode(std::span buffer, const MotionStatus& motion_status, + const RingStatus& ring_status) { InputReportActive data{}; memcpy(&data, buffer.data(), sizeof(InputReportActive)); @@ -36,6 +37,10 @@ void JoyconPoller::ReadActiveMode(std::span buffer, const MotionStatus& moti break; } + if (ring_status.is_enabled) { + UpdateRing(data.ring_input, ring_status); + } + callbacks.on_battery_data(data.battery_status); } @@ -62,13 +67,26 @@ void JoyconPoller::ReadPassiveMode(std::span buffer) { void JoyconPoller::ReadNfcIRMode(std::span buffer, const MotionStatus& motion_status) { // This mode is compatible with the active mode - ReadActiveMode(buffer, motion_status); + ReadActiveMode(buffer, motion_status, {}); } void JoyconPoller::UpdateColor(const Color& color) { callbacks.on_color_data(color); } +void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { + float normalized_value = static_cast(value - ring_status.default_value); + if (normalized_value > 0) { + normalized_value = normalized_value / + static_cast(ring_status.max_value - ring_status.default_value); + } + if (normalized_value < 0) { + normalized_value = normalized_value / + static_cast(ring_status.default_value - ring_status.min_value); + } + callbacks.on_ring_data(normalized_value); +} + void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input, const MotionStatus& motion_status) { static constexpr std::array left_buttons{ diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h index fff681d0a..68b14b8ba 100644 --- a/src/input_common/helpers/joycon_protocol/poller.h +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -28,12 +28,14 @@ public: void ReadPassiveMode(std::span buffer); /// Handles data from active packages - void ReadActiveMode(std::span buffer, const MotionStatus& motion_status); + void ReadActiveMode(std::span buffer, const MotionStatus& motion_status, + const RingStatus& ring_status); /// Handles data from nfc or ir packages void ReadNfcIRMode(std::span buffer, const MotionStatus& motion_status); void UpdateColor(const Color& color); + void UpdateRing(s16 value, const RingStatus& ring_status); private: void UpdateActiveLeftPadInput(const InputReportActive& input, diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp new file mode 100644 index 000000000..2d137b85d --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" + +namespace InputCommon::Joycon { + +RingConProtocol::RingConProtocol(std::shared_ptr handle) + : JoyconCommonProtocol(handle) {} + +DriverResult RingConProtocol::EnableRingCon() { + LOG_DEBUG(Input, "Enable Ringcon"); + DriverResult result{DriverResult::Success}; + SetBlocking(); + + if (result == DriverResult::Success) { + result = SetReportMode(ReportMode::STANDARD_FULL_60HZ); + } + if (result == DriverResult::Success) { + result = EnableMCU(true); + } + if (result == DriverResult::Success) { + const MCUConfig config{ + .command = MCUCommand::ConfigureMCU, + .sub_command = MCUSubCommand::SetDeviceMode, + .mode = MCUMode::Standby, + .crc = {}, + }; + result = ConfigureMCU(config); + } + + SetNonBlocking(); + return result; +} + +DriverResult RingConProtocol::DisableRingCon() { + LOG_DEBUG(Input, "Disable RingCon"); + DriverResult result{DriverResult::Success}; + SetBlocking(); + + if (result == DriverResult::Success) { + result = EnableMCU(false); + } + + is_enabled = false; + + SetNonBlocking(); + return result; +} + +DriverResult RingConProtocol::StartRingconPolling() { + LOG_DEBUG(Input, "Enable Ringcon"); + bool is_connected = false; + DriverResult result{DriverResult::Success}; + SetBlocking(); + + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::STANDARD_FULL_60HZ, MCUMode::Standby); + } + if (result == DriverResult::Success) { + result = IsRingConnected(is_connected); + } + if (result == DriverResult::Success && is_connected) { + LOG_INFO(Input, "Ringcon detected"); + result = ConfigureRing(); + } + if (result == DriverResult::Success) { + is_enabled = true; + } + + SetNonBlocking(); + return result; +} + +DriverResult RingConProtocol::IsRingConnected(bool& is_connected) { + LOG_DEBUG(Input, "IsRingConnected"); + constexpr std::size_t max_tries = 28; + std::vector output; + std::size_t tries = 0; + is_connected = false; + + do { + std::vector empty_data(0); + const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output); + + if (result != DriverResult::Success) { + return result; + } + + if (tries++ >= max_tries) { + return DriverResult::NoDeviceDetected; + } + } while (output[14] != 0x59 || output[16] != 0x20); + + is_connected = true; + return DriverResult::Success; +} + +DriverResult RingConProtocol::ConfigureRing() { + LOG_DEBUG(Input, "ConfigureRing"); + constexpr std::size_t max_tries = 28; + DriverResult result{DriverResult::Success}; + std::vector output; + std::size_t tries = 0; + + do { + std::vector ring_config{0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, + 0xED, 0x34, 0x36, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, + 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36}; + result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::NoDeviceDetected; + } + } while (output[14] != 0x5C); + + std::vector ringcon_data{0x04, 0x01, 0x01, 0x02}; + result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output); + + return result; +} + +bool RingConProtocol::IsEnabled() { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h new file mode 100644 index 000000000..0c25de23e --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class RingConProtocol final : private JoyconCommonProtocol { +public: + RingConProtocol(std::shared_ptr handle); + + DriverResult EnableRingCon(); + + DriverResult DisableRingCon(); + + DriverResult StartRingconPolling(); + + bool IsEnabled(); + +private: + DriverResult IsRingConnected(bool& is_connected); + + DriverResult ConfigureRing(); + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon