input_common: Add support for joycon input reports

master
Narr the Reg 2022-12-20 20:27:34 +07:00
parent 5676c2e17f
commit f09a023292
8 changed files with 798 additions and 100 deletions

@ -64,6 +64,10 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/generic_functions.cpp helpers/joycon_protocol/generic_functions.cpp
helpers/joycon_protocol/generic_functions.h helpers/joycon_protocol/generic_functions.h
helpers/joycon_protocol/joycon_types.h helpers/joycon_protocol/joycon_types.h
helpers/joycon_protocol/poller.cpp
helpers/joycon_protocol/poller.h
helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h
) )
target_link_libraries(input_common PRIVATE SDL2::SDL2) target_link_libraries(input_common PRIVATE SDL2::SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2)

@ -167,30 +167,31 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
if (result == Joycon::DriverResult::Success) { if (result == Joycon::DriverResult::Success) {
LOG_WARNING(Input, "Initialize device"); LOG_WARNING(Input, "Initialize device");
std::function<void(Joycon::Battery)> on_battery_data;
std::function<void(Joycon::Color)> on_button_data;
std::function<void(int, f32)> on_stick_data;
std::function<void(int, std::array<u8, 6>)> on_motion_data;
std::function<void(s16)> on_ring_data;
std::function<void(const std::vector<u8>&)> on_amiibo_data;
const std::size_t port = handle->GetDevicePort(); const std::size_t port = handle->GetDevicePort();
handle->on_battery_data = { const Joycon::JoyconCallbacks callbacks{
[this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }}; .on_battery_data = {[this, port, type](Joycon::Battery value) {
handle->on_color_data = { OnBatteryUpdate(port, type, value);
[this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }}; }},
handle->on_button_data = { .on_color_data = {[this, port, type](Joycon::Color value) {
[this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }}; OnColorUpdate(port, type, value);
handle->on_stick_data = { }},
[this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }}; .on_button_data = {[this, port, type](int id, bool value) {
handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) { OnButtonUpdate(port, type, id, value);
}},
.on_stick_data = {[this, port, type](int id, f32 value) {
OnStickUpdate(port, type, id, value);
}},
.on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
OnMotionUpdate(port, type, id, value); OnMotionUpdate(port, type, id, value);
}}; }},
handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}; .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
OnAmiiboUpdate(port, amiibo_data); OnAmiiboUpdate(port, amiibo_data);
}}; }},
};
handle->InitializeDevice(); handle->InitializeDevice();
handle->SetCallbacks(callbacks);
} }
} }
@ -235,7 +236,7 @@ Common::Input::VibrationError Joycons::SetVibration(
.low_amplitude = vibration.low_amplitude, .low_amplitude = vibration.low_amplitude,
.low_frequency = vibration.low_frequency, .low_frequency = vibration.low_frequency,
.high_amplitude = vibration.high_amplitude, .high_amplitude = vibration.high_amplitude,
.high_frequency = vibration.high_amplitude, .high_frequency = vibration.high_frequency,
}; };
auto handle = GetHandle(identifier); auto handle = GetHandle(identifier);
if (handle == nullptr) { if (handle == nullptr) {

@ -66,6 +66,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);
rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
// Get fixed joycon info // Get fixed joycon info
generic_protocol->GetVersionNumber(version); generic_protocol->GetVersionNumber(version);
@ -90,6 +91,10 @@ DriverResult JoyconDriver::InitializeDevice() {
// Apply HW configuration // Apply HW configuration
SetPollingMode(); SetPollingMode();
// Initialize joycon poller
joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
right_stick_calibration, motion_calibration);
// Start pooling for data // Start pooling for data
is_connected = true; is_connected = true;
if (!input_thread_running) { if (!input_thread_running) {
@ -142,15 +147,40 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
void JoyconDriver::OnNewData(std::span<u8> buffer) { void JoyconDriver::OnNewData(std::span<u8> buffer) {
const auto report_mode = static_cast<InputReport>(buffer[0]); const auto report_mode = static_cast<InputReport>(buffer[0]);
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
// experience
switch (report_mode) { switch (report_mode) {
case InputReport::STANDARD_FULL_60HZ: case InputReport::STANDARD_FULL_60HZ:
ReadActiveMode(buffer); case InputReport::NFC_IR_MODE_60HZ:
case InputReport::SIMPLE_HID_MODE: {
const auto now = std::chrono::steady_clock::now();
const auto new_delta_time = static_cast<u64>(
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
last_update = now;
joycon_poller->UpdateColor(color);
break;
}
default:
break;
}
const MotionStatus motion_status{
.is_enabled = motion_enabled,
.delta_time = delta_time,
.gyro_sensitivity = gyro_sensitivity,
.accelerometer_sensitivity = accelerometer_sensitivity,
};
switch (report_mode) {
case InputReport::STANDARD_FULL_60HZ:
joycon_poller->ReadActiveMode(buffer, motion_status);
break; break;
case InputReport::NFC_IR_MODE_60HZ: case InputReport::NFC_IR_MODE_60HZ:
ReadNfcIRMode(buffer); joycon_poller->ReadNfcIRMode(buffer, motion_status);
break; break;
case InputReport::SIMPLE_HID_MODE: case InputReport::SIMPLE_HID_MODE:
ReadPassiveMode(buffer); joycon_poller->ReadPassiveMode(buffer);
break; break;
case InputReport::SUBCMD_REPLY: case InputReport::SUBCMD_REPLY:
LOG_DEBUG(Input, "Unhandled command reply"); LOG_DEBUG(Input, "Unhandled command reply");
@ -164,6 +194,8 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
void JoyconDriver::SetPollingMode() { void JoyconDriver::SetPollingMode() {
disable_input_thread = true; disable_input_thread = true;
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
if (motion_enabled && supported_features.motion) { if (motion_enabled && supported_features.motion) {
generic_protocol->EnableImu(true); generic_protocol->EnableImu(true);
generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
@ -209,62 +241,6 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
return features; return features;
} }
void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive));
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
// experience
const auto now = std::chrono::steady_clock::now();
const auto new_delta_time =
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
last_update = now;
switch (device_type) {
case Joycon::ControllerType::Left:
break;
case Joycon::ControllerType::Right:
break;
case Joycon::ControllerType::Pro:
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
on_battery_data(data.battery_status);
on_color_data(color);
}
void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
InputReportPassive data{};
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
switch (device_type) {
case Joycon::ControllerType::Left:
break;
case Joycon::ControllerType::Right:
break;
case Joycon::ControllerType::Pro:
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
}
void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
// This mode is compatible with the active mode
ReadActiveMode(buffer);
if (!nfc_enabled) {
return;
}
}
bool JoyconDriver::IsInputThreadValid() const { bool JoyconDriver::IsInputThreadValid() const {
if (!is_connected) { if (!is_connected) {
return false; return false;
@ -302,7 +278,7 @@ DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
if (disable_input_thread) { if (disable_input_thread) {
return DriverResult::HandleInUse; return DriverResult::HandleInUse;
} }
return DriverResult::NotSupported; return rumble_protocol->SendVibration(vibration);
} }
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
@ -398,6 +374,10 @@ SerialNumber JoyconDriver::GetHandleSerialNumber() const {
return handle_serial_number; return handle_serial_number;
} }
void JoyconDriver::SetCallbacks(const Joycon::JoyconCallbacks& callbacks) {
joycon_poller->SetCallbacks(callbacks);
}
Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
ControllerType& controller_type) { ControllerType& controller_type) {
std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{ std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{

@ -11,6 +11,8 @@
#include "input_common/helpers/joycon_protocol/calibration.h" #include "input_common/helpers/joycon_protocol/calibration.h"
#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/rumble.h"
namespace InputCommon::Joycon { namespace InputCommon::Joycon {
@ -42,6 +44,8 @@ public:
DriverResult SetNfcMode(); DriverResult SetNfcMode();
DriverResult SetRingConMode(); DriverResult SetRingConMode();
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks);
// Returns device type from hidapi handle // Returns device type from hidapi handle
static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info, static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
Joycon::ControllerType& controller_type); Joycon::ControllerType& controller_type);
@ -50,14 +54,6 @@ public:
static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info, static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
Joycon::SerialNumber& serial_number); Joycon::SerialNumber& serial_number);
std::function<void(Battery)> on_battery_data;
std::function<void(Color)> on_color_data;
std::function<void(int, bool)> on_button_data;
std::function<void(int, f32)> on_stick_data;
std::function<void(int, MotionData)> on_motion_data;
std::function<void(f32)> on_ring_data;
std::function<void(const std::vector<u8>&)> on_amiibo_data;
private: private:
struct SupportedFeatures { struct SupportedFeatures {
bool passive{}; bool passive{};
@ -86,18 +82,11 @@ private:
/// Returns a list of supported features that can be enabled on this device /// Returns a list of supported features that can be enabled on this device
SupportedFeatures GetSupportedFeatures(); SupportedFeatures GetSupportedFeatures();
/// Handles data from passive packages
void ReadPassiveMode(std::span<u8> buffer);
/// Handles data from active packages
void ReadActiveMode(std::span<u8> buffer);
/// Handles data from nfc or ir packages
void ReadNfcIRMode(std::span<u8> buffer);
// Protocol Features // Protocol Features
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<RumbleProtocol> rumble_protocol = nullptr;
// Connection status // Connection status
bool is_connected{}; bool is_connected{};

@ -0,0 +1,315 @@
// 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/poller.h"
namespace InputCommon::Joycon {
JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
JoyStickCalibration right_stick_calibration_,
MotionCalibration motion_calibration_)
: device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
callbacks = std::move(callbacks_);
}
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive));
switch (device_type) {
case Joycon::ControllerType::Left:
UpdateActiveLeftPadInput(data, motion_status);
break;
case Joycon::ControllerType::Right:
UpdateActiveRightPadInput(data, motion_status);
break;
case Joycon::ControllerType::Pro:
UpdateActiveProPadInput(data, motion_status);
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
callbacks.on_battery_data(data.battery_status);
}
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
InputReportPassive data{};
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
switch (device_type) {
case Joycon::ControllerType::Left:
UpdatePasiveLeftPadInput(data);
break;
case Joycon::ControllerType::Right:
UpdatePasiveRightPadInput(data);
break;
case Joycon::ControllerType::Pro:
UpdatePasiveProPadInput(data);
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
}
void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
// This mode is compatible with the active mode
ReadActiveMode(buffer, motion_status);
}
void JoyconPoller::UpdateColor(const Color& color) {
callbacks.on_color_data(color);
}
void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 11> left_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
Joycon::PadButton::Capture, Joycon::PadButton::StickL,
};
const u32 raw_button =
static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
const int button = static_cast<int>(left_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_left_axis_x =
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
const u16 raw_left_axis_y =
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
if (motion_status.is_enabled) {
auto left_motion = GetMotionInput(input, motion_status);
// Rotate motion axis to the correct direction
left_motion.accel_y = -left_motion.accel_y;
left_motion.accel_z = -left_motion.accel_z;
left_motion.gyro_x = -left_motion.gyro_x;
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
}
}
void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 11> right_buttons{
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickR,
};
const u32 raw_button =
static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
const int button = static_cast<int>(right_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_right_axis_x =
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
const u16 raw_right_axis_y =
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (motion_status.is_enabled) {
auto right_motion = GetMotionInput(input, motion_status);
// Rotate motion axis to the correct direction
right_motion.accel_x = -right_motion.accel_x;
right_motion.accel_y = -right_motion.accel_y;
right_motion.gyro_z = -right_motion.gyro_z;
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
}
}
void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
};
const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
(input.button_input[1] << 16));
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
const int button = static_cast<int>(pro_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_left_axis_x =
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
const u16 raw_left_axis_y =
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
const u16 raw_right_axis_x =
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
const u16 raw_right_axis_y =
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (motion_status.is_enabled) {
auto pro_motion = GetMotionInput(input, motion_status);
pro_motion.gyro_x = -pro_motion.gyro_x;
pro_motion.accel_y = -pro_motion.accel_y;
pro_motion.accel_z = -pro_motion.accel_z;
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
}
}
void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
Joycon::PasivePadButton::StickL,
};
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
const int button = static_cast<int>(left_buttons[i]);
callbacks.on_button_data(button, button_status);
}
}
void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
Joycon::PasivePadButton::StickR,
};
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
const int button = static_cast<int>(right_buttons[i]);
callbacks.on_button_data(button, button_status);
}
}
void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
};
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
const int button = static_cast<int>(pro_buttons[i]);
callbacks.on_button_data(button, button_status);
}
}
f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
const f32 value = static_cast<f32>(raw_value - calibration.center);
if (value > 0.0f) {
return value / calibration.max;
}
return value / calibration.min;
}
f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
AccelerometerSensitivity sensitivity) const {
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
switch (sensitivity) {
case Joycon::AccelerometerSensitivity::G2:
return value / 4.0f;
case Joycon::AccelerometerSensitivity::G4:
return value / 2.0f;
case Joycon::AccelerometerSensitivity::G8:
return value;
case Joycon::AccelerometerSensitivity::G16:
return value * 2.0f;
}
return value;
}
f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
GyroSensitivity sensitivity) const {
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
switch (sensitivity) {
case Joycon::GyroSensitivity::DPS250:
return value / 8.0f;
case Joycon::GyroSensitivity::DPS500:
return value / 4.0f;
case Joycon::GyroSensitivity::DPS1000:
return value / 2.0f;
case Joycon::GyroSensitivity::DPS2000:
return value;
}
return value;
}
s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
const InputReportActive& input) const {
return input.motion_input[(sensor * 3) + axis];
}
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
const MotionStatus& motion_status) const {
MotionData motion{};
const auto& accel_cal = motion_calibration.accelerometer;
const auto& gyro_cal = motion_calibration.gyro;
const s16 raw_accel_x = input.motion_input[1];
const s16 raw_accel_y = input.motion_input[0];
const s16 raw_accel_z = input.motion_input[2];
const s16 raw_gyro_x = input.motion_input[4];
const s16 raw_gyro_y = input.motion_input[3];
const s16 raw_gyro_z = input.motion_input[5];
motion.delta_timestamp = motion_status.delta_time;
motion.accel_x =
GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
motion.accel_y =
GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
motion.accel_z =
GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
// TODO(German77): Return all three samples data
return motion;
}
} // namespace InputCommon::Joycon

