diff options
| author | Narr the Reg <juangerman-13@hotmail.com> | 2022-12-20 19:10:42 -0600 | 
|---|---|---|
| committer | Narr the Reg <juangerman-13@hotmail.com> | 2023-01-19 18:05:21 -0600 | 
| commit | 751d36e7392b0b1637f17988cfc1ef0d7cd95753 (patch) | |
| tree | 8ba772846be04719e41f82ef059ee81491a7c0e9 | |
| parent | f09a023292e659af46d551b9b134d94d000a57c7 (diff) | |
input_common: Add support for joycon ring controller
| -rw-r--r-- | src/input_common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_driver.cpp | 43 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_driver.h | 3 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/calibration.cpp | 22 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/calibration.h | 10 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/poller.cpp | 22 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/poller.h | 4 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/ringcon.cpp | 132 | ||||
| -rw-r--r-- | src/input_common/helpers/joycon_protocol/ringcon.h | 38 | 
9 files changed, 272 insertions, 4 deletions
| 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<CalibrationProtocol>(hidapi_handle);      generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); +    ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);      rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);      // Get fixed joycon info @@ -172,9 +179,23 @@ void JoyconDriver::OnNewData(std::span<u8> 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<CalibrationProtocol> calibration_protocol = nullptr;      std::unique_ptr<GenericProtocol> generic_protocol = nullptr;      std::unique_ptr<JoyconPoller> joycon_poller = nullptr; +    std::unique_ptr<RingConProtocol> ring_protocol = nullptr;      std::unique_ptr<RumbleProtocol> 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<u8> buffer, const MotionStatus& motion_status) { +void JoyconPoller::ReadActiveMode(std::span<u8> 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<u8> 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<u8> buffer) {  void JoyconPoller::ReadNfcIRMode(std::span<u8> 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<float>(value - ring_status.default_value); +    if (normalized_value > 0) { +        normalized_value = normalized_value / +                           static_cast<float>(ring_status.max_value - ring_status.default_value); +    } +    if (normalized_value < 0) { +        normalized_value = normalized_value / +                           static_cast<float>(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<Joycon::PadButton, 11> 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<u8> buffer);      /// Handles data from active packages -    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status); +    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, +                        const RingStatus& ring_status);      /// Handles data from nfc or ir packages      void ReadNfcIRMode(std::span<u8> 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<JoyconHandle> 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<u8> output; +    std::size_t tries = 0; +    is_connected = false; + +    do { +        std::vector<u8> 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<u8> output; +    std::size_t tries = 0; + +    do { +        std::vector<u8> 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<u8> 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 <vector> + +#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<JoyconHandle> handle); + +    DriverResult EnableRingCon(); + +    DriverResult DisableRingCon(); + +    DriverResult StartRingconPolling(); + +    bool IsEnabled(); + +private: +    DriverResult IsRingConnected(bool& is_connected); + +    DriverResult ConfigureRing(); + +    bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon | 
