input_common: Add support for joycon ring controller

master
Narr the Reg 2022-12-20 19:10:42 +07:00
parent f09a023292
commit 751d36e739
9 changed files with 272 additions and 4 deletions

@ -66,6 +66,8 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/joycon_types.h helpers/joycon_protocol/joycon_types.h
helpers/joycon_protocol/poller.cpp helpers/joycon_protocol/poller.cpp
helpers/joycon_protocol/poller.h helpers/joycon_protocol/poller.h
helpers/joycon_protocol/ringcon.cpp
helpers/joycon_protocol/ringcon.h
helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h helpers/joycon_protocol/rumble.h
) )

@ -52,12 +52,18 @@ DriverResult JoyconDriver::InitializeDevice() {
error_counter = 0; error_counter = 0;
hidapi_handle->packet_counter = 0; hidapi_handle->packet_counter = 0;
// Reset external device status
starlink_connected = false;
ring_connected = false;
amiibo_detected = false;
// Set HW default configuration // Set HW default configuration
vibration_enabled = true; vibration_enabled = true;
motion_enabled = true; motion_enabled = true;
hidbus_enabled = false; hidbus_enabled = false;
nfc_enabled = false; nfc_enabled = false;
passive_enabled = false; passive_enabled = false;
irs_enabled = false;
gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
gyro_performance = Joycon::GyroPerformance::HZ833; gyro_performance = Joycon::GyroPerformance::HZ833;
accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
@ -66,6 +72,7 @@ DriverResult JoyconDriver::InitializeDevice() {
// Initialize HW Protocols // Initialize HW Protocols
calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
generic_protocol = std::make_unique<GenericProtocol>(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); rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
// Get fixed joycon info // Get fixed joycon info
@ -172,9 +179,23 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
.accelerometer_sensitivity = accelerometer_sensitivity, .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) { switch (report_mode) {
case InputReport::STANDARD_FULL_60HZ: case InputReport::STANDARD_FULL_60HZ:
joycon_poller->ReadActiveMode(buffer, motion_status); joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
break; break;
case InputReport::NFC_IR_MODE_60HZ: case InputReport::NFC_IR_MODE_60HZ:
joycon_poller->ReadNfcIRMode(buffer, motion_status); joycon_poller->ReadNfcIRMode(buffer, motion_status);
@ -204,6 +225,26 @@ void JoyconDriver::SetPollingMode() {
generic_protocol->EnableImu(false); 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) { if (passive_enabled && supported_features.passive) {
const auto result = generic_protocol->EnablePassiveMode(); const auto result = generic_protocol->EnablePassiveMode();
if (result == DriverResult::Success) { if (result == DriverResult::Success) {

@ -12,6 +12,7 @@
#include "input_common/helpers/joycon_protocol/generic_functions.h" #include "input_common/helpers/joycon_protocol/generic_functions.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h" #include "input_common/helpers/joycon_protocol/joycon_types.h"
#include "input_common/helpers/joycon_protocol/poller.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" #include "input_common/helpers/joycon_protocol/rumble.h"
namespace InputCommon::Joycon { namespace InputCommon::Joycon {
@ -86,6 +87,7 @@ private:
std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr; std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
std::unique_ptr<GenericProtocol> generic_protocol = nullptr; std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
std::unique_ptr<JoyconPoller> joycon_poller = nullptr; std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
std::unique_ptr<RingConProtocol> ring_protocol = nullptr;
std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr; std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
// Connection status // Connection status
@ -118,6 +120,7 @@ private:
JoyStickCalibration left_stick_calibration{}; JoyStickCalibration left_stick_calibration{};
JoyStickCalibration right_stick_calibration{}; JoyStickCalibration right_stick_calibration{};
MotionCalibration motion_calibration{}; MotionCalibration motion_calibration{};
RingCalibration ring_calibration{};
// Fixed joycon info // Fixed joycon info
FirmwareVersion version{}; FirmwareVersion version{};

@ -128,6 +128,28 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati
return result; 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) { void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
constexpr u16 DefaultStickCenter{2048}; constexpr u16 DefaultStickCenter{2048};
constexpr u16 DefaultStickRange{1740}; constexpr u16 DefaultStickRange{1740};

@ -46,9 +46,19 @@ public:
*/ */
DriverResult GetImuCalibration(MotionCalibration& calibration); 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: private:
void ValidateCalibration(JoyStickCalibration& calibration); void ValidateCalibration(JoyStickCalibration& calibration);
void ValidateCalibration(MotionCalibration& calibration); void ValidateCalibration(MotionCalibration& calibration);
s16 ring_data_max = 0;
s16 ring_data_default = 0;
s16 ring_data_min = 0;
}; };
} // namespace InputCommon::Joycon } // namespace InputCommon::Joycon

@ -16,7 +16,8 @@ void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
callbacks = std::move(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{}; InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive)); memcpy(&data, buffer.data(), sizeof(InputReportActive));
@ -36,6 +37,10 @@ void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& moti
break; break;
} }
if (ring_status.is_enabled) {
UpdateRing(data.ring_input, ring_status);
}
callbacks.on_battery_data(data.battery_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) { void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
// This mode is compatible with the active mode // This mode is compatible with the active mode
ReadActiveMode(buffer, motion_status); ReadActiveMode(buffer, motion_status, {});
} }
void JoyconPoller::UpdateColor(const Color& color) { void JoyconPoller::UpdateColor(const Color& color) {
callbacks.on_color_data(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, void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
const MotionStatus& motion_status) { const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 11> left_buttons{ static constexpr std::array<Joycon::PadButton, 11> left_buttons{

@ -28,12 +28,14 @@ public:
void ReadPassiveMode(std::span<u8> buffer); void ReadPassiveMode(std::span<u8> buffer);
/// Handles data from active packages /// 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 /// Handles data from nfc or ir packages
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status); void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
void UpdateColor(const Color& color); void UpdateColor(const Color& color);
void UpdateRing(s16 value, const RingStatus& ring_status);
private: private:
void UpdateActiveLeftPadInput(const InputReportActive& input, void UpdateActiveLeftPadInput(const InputReportActive& input,

@ -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

@ -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