@ -0,0 +1,77 @@
// 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 <functional>
#include <span>
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
// Handles input packages and triggers the corresponding input events
class JoyconPoller {
public:
JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
JoyStickCalibration right_stick_calibration_,
MotionCalibration motion_calibration_);
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
/// Handles data from passive packages
void ReadPassiveMode(std::span<u8> buffer);
/// Handles data from active packages
void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status);
/// Handles data from nfc or ir packages
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
void UpdateColor(const Color& color);
private:
void UpdateActiveLeftPadInput(const InputReportActive& input,
const MotionStatus& motion_status);
void UpdateActiveRightPadInput(const InputReportActive& input,
const MotionStatus& motion_status);
void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
void UpdatePasiveProPadInput(const InputReportPassive& buffer);
/// Returns a calibrated joystick axis from raw axis data
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
/// Returns a calibrated accelerometer axis from raw motion data
f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
AccelerometerSensitivity sensitivity) const;
/// Returns a calibrated gyro axis from raw motion data
f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
GyroSensitivity sensitivity) const;
/// Returns a raw motion value from a buffer
s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
/// Returns motion data from a buffer
MotionData GetMotionInput(const InputReportActive& input,
const MotionStatus& motion_status) const;
ControllerType device_type{};
// Device calibration
JoyStickCalibration left_stick_calibration{};
JoyStickCalibration right_stick_calibration{};
MotionCalibration motion_calibration{};
Joycon::JoyconCallbacks callbacks{};
};
} // namespace InputCommon::Joycon

@ -0,0 +1,299 @@
// 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/rumble.h"
namespace InputCommon::Joycon {
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(handle) {}
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
LOG_DEBUG(Input, "Enable Rumble");
const std::vector<u8> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
std::vector<u8> output;
SetBlocking();
const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
SetNonBlocking();
return result;
}
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
std::vector<u8> buffer(sizeof(DefaultVibrationBuffer));
if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
return SendVibrationReport(DefaultVibrationBuffer);
}
// Protect joycons from damage from strong vibrations
const f32 clamp_amplitude =
1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
const u8 encoded_high_amplitude =
EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
// Duplicate rumble for now
buffer[4] = buffer[0];
buffer[5] = buffer[1];
buffer[6] = buffer[2];
buffer[7] = buffer[3];
return SendVibrationReport(buffer);
}
u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
const u8 new_frequency =
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
return static_cast<u16>((new_frequency - 0x60) * 4);
}
u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
const u8 new_frequency =
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
return static_cast<u8>(new_frequency - 0x40);
}
u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
/* More information about these values can be found here:
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
*/
constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
std::pair<f32, int>{0.0f, 0x0},
{0.01f, 0x2},
{0.012f, 0x4},
{0.014f, 0x6},
{0.017f, 0x8},
{0.02f, 0x0a},
{0.024f, 0x0c},
{0.028f, 0x0e},
{0.033f, 0x10},
{0.04f, 0x12},
{0.047f, 0x14},
{0.056f, 0x16},
{0.067f, 0x18},
{0.08f, 0x1a},
{0.095f, 0x1c},
{0.112f, 0x1e},
{0.117f, 0x20},
{0.123f, 0x22},
{0.128f, 0x24},
{0.134f, 0x26},
{0.14f, 0x28},
{0.146f, 0x2a},
{0.152f, 0x2c},
{0.159f, 0x2e},
{0.166f, 0x30},
{0.173f, 0x32},
{0.181f, 0x34},
{0.189f, 0x36},
{0.198f, 0x38},
{0.206f, 0x3a},
{0.215f, 0x3c},
{0.225f, 0x3e},
{0.23f, 0x40},
{0.235f, 0x42},
{0.24f, 0x44},
{0.245f, 0x46},
{0.251f, 0x48},
{0.256f, 0x4a},
{0.262f, 0x4c},
{0.268f, 0x4e},
{0.273f, 0x50},
{0.279f, 0x52},
{0.286f, 0x54},
{0.292f, 0x56},
{0.298f, 0x58},
{0.305f, 0x5a},
{0.311f, 0x5c},
{0.318f, 0x5e},
{0.325f, 0x60},
{0.332f, 0x62},
{0.34f, 0x64},
{0.347f, 0x66},
{0.355f, 0x68},
{0.362f, 0x6a},
{0.37f, 0x6c},
{0.378f, 0x6e},
{0.387f, 0x70},
{0.395f, 0x72},
{0.404f, 0x74},
{0.413f, 0x76},
{0.422f, 0x78},
{0.431f, 0x7a},
{0.44f, 0x7c},
{0.45f, 0x7e},
{0.46f, 0x80},
{0.47f, 0x82},
{0.48f, 0x84},
{0.491f, 0x86},
{0.501f, 0x88},
{0.512f, 0x8a},
{0.524f, 0x8c},
{0.535f, 0x8e},
{0.547f, 0x90},
{0.559f, 0x92},
{0.571f, 0x94},
{0.584f, 0x96},
{0.596f, 0x98},
{0.609f, 0x9a},
{0.623f, 0x9c},
{0.636f, 0x9e},
{0.65f, 0xa0},
{0.665f, 0xa2},
{0.679f, 0xa4},
{0.694f, 0xa6},
{0.709f, 0xa8},
{0.725f, 0xaa},
{0.741f, 0xac},
{0.757f, 0xae},
{0.773f, 0xb0},
{0.79f, 0xb2},
{0.808f, 0xb4},
{0.825f, 0xb6},
{0.843f, 0xb8},
{0.862f, 0xba},
{0.881f, 0xbc},
{0.9f, 0xbe},
{0.92f, 0xc0},
{0.94f, 0xc2},
{0.96f, 0xc4},
{0.981f, 0xc6},
{1.003f, 0xc8},
};
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
if (amplitude <= amplitude_value) {
return static_cast<u8>(code);
}
}
return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
}
u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
/* More information about these values can be found here:
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
*/
constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
std::pair<f32, int>{0.0f, 0x0040},
{0.01f, 0x8040},
{0.012f, 0x0041},
{0.014f, 0x8041},
{0.017f, 0x0042},
{0.02f, 0x8042},
{0.024f, 0x0043},
{0.028f, 0x8043},
{0.033f, 0x0044},
{0.04f, 0x8044},
{0.047f, 0x0045},
{0.056f, 0x8045},
{0.067f, 0x0046},
{0.08f, 0x8046},
{0.095f, 0x0047},
{0.112f, 0x8047},
{0.117f, 0x0048},
{0.123f, 0x8048},
{0.128f, 0x0049},
{0.134f, 0x8049},
{0.14f, 0x004a},
{0.146f, 0x804a},
{0.152f, 0x004b},
{0.159f, 0x804b},
{0.166f, 0x004c},
{0.173f, 0x804c},
{0.181f, 0x004d},
{0.189f, 0x804d},
{0.198f, 0x004e},
{0.206f, 0x804e},
{0.215f, 0x004f},
{0.225f, 0x804f},
{0.23f, 0x0050},
{0.235f, 0x8050},
{0.24f, 0x0051},
{0.245f, 0x8051},
{0.251f, 0x0052},
{0.256f, 0x8052},
{0.262f, 0x0053},
{0.268f, 0x8053},
{0.273f, 0x0054},
{0.279f, 0x8054},
{0.286f, 0x0055},
{0.292f, 0x8055},
{0.298f, 0x0056},
{0.305f, 0x8056},
{0.311f, 0x0057},
{0.318f, 0x8057},
{0.325f, 0x0058},
{0.332f, 0x8058},
{0.34f, 0x0059},
{0.347f, 0x8059},
{0.355f, 0x005a},
{0.362f, 0x805a},
{0.37f, 0x005b},
{0.378f, 0x805b},
{0.387f, 0x005c},
{0.395f, 0x805c},
{0.404f, 0x005d},
{0.413f, 0x805d},
{0.422f, 0x005e},
{0.431f, 0x805e},
{0.44f, 0x005f},
{0.45f, 0x805f},
{0.46f, 0x0060},
{0.47f, 0x8060},
{0.48f, 0x0061},
{0.491f, 0x8061},
{0.501f, 0x0062},
{0.512f, 0x8062},
{0.524f, 0x0063},
{0.535f, 0x8063},
{0.547f, 0x0064},
{0.559f, 0x8064},
{0.571f, 0x0065},
{0.584f, 0x8065},
{0.596f, 0x0066},
{0.609f, 0x8066},
{0.623f, 0x0067},
{0.636f, 0x8067},
{0.65f, 0x0068},
{0.665f, 0x8068},
{0.679f, 0x0069},
{0.694f, 0x8069},
{0.709f, 0x006a},
{0.725f, 0x806a},
{0.741f, 0x006b},
{0.757f, 0x806b},
{0.773f, 0x006c},
{0.79f, 0x806c},
{0.808f, 0x006d},
{0.825f, 0x806d},
{0.843f, 0x006e},
{0.862f, 0x806e},
{0.881f, 0x006f},
{0.9f, 0x806f},
{0.92f, 0x0070},
{0.94f, 0x8070},
{0.96f, 0x0071},
{0.981f, 0x8071},
{1.003f, 0x0072},
};
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
if (amplitude <= amplitude_value) {
return static_cast<u16>(code);
}
}
return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
}
} // namespace InputCommon::Joycon

@ -0,0 +1,33 @@
// 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 RumbleProtocol final : private JoyconCommonProtocol {
public:
RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
DriverResult EnableRumble(bool is_enabled);
DriverResult SendVibration(const VibrationValue& vibration);
private:
u16 EncodeHighFrequency(f32 frequency) const;
u8 EncodeLowFrequency(f32 frequency) const;
u8 EncodeHighAmplitude(f32 amplitude) const;
u16 EncodeLowAmplitude(f32 amplitude) const;
};
} // namespace InputCommon::Joycon