Browse Source
Merge pull request #9492 from german77/joycon_release
Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2nce_cpp
committed by
GitHub
58 changed files with 5812 additions and 408 deletions
-
68src/common/input.h
-
1src/common/settings.h
-
298src/core/hid/emulated_controller.cpp
-
64src/core/hid/emulated_controller.h
-
46src/core/hid/emulated_devices.cpp
-
18src/core/hid/emulated_devices.h
-
12src/core/hid/input_converter.cpp
-
10src/core/hid/input_converter.h
-
18src/core/hle/service/hid/controllers/npad.cpp
-
24src/core/hle/service/hid/hidbus.cpp
-
8src/core/hle/service/hid/hidbus/ringcon.cpp
-
4src/core/hle/service/hid/hidbus/ringcon.h
-
18src/core/hle/service/hid/irs.cpp
-
7src/core/hle/service/nfc/nfc_device.cpp
-
7src/core/hle/service/nfp/nfp_device.cpp
-
21src/input_common/CMakeLists.txt
-
4src/input_common/drivers/camera.cpp
-
4src/input_common/drivers/camera.h
-
6src/input_common/drivers/gc_adapter.cpp
-
2src/input_common/drivers/gc_adapter.h
-
677src/input_common/drivers/joycon.cpp
-
111src/input_common/drivers/joycon.h
-
23src/input_common/drivers/sdl_driver.cpp
-
2src/input_common/drivers/sdl_driver.h
-
11src/input_common/drivers/virtual_amiibo.cpp
-
2src/input_common/drivers/virtual_amiibo.h
-
572src/input_common/helpers/joycon_driver.cpp
-
150src/input_common/helpers/joycon_driver.h
-
184src/input_common/helpers/joycon_protocol/calibration.cpp
-
64src/input_common/helpers/joycon_protocol/calibration.h
-
299src/input_common/helpers/joycon_protocol/common_protocol.cpp
-
173src/input_common/helpers/joycon_protocol/common_protocol.h
-
125src/input_common/helpers/joycon_protocol/generic_functions.cpp
-
108src/input_common/helpers/joycon_protocol/generic_functions.h
-
298src/input_common/helpers/joycon_protocol/irs.cpp
-
63src/input_common/helpers/joycon_protocol/irs.h
-
612src/input_common/helpers/joycon_protocol/joycon_types.h
-
400src/input_common/helpers/joycon_protocol/nfc.cpp
-
61src/input_common/helpers/joycon_protocol/nfc.h
-
341src/input_common/helpers/joycon_protocol/poller.cpp
-
81src/input_common/helpers/joycon_protocol/poller.h
-
117src/input_common/helpers/joycon_protocol/ringcon.cpp
-
38src/input_common/helpers/joycon_protocol/ringcon.h
-
299src/input_common/helpers/joycon_protocol/rumble.cpp
-
33src/input_common/helpers/joycon_protocol/rumble.h
-
37src/input_common/input_engine.cpp
-
25src/input_common/input_engine.h
-
78src/input_common/input_poller.cpp
-
11src/input_common/input_poller.h
-
14src/input_common/main.cpp
-
2src/yuzu/configuration/config.cpp
-
2src/yuzu/configuration/configure_input_advanced.cpp
-
22src/yuzu/configuration/configure_input_advanced.ui
-
20src/yuzu/configuration/configure_input_player.cpp
-
10src/yuzu/configuration/configure_input_player_widget.cpp
-
105src/yuzu/configuration/configure_ringcon.cpp
-
14src/yuzu/configuration/configure_ringcon.h
-
396src/yuzu/configuration/configure_ringcon.ui
@ -0,0 +1,677 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
#include "common/param_package.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "common/thread.h"
|
||||
|
#include "input_common/drivers/joycon.h"
|
||||
|
#include "input_common/helpers/joycon_driver.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
|
||||
|
namespace InputCommon { |
||||
|
|
||||
|
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { |
||||
|
// Avoid conflicting with SDL driver
|
||||
|
if (!Settings::values.enable_joycon_driver) { |
||||
|
return; |
||||
|
} |
||||
|
LOG_INFO(Input, "Joycon driver Initialization started"); |
||||
|
const int init_res = SDL_hid_init(); |
||||
|
if (init_res == 0) { |
||||
|
Setup(); |
||||
|
} else { |
||||
|
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Joycons::~Joycons() { |
||||
|
Reset(); |
||||
|
} |
||||
|
|
||||
|
void Joycons::Reset() { |
||||
|
scan_thread = {}; |
||||
|
for (const auto& device : left_joycons) { |
||||
|
if (!device) { |
||||
|
continue; |
||||
|
} |
||||
|
device->Stop(); |
||||
|
} |
||||
|
for (const auto& device : right_joycons) { |
||||
|
if (!device) { |
||||
|
continue; |
||||
|
} |
||||
|
device->Stop(); |
||||
|
} |
||||
|
SDL_hid_exit(); |
||||
|
} |
||||
|
|
||||
|
void Joycons::Setup() { |
||||
|
u32 port = 0; |
||||
|
PreSetController(GetIdentifier(0, Joycon::ControllerType::None)); |
||||
|
for (auto& device : left_joycons) { |
||||
|
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); |
||||
|
device = std::make_shared<Joycon::JoyconDriver>(port++); |
||||
|
} |
||||
|
port = 0; |
||||
|
for (auto& device : right_joycons) { |
||||
|
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); |
||||
|
device = std::make_shared<Joycon::JoyconDriver>(port++); |
||||
|
} |
||||
|
|
||||
|
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); |
||||
|
} |
||||
|
|
||||
|
void Joycons::ScanThread(std::stop_token stop_token) { |
||||
|
constexpr u16 nintendo_vendor_id = 0x057e; |
||||
|
Common::SetCurrentThreadName("JoyconScanThread"); |
||||
|
while (!stop_token.stop_requested()) { |
||||
|
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); |
||||
|
SDL_hid_device_info* cur_dev = devs; |
||||
|
|
||||
|
while (cur_dev) { |
||||
|
if (IsDeviceNew(cur_dev)) { |
||||
|
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, |
||||
|
cur_dev->product_id); |
||||
|
RegisterNewDevice(cur_dev); |
||||
|
} |
||||
|
cur_dev = cur_dev->next; |
||||
|
} |
||||
|
|
||||
|
SDL_hid_free_enumeration(devs); |
||||
|
std::this_thread::sleep_for(std::chrono::seconds(5)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { |
||||
|
Joycon::ControllerType type{}; |
||||
|
Joycon::SerialNumber serial_number{}; |
||||
|
|
||||
|
const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); |
||||
|
if (result != Joycon::DriverResult::Success) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); |
||||
|
if (result2 != Joycon::DriverResult::Success) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) { |
||||
|
if (!device) { |
||||
|
return false; |
||||
|
} |
||||
|
if (!device->IsConnected()) { |
||||
|
return false; |
||||
|
} |
||||
|
if (device->GetHandleSerialNumber() != serial_number) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
}; |
||||
|
|
||||
|
// Check if device already exist
|
||||
|
switch (type) { |
||||
|
case Joycon::ControllerType::Left: |
||||
|
for (const auto& device : left_joycons) { |
||||
|
if (is_handle_identical(device)) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
case Joycon::ControllerType::Right: |
||||
|
for (const auto& device : right_joycons) { |
||||
|
if (is_handle_identical(device)) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { |
||||
|
Joycon::ControllerType type{}; |
||||
|
auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); |
||||
|
auto handle = GetNextFreeHandle(type); |
||||
|
if (handle == nullptr) { |
||||
|
LOG_WARNING(Input, "No free handles available"); |
||||
|
return; |
||||
|
} |
||||
|
if (result == Joycon::DriverResult::Success) { |
||||
|
result = handle->RequestDeviceAccess(device_info); |
||||
|
} |
||||
|
if (result == Joycon::DriverResult::Success) { |
||||
|
LOG_WARNING(Input, "Initialize device"); |
||||
|
|
||||
|
const std::size_t port = handle->GetDevicePort(); |
||||
|
const Joycon::JoyconCallbacks callbacks{ |
||||
|
.on_battery_data = {[this, port, type](Joycon::Battery value) { |
||||
|
OnBatteryUpdate(port, type, value); |
||||
|
}}, |
||||
|
.on_color_data = {[this, port, type](Joycon::Color value) { |
||||
|
OnColorUpdate(port, type, value); |
||||
|
}}, |
||||
|
.on_button_data = {[this, port, type](int id, bool 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); |
||||
|
}}, |
||||
|
.on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, |
||||
|
.on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { |
||||
|
OnAmiiboUpdate(port, amiibo_data); |
||||
|
}}, |
||||
|
.on_camera_data = {[this, port](const std::vector<u8>& camera_data, |
||||
|
Joycon::IrsResolution format) { |
||||
|
OnCameraUpdate(port, camera_data, format); |
||||
|
}}, |
||||
|
}; |
||||
|
|
||||
|
handle->InitializeDevice(); |
||||
|
handle->SetCallbacks(callbacks); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle( |
||||
|
Joycon::ControllerType type) const { |
||||
|
if (type == Joycon::ControllerType::Left) { |
||||
|
const auto unconnected_device = |
||||
|
std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); }); |
||||
|
if (unconnected_device != left_joycons.end()) { |
||||
|
return *unconnected_device; |
||||
|
} |
||||
|
} |
||||
|
if (type == Joycon::ControllerType::Right) { |
||||
|
const auto unconnected_device = std::ranges::find_if( |
||||
|
right_joycons, [](auto& device) { return !device->IsConnected(); }); |
||||
|
|
||||
|
if (unconnected_device != right_joycons.end()) { |
||||
|
return *unconnected_device; |
||||
|
} |
||||
|
} |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { |
||||
|
const auto handle = GetHandle(identifier); |
||||
|
if (handle == nullptr) { |
||||
|
return false; |
||||
|
} |
||||
|
return handle->IsVibrationEnabled(); |
||||
|
} |
||||
|
|
||||
|
Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier, |
||||
|
const Common::Input::VibrationStatus& vibration) { |
||||
|
const Joycon::VibrationValue native_vibration{ |
||||
|
.low_amplitude = vibration.low_amplitude, |
||||
|
.low_frequency = vibration.low_frequency, |
||||
|
.high_amplitude = vibration.high_amplitude, |
||||
|
.high_frequency = vibration.high_frequency, |
||||
|
}; |
||||
|
auto handle = GetHandle(identifier); |
||||
|
if (handle == nullptr) { |
||||
|
return Common::Input::DriverResult::InvalidHandle; |
||||
|
} |
||||
|
|
||||
|
handle->SetVibration(native_vibration); |
||||
|
return Common::Input::DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, |
||||
|
const Common::Input::LedStatus& led_status) { |
||||
|
auto handle = GetHandle(identifier); |
||||
|
if (handle == nullptr) { |
||||
|
return Common::Input::DriverResult::InvalidHandle; |
||||
|
} |
||||
|
int led_config = led_status.led_1 ? 1 : 0; |
||||
|
led_config += led_status.led_2 ? 2 : 0; |
||||
|
led_config += led_status.led_3 ? 4 : 0; |
||||
|
led_config += led_status.led_4 ? 8 : 0; |
||||
|
|
||||
|
return static_cast<Common::Input::DriverResult>( |
||||
|
handle->SetLedConfig(static_cast<u8>(led_config))); |
||||
|
} |
||||
|
|
||||
|
Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, |
||||
|
Common::Input::CameraFormat camera_format) { |
||||
|
auto handle = GetHandle(identifier); |
||||
|
if (handle == nullptr) { |
||||
|
return Common::Input::DriverResult::InvalidHandle; |
||||
|
} |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig( |
||||
|
Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format))); |
||||
|
}; |
||||
|
|
||||
|
Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { |
||||
|
return Common::Input::NfcState::Success; |
||||
|
}; |
||||
|
|
||||
|
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, |
||||
|
const std::vector<u8>& data) { |
||||
|
return Common::Input::NfcState::NotSupported; |
||||
|
}; |
||||
|
|
||||
|
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, |
||||
|
const Common::Input::PollingMode polling_mode) { |
||||
|
auto handle = GetHandle(identifier); |
||||
|
if (handle == nullptr) { |
||||
|
LOG_ERROR(Input, "Invalid handle {}", identifier.port); |
||||
|
return Common::Input::DriverResult::InvalidHandle; |
||||
|
} |
||||
|
|
||||
|
switch (polling_mode) { |
||||
|
case Common::Input::PollingMode::Active: |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetActiveMode()); |
||||
|
case Common::Input::PollingMode::Pasive: |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode()); |
||||
|
case Common::Input::PollingMode::IR: |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetIrMode()); |
||||
|
case Common::Input::PollingMode::NFC: |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetNfcMode()); |
||||
|
case Common::Input::PollingMode::Ring: |
||||
|
return static_cast<Common::Input::DriverResult>(handle->SetRingConMode()); |
||||
|
default: |
||||
|
return Common::Input::DriverResult::NotSupported; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, |
||||
|
Joycon::Battery value) { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
if (value.charging != 0) { |
||||
|
SetBattery(identifier, Common::Input::BatteryLevel::Charging); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Common::Input::BatteryLevel battery{}; |
||||
|
switch (value.status) { |
||||
|
case 0: |
||||
|
battery = Common::Input::BatteryLevel::Empty; |
||||
|
break; |
||||
|
case 1: |
||||
|
battery = Common::Input::BatteryLevel::Critical; |
||||
|
break; |
||||
|
case 2: |
||||
|
battery = Common::Input::BatteryLevel::Low; |
||||
|
break; |
||||
|
case 3: |
||||
|
battery = Common::Input::BatteryLevel::Medium; |
||||
|
break; |
||||
|
case 4: |
||||
|
default: |
||||
|
battery = Common::Input::BatteryLevel::Full; |
||||
|
break; |
||||
|
} |
||||
|
SetBattery(identifier, battery); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, |
||||
|
const Joycon::Color& value) { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
Common::Input::BodyColorStatus color{ |
||||
|
.body = value.body, |
||||
|
.buttons = value.buttons, |
||||
|
.left_grip = value.left_grip, |
||||
|
.right_grip = value.right_grip, |
||||
|
}; |
||||
|
SetColor(identifier, color); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
SetButton(identifier, id, value); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
SetAxis(identifier, id, value); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, |
||||
|
const Joycon::MotionData& value) { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
BasicMotion motion_data{ |
||||
|
.gyro_x = value.gyro_x, |
||||
|
.gyro_y = value.gyro_y, |
||||
|
.gyro_z = value.gyro_z, |
||||
|
.accel_x = value.accel_x, |
||||
|
.accel_y = value.accel_y, |
||||
|
.accel_z = value.accel_z, |
||||
|
.delta_timestamp = 15000, |
||||
|
}; |
||||
|
SetMotion(identifier, id, motion_data); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnRingConUpdate(f32 ring_data) { |
||||
|
// To simplify ring detection it will always be mapped to an empty identifier for all
|
||||
|
// controllers
|
||||
|
constexpr PadIdentifier identifier = { |
||||
|
.guid = Common::UUID{}, |
||||
|
.port = 0, |
||||
|
.pad = 0, |
||||
|
}; |
||||
|
SetAxis(identifier, 100, ring_data); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) { |
||||
|
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); |
||||
|
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved |
||||
|
: Common::Input::NfcState::NewAmiibo; |
||||
|
SetNfc(identifier, {nfc_state, amiibo_data}); |
||||
|
} |
||||
|
|
||||
|
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, |
||||
|
Joycon::IrsResolution format) { |
||||
|
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); |
||||
|
SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data}); |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const { |
||||
|
auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) { |
||||
|
if (!device) { |
||||
|
return false; |
||||
|
} |
||||
|
if (!device->IsConnected()) { |
||||
|
return false; |
||||
|
} |
||||
|
if (device->GetDevicePort() == identifier.port) { |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}; |
||||
|
const auto type = static_cast<Joycon::ControllerType>(identifier.pad); |
||||
|
|
||||
|
if (type == Joycon::ControllerType::Left) { |
||||
|
const auto matching_device = std::ranges::find_if( |
||||
|
left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); |
||||
|
|
||||
|
if (matching_device != left_joycons.end()) { |
||||
|
return *matching_device; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (type == Joycon::ControllerType::Right) { |
||||
|
const auto matching_device = std::ranges::find_if( |
||||
|
right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); |
||||
|
|
||||
|
if (matching_device != right_joycons.end()) { |
||||
|
return *matching_device; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { |
||||
|
const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0, |
||||
|
0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)}; |
||||
|
return { |
||||
|
.guid = Common::UUID{guid}, |
||||
|
.port = port, |
||||
|
.pad = static_cast<std::size_t>(type), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const { |
||||
|
const auto identifier = GetIdentifier(port, type); |
||||
|
return { |
||||
|
{"engine", GetEngineName()}, |
||||
|
{"guid", identifier.guid.RawString()}, |
||||
|
{"port", std::to_string(identifier.port)}, |
||||
|
{"pad", std::to_string(identifier.pad)}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const { |
||||
|
std::vector<Common::ParamPackage> devices{}; |
||||
|
|
||||
|
auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) { |
||||
|
if (!device) { |
||||
|
return; |
||||
|
} |
||||
|
if (!device->IsConnected()) { |
||||
|
return; |
||||
|
} |
||||
|
auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType()); |
||||
|
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), |
||||
|
device->GetDevicePort() + 1); |
||||
|
param.Set("display", std::move(name)); |
||||
|
devices.emplace_back(param); |
||||
|
}; |
||||
|
|
||||
|
for (const auto& controller : left_joycons) { |
||||
|
add_entry(controller); |
||||
|
} |
||||
|
for (const auto& controller : right_joycons) { |
||||
|
add_entry(controller); |
||||
|
} |
||||
|
|
||||
|
// List dual joycon pairs
|
||||
|
for (std::size_t i = 0; i < MaxSupportedControllers; i++) { |
||||
|
if (!left_joycons[i] || !right_joycons[i]) { |
||||
|
continue; |
||||
|
} |
||||
|
if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) { |
||||
|
continue; |
||||
|
} |
||||
|
auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType()); |
||||
|
const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType()); |
||||
|
const auto type = Joycon::ControllerType::Dual; |
||||
|
std::string name = fmt::format("{} {}", JoyconName(type), i + 1); |
||||
|
|
||||
|
main_param.Set("display", std::move(name)); |
||||
|
main_param.Set("guid2", second_param.Get("guid", "")); |
||||
|
main_param.Set("pad", std::to_string(static_cast<size_t>(type))); |
||||
|
devices.emplace_back(main_param); |
||||
|
} |
||||
|
|
||||
|
return devices; |
||||
|
} |
||||
|
|
||||
|
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { |
||||
|
static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>, |
||||
|
18> |
||||
|
switch_to_joycon_button = { |
||||
|
std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true}, |
||||
|
{Settings::NativeButton::B, Joycon::PadButton::B, true}, |
||||
|
{Settings::NativeButton::X, Joycon::PadButton::X, true}, |
||||
|
{Settings::NativeButton::Y, Joycon::PadButton::Y, true}, |
||||
|
{Settings::NativeButton::DLeft, Joycon::PadButton::Left, false}, |
||||
|
{Settings::NativeButton::DUp, Joycon::PadButton::Up, false}, |
||||
|
{Settings::NativeButton::DRight, Joycon::PadButton::Right, false}, |
||||
|
{Settings::NativeButton::DDown, Joycon::PadButton::Down, false}, |
||||
|
{Settings::NativeButton::L, Joycon::PadButton::L, false}, |
||||
|
{Settings::NativeButton::R, Joycon::PadButton::R, true}, |
||||
|
{Settings::NativeButton::ZL, Joycon::PadButton::ZL, false}, |
||||
|
{Settings::NativeButton::ZR, Joycon::PadButton::ZR, true}, |
||||
|
{Settings::NativeButton::Plus, Joycon::PadButton::Plus, true}, |
||||
|
{Settings::NativeButton::Minus, Joycon::PadButton::Minus, false}, |
||||
|
{Settings::NativeButton::Home, Joycon::PadButton::Home, true}, |
||||
|
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false}, |
||||
|
{Settings::NativeButton::LStick, Joycon::PadButton::StickL, false}, |
||||
|
{Settings::NativeButton::RStick, Joycon::PadButton::StickR, true}, |
||||
|
}; |
||||
|
|
||||
|
if (!params.Has("port")) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
ButtonMapping mapping{}; |
||||
|
for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) { |
||||
|
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); |
||||
|
auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); |
||||
|
if (pad == Joycon::ControllerType::Dual) { |
||||
|
pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left; |
||||
|
} |
||||
|
|
||||
|
Common::ParamPackage button_params = GetParamPackage(port, pad); |
||||
|
button_params.Set("button", static_cast<int>(joycon_button)); |
||||
|
mapping.insert_or_assign(switch_button, std::move(button_params)); |
||||
|
} |
||||
|
|
||||
|
// Map SL and SR buttons for left joycons
|
||||
|
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) { |
||||
|
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); |
||||
|
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left); |
||||
|
|
||||
|
Common::ParamPackage sl_button_params = button_params; |
||||
|
Common::ParamPackage sr_button_params = button_params; |
||||
|
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL)); |
||||
|
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR)); |
||||
|
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); |
||||
|
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); |
||||
|
} |
||||
|
|
||||
|
// Map SL and SR buttons for right joycons
|
||||
|
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) { |
||||
|
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); |
||||
|
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right); |
||||
|
|
||||
|
Common::ParamPackage sl_button_params = button_params; |
||||
|
Common::ParamPackage sr_button_params = button_params; |
||||
|
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL)); |
||||
|
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR)); |
||||
|
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); |
||||
|
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); |
||||
|
} |
||||
|
|
||||
|
return mapping; |
||||
|
} |
||||
|
|
||||
|
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) { |
||||
|
if (!params.Has("port")) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); |
||||
|
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); |
||||
|
auto pad_right = pad_left; |
||||
|
if (pad_left == Joycon::ControllerType::Dual) { |
||||
|
pad_left = Joycon::ControllerType::Left; |
||||
|
pad_right = Joycon::ControllerType::Right; |
||||
|
} |
||||
|
|
||||
|
AnalogMapping mapping = {}; |
||||
|
Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left); |
||||
|
left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX)); |
||||
|
left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY)); |
||||
|
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); |
||||
|
Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right); |
||||
|
right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX)); |
||||
|
right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY)); |
||||
|
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); |
||||
|
return mapping; |
||||
|
} |
||||
|
|
||||
|
MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { |
||||
|
if (!params.Has("port")) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); |
||||
|
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); |
||||
|
auto pad_right = pad_left; |
||||
|
if (pad_left == Joycon::ControllerType::Dual) { |
||||
|
pad_left = Joycon::ControllerType::Left; |
||||
|
pad_right = Joycon::ControllerType::Right; |
||||
|
} |
||||
|
|
||||
|
MotionMapping mapping = {}; |
||||
|
Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left); |
||||
|
left_motion_params.Set("motion", 0); |
||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); |
||||
|
Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right); |
||||
|
right_Motion_params.Set("motion", 1); |
||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); |
||||
|
return mapping; |
||||
|
} |
||||
|
|
||||
|
Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { |
||||
|
const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0)); |
||||
|
switch (button) { |
||||
|
case Joycon::PadButton::Left: |
||||
|
return Common::Input::ButtonNames::ButtonLeft; |
||||
|
case Joycon::PadButton::Right: |
||||
|
return Common::Input::ButtonNames::ButtonRight; |
||||
|
case Joycon::PadButton::Down: |
||||
|
return Common::Input::ButtonNames::ButtonDown; |
||||
|
case Joycon::PadButton::Up: |
||||
|
return Common::Input::ButtonNames::ButtonUp; |
||||
|
case Joycon::PadButton::LeftSL: |
||||
|
case Joycon::PadButton::RightSL: |
||||
|
return Common::Input::ButtonNames::TriggerSL; |
||||
|
case Joycon::PadButton::LeftSR: |
||||
|
case Joycon::PadButton::RightSR: |
||||
|
return Common::Input::ButtonNames::TriggerSR; |
||||
|
case Joycon::PadButton::L: |
||||
|
return Common::Input::ButtonNames::TriggerL; |
||||
|
case Joycon::PadButton::R: |
||||
|
return Common::Input::ButtonNames::TriggerR; |
||||
|
case Joycon::PadButton::ZL: |
||||
|
return Common::Input::ButtonNames::TriggerZL; |
||||
|
case Joycon::PadButton::ZR: |
||||
|
return Common::Input::ButtonNames::TriggerZR; |
||||
|
case Joycon::PadButton::A: |
||||
|
return Common::Input::ButtonNames::ButtonA; |
||||
|
case Joycon::PadButton::B: |
||||
|
return Common::Input::ButtonNames::ButtonB; |
||||
|
case Joycon::PadButton::X: |
||||
|
return Common::Input::ButtonNames::ButtonX; |
||||
|
case Joycon::PadButton::Y: |
||||
|
return Common::Input::ButtonNames::ButtonY; |
||||
|
case Joycon::PadButton::Plus: |
||||
|
return Common::Input::ButtonNames::ButtonPlus; |
||||
|
case Joycon::PadButton::Minus: |
||||
|
return Common::Input::ButtonNames::ButtonMinus; |
||||
|
case Joycon::PadButton::Home: |
||||
|
return Common::Input::ButtonNames::ButtonHome; |
||||
|
case Joycon::PadButton::Capture: |
||||
|
return Common::Input::ButtonNames::ButtonCapture; |
||||
|
case Joycon::PadButton::StickL: |
||||
|
return Common::Input::ButtonNames::ButtonStickL; |
||||
|
case Joycon::PadButton::StickR: |
||||
|
return Common::Input::ButtonNames::ButtonStickR; |
||||
|
default: |
||||
|
return Common::Input::ButtonNames::Undefined; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const { |
||||
|
if (params.Has("button")) { |
||||
|
return GetUIButtonName(params); |
||||
|
} |
||||
|
if (params.Has("axis")) { |
||||
|
return Common::Input::ButtonNames::Value; |
||||
|
} |
||||
|
if (params.Has("motion")) { |
||||
|
return Common::Input::ButtonNames::Engine; |
||||
|
} |
||||
|
|
||||
|
return Common::Input::ButtonNames::Invalid; |
||||
|
} |
||||
|
|
||||
|
std::string Joycons::JoyconName(Joycon::ControllerType type) const { |
||||
|
switch (type) { |
||||
|
case Joycon::ControllerType::Left: |
||||
|
return "Left Joycon"; |
||||
|
case Joycon::ControllerType::Right: |
||||
|
return "Right Joycon"; |
||||
|
case Joycon::ControllerType::Pro: |
||||
|
return "Pro Controller"; |
||||
|
case Joycon::ControllerType::Grip: |
||||
|
return "Grip Controller"; |
||||
|
case Joycon::ControllerType::Dual: |
||||
|
return "Dual Joycon"; |
||||
|
default: |
||||
|
return "Unknown Joycon"; |
||||
|
} |
||||
|
} |
||||
|
} // namespace InputCommon
|
||||
@ -0,0 +1,111 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <span> |
||||
|
#include <thread> |
||||
|
#include <SDL_hidapi.h> |
||||
|
|
||||
|
#include "input_common/input_engine.h" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
using SerialNumber = std::array<u8, 15>; |
||||
|
struct Battery; |
||||
|
struct Color; |
||||
|
struct MotionData; |
||||
|
enum class ControllerType; |
||||
|
enum class DriverResult; |
||||
|
enum class IrsResolution; |
||||
|
class JoyconDriver; |
||||
|
} // namespace InputCommon::Joycon |
||||
|
|
||||
|
namespace InputCommon { |
||||
|
|
||||
|
class Joycons final : public InputCommon::InputEngine { |
||||
|
public: |
||||
|
explicit Joycons(const std::string& input_engine_); |
||||
|
|
||||
|
~Joycons(); |
||||
|
|
||||
|
bool IsVibrationEnabled(const PadIdentifier& identifier) override; |
||||
|
Common::Input::DriverResult SetVibration( |
||||
|
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; |
||||
|
|
||||
|
Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, |
||||
|
const Common::Input::LedStatus& led_status) override; |
||||
|
|
||||
|
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, |
||||
|
Common::Input::CameraFormat camera_format) override; |
||||
|
|
||||
|
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; |
||||
|
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, |
||||
|
const std::vector<u8>& data) override; |
||||
|
|
||||
|
Common::Input::DriverResult SetPollingMode( |
||||
|
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; |
||||
|
|
||||
|
/// Used for automapping features |
||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override; |
||||
|
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; |
||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; |
||||
|
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; |
||||
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; |
||||
|
|
||||
|
private: |
||||
|
static constexpr std::size_t MaxSupportedControllers = 8; |
||||
|
|
||||
|
/// For shutting down, clear all data, join all threads, release usb devices |
||||
|
void Reset(); |
||||
|
|
||||
|
/// Registers controllers, clears all data and starts the scan thread |
||||
|
void Setup(); |
||||
|
|
||||
|
/// Actively searchs for new devices |
||||
|
void ScanThread(std::stop_token stop_token); |
||||
|
|
||||
|
/// Returns true if device is valid and not registered |
||||
|
bool IsDeviceNew(SDL_hid_device_info* device_info) const; |
||||
|
|
||||
|
/// Tries to connect to the new device |
||||
|
void RegisterNewDevice(SDL_hid_device_info* device_info); |
||||
|
|
||||
|
/// Returns the next free handle |
||||
|
std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const; |
||||
|
|
||||
|
void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value); |
||||
|
void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value); |
||||
|
void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value); |
||||
|
void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value); |
||||
|
void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, |
||||
|
const Joycon::MotionData& value); |
||||
|
void OnRingConUpdate(f32 ring_data); |
||||
|
void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data); |
||||
|
void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, |
||||
|
Joycon::IrsResolution format); |
||||
|
|
||||
|
/// Returns a JoyconHandle corresponding to a PadIdentifier |
||||
|
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const; |
||||
|
|
||||
|
/// Returns a PadIdentifier corresponding to the port number and joycon type |
||||
|
PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const; |
||||
|
|
||||
|
/// Returns a ParamPackage corresponding to the port number and joycon type |
||||
|
Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const; |
||||
|
|
||||
|
std::string JoyconName(std::size_t port) const; |
||||
|
|
||||
|
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; |
||||
|
|
||||
|
/// Returns the name of the device in text format |
||||
|
std::string JoyconName(Joycon::ControllerType type) const; |
||||
|
|
||||
|
std::jthread scan_thread; |
||||
|
|
||||
|
// Joycon types are split by type to ease supporting dualjoycon configurations |
||||
|
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{}; |
||||
|
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon |
||||
@ -0,0 +1,572 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/swap.h"
|
||||
|
#include "common/thread.h"
|
||||
|
#include "input_common/helpers/joycon_driver.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/calibration.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/generic_functions.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/irs.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/nfc.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 { |
||||
|
JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { |
||||
|
hidapi_handle = std::make_shared<JoyconHandle>(); |
||||
|
} |
||||
|
|
||||
|
JoyconDriver::~JoyconDriver() { |
||||
|
Stop(); |
||||
|
} |
||||
|
|
||||
|
void JoyconDriver::Stop() { |
||||
|
is_connected = false; |
||||
|
input_thread = {}; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
|
||||
|
handle_device_type = ControllerType::None; |
||||
|
GetDeviceType(device_info, handle_device_type); |
||||
|
if (handle_device_type == ControllerType::None) { |
||||
|
return DriverResult::UnsupportedControllerType; |
||||
|
} |
||||
|
|
||||
|
hidapi_handle->handle = |
||||
|
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); |
||||
|
std::memcpy(&handle_serial_number, device_info->serial_number, 15); |
||||
|
if (!hidapi_handle->handle) { |
||||
|
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", |
||||
|
device_info->vendor_id, device_info->product_id); |
||||
|
return DriverResult::HandleInUse; |
||||
|
} |
||||
|
SDL_hid_set_nonblocking(hidapi_handle->handle, 1); |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::InitializeDevice() { |
||||
|
if (!hidapi_handle->handle) { |
||||
|
return DriverResult::InvalidHandle; |
||||
|
} |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
disable_input_thread = true; |
||||
|
|
||||
|
// Reset Counters
|
||||
|
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; |
||||
|
accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; |
||||
|
|
||||
|
// Initialize HW Protocols
|
||||
|
calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); |
||||
|
generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); |
||||
|
irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); |
||||
|
nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); |
||||
|
ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); |
||||
|
rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); |
||||
|
|
||||
|
// Get fixed joycon info
|
||||
|
generic_protocol->GetVersionNumber(version); |
||||
|
generic_protocol->GetColor(color); |
||||
|
if (handle_device_type == ControllerType::Pro) { |
||||
|
// Some 3rd party controllers aren't pro controllers
|
||||
|
generic_protocol->GetControllerType(device_type); |
||||
|
} else { |
||||
|
device_type = handle_device_type; |
||||
|
} |
||||
|
generic_protocol->GetSerialNumber(serial_number); |
||||
|
supported_features = GetSupportedFeatures(); |
||||
|
|
||||
|
// Get Calibration data
|
||||
|
calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); |
||||
|
calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); |
||||
|
calibration_protocol->GetImuCalibration(motion_calibration); |
||||
|
|
||||
|
// Set led status
|
||||
|
generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); |
||||
|
|
||||
|
// Apply HW configuration
|
||||
|
SetPollingMode(); |
||||
|
|
||||
|
// Initialize joycon poller
|
||||
|
joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, |
||||
|
right_stick_calibration, motion_calibration); |
||||
|
|
||||
|
// Start pooling for data
|
||||
|
is_connected = true; |
||||
|
if (!input_thread_running) { |
||||
|
input_thread = |
||||
|
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); |
||||
|
} |
||||
|
|
||||
|
disable_input_thread = false; |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
void JoyconDriver::InputThread(std::stop_token stop_token) { |
||||
|
LOG_INFO(Input, "Joycon Adapter input thread started"); |
||||
|
Common::SetCurrentThreadName("JoyconInput"); |
||||
|
input_thread_running = true; |
||||
|
|
||||
|
// Max update rate is 5ms, ensure we are always able to read a bit faster
|
||||
|
constexpr int ThreadDelay = 2; |
||||
|
std::vector<u8> buffer(MaxBufferSize); |
||||
|
|
||||
|
while (!stop_token.stop_requested()) { |
||||
|
int status = 0; |
||||
|
|
||||
|
if (!IsInputThreadValid()) { |
||||
|
input_thread.request_stop(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// By disabling the input thread we can ensure custom commands will succeed as no package is
|
||||
|
// skipped
|
||||
|
if (!disable_input_thread) { |
||||
|
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), |
||||
|
ThreadDelay); |
||||
|
} else { |
||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); |
||||
|
} |
||||
|
|
||||
|
if (IsPayloadCorrect(status, buffer)) { |
||||
|
OnNewData(buffer); |
||||
|
} |
||||
|
|
||||
|
std::this_thread::yield(); |
||||
|
} |
||||
|
|
||||
|
is_connected = false; |
||||
|
input_thread_running = false; |
||||
|
LOG_INFO(Input, "Joycon Adapter input thread stopped"); |
||||
|
} |
||||
|
|
||||
|
void JoyconDriver::OnNewData(std::span<u8> buffer) { |
||||
|
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) { |
||||
|
case InputReport::STANDARD_FULL_60HZ: |
||||
|
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, |
||||
|
}; |
||||
|
|
||||
|
// 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, |
||||
|
}; |
||||
|
|
||||
|
if (irs_protocol->IsEnabled()) { |
||||
|
irs_protocol->RequestImage(buffer); |
||||
|
joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); |
||||
|
} |
||||
|
|
||||
|
if (nfc_protocol->IsEnabled()) { |
||||
|
if (amiibo_detected) { |
||||
|
if (!nfc_protocol->HasAmiibo()) { |
||||
|
joycon_poller->UpdateAmiibo({}); |
||||
|
amiibo_detected = false; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!amiibo_detected) { |
||||
|
std::vector<u8> data(0x21C); |
||||
|
const auto result = nfc_protocol->ScanAmiibo(data); |
||||
|
if (result == DriverResult::Success) { |
||||
|
joycon_poller->UpdateAmiibo(data); |
||||
|
amiibo_detected = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
switch (report_mode) { |
||||
|
case InputReport::STANDARD_FULL_60HZ: |
||||
|
joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); |
||||
|
break; |
||||
|
case InputReport::NFC_IR_MODE_60HZ: |
||||
|
joycon_poller->ReadNfcIRMode(buffer, motion_status); |
||||
|
break; |
||||
|
case InputReport::SIMPLE_HID_MODE: |
||||
|
joycon_poller->ReadPassiveMode(buffer); |
||||
|
break; |
||||
|
case InputReport::SUBCMD_REPLY: |
||||
|
LOG_DEBUG(Input, "Unhandled command reply"); |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetPollingMode() { |
||||
|
disable_input_thread = true; |
||||
|
|
||||
|
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); |
||||
|
|
||||
|
if (motion_enabled && supported_features.motion) { |
||||
|
generic_protocol->EnableImu(true); |
||||
|
generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, |
||||
|
accelerometer_sensitivity, accelerometer_performance); |
||||
|
} else { |
||||
|
generic_protocol->EnableImu(false); |
||||
|
} |
||||
|
|
||||
|
if (irs_protocol->IsEnabled()) { |
||||
|
irs_protocol->DisableIrs(); |
||||
|
} |
||||
|
|
||||
|
if (nfc_protocol->IsEnabled()) { |
||||
|
amiibo_detected = false; |
||||
|
nfc_protocol->DisableNfc(); |
||||
|
} |
||||
|
|
||||
|
if (ring_protocol->IsEnabled()) { |
||||
|
ring_connected = false; |
||||
|
ring_protocol->DisableRingCon(); |
||||
|
} |
||||
|
|
||||
|
if (irs_enabled && supported_features.irs) { |
||||
|
auto result = irs_protocol->EnableIrs(); |
||||
|
if (result == DriverResult::Success) { |
||||
|
disable_input_thread = false; |
||||
|
return result; |
||||
|
} |
||||
|
irs_protocol->DisableIrs(); |
||||
|
LOG_ERROR(Input, "Error enabling IRS"); |
||||
|
} |
||||
|
|
||||
|
if (nfc_enabled && supported_features.nfc) { |
||||
|
auto result = nfc_protocol->EnableNfc(); |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = nfc_protocol->StartNFCPollingMode(); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
disable_input_thread = false; |
||||
|
return result; |
||||
|
} |
||||
|
nfc_protocol->DisableNfc(); |
||||
|
LOG_ERROR(Input, "Error enabling NFC"); |
||||
|
} |
||||
|
|
||||
|
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 result; |
||||
|
} |
||||
|
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) { |
||||
|
disable_input_thread = false; |
||||
|
return result; |
||||
|
} |
||||
|
LOG_ERROR(Input, "Error enabling passive mode"); |
||||
|
} |
||||
|
|
||||
|
// Default Mode
|
||||
|
const auto result = generic_protocol->EnableActiveMode(); |
||||
|
if (result != DriverResult::Success) { |
||||
|
LOG_ERROR(Input, "Error enabling active mode"); |
||||
|
} |
||||
|
|
||||
|
disable_input_thread = false; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { |
||||
|
SupportedFeatures features{ |
||||
|
.passive = true, |
||||
|
.motion = true, |
||||
|
.vibration = true, |
||||
|
}; |
||||
|
|
||||
|
if (device_type == ControllerType::Right) { |
||||
|
features.nfc = true; |
||||
|
features.irs = true; |
||||
|
features.hidbus = true; |
||||
|
} |
||||
|
|
||||
|
if (device_type == ControllerType::Pro) { |
||||
|
features.nfc = true; |
||||
|
} |
||||
|
return features; |
||||
|
} |
||||
|
|
||||
|
bool JoyconDriver::IsInputThreadValid() const { |
||||
|
if (!is_connected.load()) { |
||||
|
return false; |
||||
|
} |
||||
|
if (hidapi_handle->handle == nullptr) { |
||||
|
return false; |
||||
|
} |
||||
|
// Controller is not responding. Terminate connection
|
||||
|
if (error_counter > MaxErrorCount) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { |
||||
|
if (status <= -1) { |
||||
|
error_counter++; |
||||
|
return false; |
||||
|
} |
||||
|
// There's no new data
|
||||
|
if (status == 0) { |
||||
|
return false; |
||||
|
} |
||||
|
// No reply ever starts with zero
|
||||
|
if (buffer[0] == 0x00) { |
||||
|
error_counter++; |
||||
|
return false; |
||||
|
} |
||||
|
error_counter = 0; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
if (disable_input_thread) { |
||||
|
return DriverResult::HandleInUse; |
||||
|
} |
||||
|
return rumble_protocol->SendVibration(vibration); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
if (disable_input_thread) { |
||||
|
return DriverResult::HandleInUse; |
||||
|
} |
||||
|
return generic_protocol->SetLedPattern(led_pattern); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
if (disable_input_thread) { |
||||
|
return DriverResult::HandleInUse; |
||||
|
} |
||||
|
disable_input_thread = true; |
||||
|
const auto result = irs_protocol->SetIrsConfig(mode_, format_); |
||||
|
disable_input_thread = false; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetPasiveMode() { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
motion_enabled = false; |
||||
|
hidbus_enabled = false; |
||||
|
nfc_enabled = false; |
||||
|
passive_enabled = true; |
||||
|
irs_enabled = false; |
||||
|
return SetPollingMode(); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetActiveMode() { |
||||
|
if (is_ring_disabled_by_irs) { |
||||
|
is_ring_disabled_by_irs = false; |
||||
|
SetActiveMode(); |
||||
|
return SetRingConMode(); |
||||
|
} |
||||
|
|
||||
|
std::scoped_lock lock{mutex}; |
||||
|
motion_enabled = true; |
||||
|
hidbus_enabled = false; |
||||
|
nfc_enabled = false; |
||||
|
passive_enabled = false; |
||||
|
irs_enabled = false; |
||||
|
return SetPollingMode(); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetIrMode() { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
|
||||
|
if (!supported_features.irs) { |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
if (ring_connected) { |
||||
|
is_ring_disabled_by_irs = true; |
||||
|
} |
||||
|
|
||||
|
motion_enabled = false; |
||||
|
hidbus_enabled = false; |
||||
|
nfc_enabled = false; |
||||
|
passive_enabled = false; |
||||
|
irs_enabled = true; |
||||
|
return SetPollingMode(); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetNfcMode() { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
|
||||
|
if (!supported_features.nfc) { |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
motion_enabled = true; |
||||
|
hidbus_enabled = false; |
||||
|
nfc_enabled = true; |
||||
|
passive_enabled = false; |
||||
|
irs_enabled = false; |
||||
|
return SetPollingMode(); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::SetRingConMode() { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
|
||||
|
if (!supported_features.hidbus) { |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
motion_enabled = true; |
||||
|
hidbus_enabled = true; |
||||
|
nfc_enabled = false; |
||||
|
passive_enabled = false; |
||||
|
irs_enabled = false; |
||||
|
|
||||
|
const auto result = SetPollingMode(); |
||||
|
|
||||
|
if (!ring_connected) { |
||||
|
return DriverResult::NoDeviceDetected; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
bool JoyconDriver::IsConnected() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return is_connected.load(); |
||||
|
} |
||||
|
|
||||
|
bool JoyconDriver::IsVibrationEnabled() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return vibration_enabled; |
||||
|
} |
||||
|
|
||||
|
FirmwareVersion JoyconDriver::GetDeviceVersion() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return version; |
||||
|
} |
||||
|
|
||||
|
Color JoyconDriver::GetDeviceColor() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return color; |
||||
|
} |
||||
|
|
||||
|
std::size_t JoyconDriver::GetDevicePort() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return port; |
||||
|
} |
||||
|
|
||||
|
ControllerType JoyconDriver::GetDeviceType() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return device_type; |
||||
|
} |
||||
|
|
||||
|
ControllerType JoyconDriver::GetHandleDeviceType() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return handle_device_type; |
||||
|
} |
||||
|
|
||||
|
SerialNumber JoyconDriver::GetSerialNumber() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return serial_number; |
||||
|
} |
||||
|
|
||||
|
SerialNumber JoyconDriver::GetHandleSerialNumber() const { |
||||
|
std::scoped_lock lock{mutex}; |
||||
|
return handle_serial_number; |
||||
|
} |
||||
|
|
||||
|
void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { |
||||
|
joycon_poller->SetCallbacks(callbacks); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, |
||||
|
ControllerType& controller_type) { |
||||
|
static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{ |
||||
|
std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, |
||||
|
{0x2007, ControllerType::Right}, |
||||
|
}; |
||||
|
constexpr u16 nintendo_vendor_id = 0x057e; |
||||
|
|
||||
|
controller_type = ControllerType::None; |
||||
|
if (device_info->vendor_id != nintendo_vendor_id) { |
||||
|
return DriverResult::UnsupportedControllerType; |
||||
|
} |
||||
|
|
||||
|
for (const auto& [product_id, type] : supported_devices) { |
||||
|
if (device_info->product_id == static_cast<u16>(product_id)) { |
||||
|
controller_type = type; |
||||
|
return Joycon::DriverResult::Success; |
||||
|
} |
||||
|
} |
||||
|
return Joycon::DriverResult::UnsupportedControllerType; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, |
||||
|
SerialNumber& serial_number) { |
||||
|
if (device_info->serial_number == nullptr) { |
||||
|
return DriverResult::Unknown; |
||||
|
} |
||||
|
std::memcpy(&serial_number, device_info->serial_number, 15); |
||||
|
return Joycon::DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,150 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <atomic> |
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <span> |
||||
|
#include <thread> |
||||
|
|
||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
class CalibrationProtocol; |
||||
|
class GenericProtocol; |
||||
|
class IrsProtocol; |
||||
|
class NfcProtocol; |
||||
|
class JoyconPoller; |
||||
|
class RingConProtocol; |
||||
|
class RumbleProtocol; |
||||
|
|
||||
|
class JoyconDriver final { |
||||
|
public: |
||||
|
explicit JoyconDriver(std::size_t port_); |
||||
|
|
||||
|
~JoyconDriver(); |
||||
|
|
||||
|
DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info); |
||||
|
DriverResult InitializeDevice(); |
||||
|
void Stop(); |
||||
|
|
||||
|
bool IsConnected() const; |
||||
|
bool IsVibrationEnabled() const; |
||||
|
|
||||
|
FirmwareVersion GetDeviceVersion() const; |
||||
|
Color GetDeviceColor() const; |
||||
|
std::size_t GetDevicePort() const; |
||||
|
ControllerType GetDeviceType() const; |
||||
|
ControllerType GetHandleDeviceType() const; |
||||
|
SerialNumber GetSerialNumber() const; |
||||
|
SerialNumber GetHandleSerialNumber() const; |
||||
|
|
||||
|
DriverResult SetVibration(const VibrationValue& vibration); |
||||
|
DriverResult SetLedConfig(u8 led_pattern); |
||||
|
DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_); |
||||
|
DriverResult SetPasiveMode(); |
||||
|
DriverResult SetActiveMode(); |
||||
|
DriverResult SetIrMode(); |
||||
|
DriverResult SetNfcMode(); |
||||
|
DriverResult SetRingConMode(); |
||||
|
|
||||
|
void SetCallbacks(const JoyconCallbacks& callbacks); |
||||
|
|
||||
|
// Returns device type from hidapi handle |
||||
|
static DriverResult GetDeviceType(SDL_hid_device_info* device_info, |
||||
|
ControllerType& controller_type); |
||||
|
|
||||
|
// Returns serial number from hidapi handle |
||||
|
static DriverResult GetSerialNumber(SDL_hid_device_info* device_info, |
||||
|
SerialNumber& serial_number); |
||||
|
|
||||
|
private: |
||||
|
struct SupportedFeatures { |
||||
|
bool passive{}; |
||||
|
bool hidbus{}; |
||||
|
bool irs{}; |
||||
|
bool motion{}; |
||||
|
bool nfc{}; |
||||
|
bool vibration{}; |
||||
|
}; |
||||
|
|
||||
|
/// Main thread, actively request new data from the handle |
||||
|
void InputThread(std::stop_token stop_token); |
||||
|
|
||||
|
/// Called everytime a valid package arrives |
||||
|
void OnNewData(std::span<u8> buffer); |
||||
|
|
||||
|
/// Updates device configuration to enable or disable features |
||||
|
DriverResult SetPollingMode(); |
||||
|
|
||||
|
/// Returns true if input thread is valid and doesn't need to be stopped |
||||
|
bool IsInputThreadValid() const; |
||||
|
|
||||
|
/// Returns true if the data should be interpreted. Otherwise the error counter is incremented |
||||
|
bool IsPayloadCorrect(int status, std::span<const u8> buffer); |
||||
|
|
||||
|
/// Returns a list of supported features that can be enabled on this device |
||||
|
SupportedFeatures GetSupportedFeatures(); |
||||
|
|
||||
|
// Protocol Features |
||||
|
std::unique_ptr<CalibrationProtocol> calibration_protocol; |
||||
|
std::unique_ptr<GenericProtocol> generic_protocol; |
||||
|
std::unique_ptr<IrsProtocol> irs_protocol; |
||||
|
std::unique_ptr<NfcProtocol> nfc_protocol; |
||||
|
std::unique_ptr<JoyconPoller> joycon_poller; |
||||
|
std::unique_ptr<RingConProtocol> ring_protocol; |
||||
|
std::unique_ptr<RumbleProtocol> rumble_protocol; |
||||
|
|
||||
|
// Connection status |
||||
|
std::atomic<bool> is_connected{}; |
||||
|
u64 delta_time; |
||||
|
std::size_t error_counter{}; |
||||
|
std::shared_ptr<JoyconHandle> hidapi_handle; |
||||
|
std::chrono::time_point<std::chrono::steady_clock> last_update; |
||||
|
|
||||
|
// External device status |
||||
|
bool starlink_connected{}; |
||||
|
bool ring_connected{}; |
||||
|
bool amiibo_detected{}; |
||||
|
bool is_ring_disabled_by_irs{}; |
||||
|
|
||||
|
// Harware configuration |
||||
|
u8 leds{}; |
||||
|
ReportMode mode{}; |
||||
|
bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time |
||||
|
bool hidbus_enabled{}; // External device support |
||||
|
bool irs_enabled{}; // Infrared camera input |
||||
|
bool motion_enabled{}; // Enables motion input |
||||
|
bool nfc_enabled{}; // Enables Amiibo detection |
||||
|
bool vibration_enabled{}; // Allows vibrations |
||||
|
|
||||
|
// Calibration data |
||||
|
GyroSensitivity gyro_sensitivity{}; |
||||
|
GyroPerformance gyro_performance{}; |
||||
|
AccelerometerSensitivity accelerometer_sensitivity{}; |
||||
|
AccelerometerPerformance accelerometer_performance{}; |
||||
|
JoyStickCalibration left_stick_calibration{}; |
||||
|
JoyStickCalibration right_stick_calibration{}; |
||||
|
MotionCalibration motion_calibration{}; |
||||
|
RingCalibration ring_calibration{}; |
||||
|
|
||||
|
// Fixed joycon info |
||||
|
FirmwareVersion version{}; |
||||
|
Color color{}; |
||||
|
std::size_t port{}; |
||||
|
ControllerType device_type{}; // Device type reported by controller |
||||
|
ControllerType handle_device_type{}; // Device type reported by hidapi |
||||
|
SerialNumber serial_number{}; // Serial number reported by controller |
||||
|
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi |
||||
|
SupportedFeatures supported_features{}; |
||||
|
|
||||
|
// Thread related |
||||
|
mutable std::mutex mutex; |
||||
|
std::jthread input_thread; |
||||
|
bool input_thread_running{}; |
||||
|
bool disable_input_thread{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,184 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <cstring>
|
||||
|
|
||||
|
#include "input_common/helpers/joycon_protocol/calibration.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle) |
||||
|
: JoyconCommonProtocol(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> buffer; |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
calibration = {}; |
||||
|
|
||||
|
result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer); |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; |
||||
|
if (has_user_calibration) { |
||||
|
result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer); |
||||
|
} else { |
||||
|
result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]); |
||||
|
calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4)); |
||||
|
calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]); |
||||
|
calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4)); |
||||
|
calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]); |
||||
|
calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4)); |
||||
|
} |
||||
|
|
||||
|
// Nintendo fix for drifting stick
|
||||
|
// result = ReadSPI(0x60, 0x86 ,buffer, 16);
|
||||
|
// calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
|
||||
|
// Set a valid default calibration if data is missing
|
||||
|
ValidateCalibration(calibration); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> buffer; |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
calibration = {}; |
||||
|
|
||||
|
result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer); |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; |
||||
|
if (has_user_calibration) { |
||||
|
result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer); |
||||
|
} else { |
||||
|
result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]); |
||||
|
calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4)); |
||||
|
calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]); |
||||
|
calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4)); |
||||
|
calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]); |
||||
|
calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4)); |
||||
|
} |
||||
|
|
||||
|
// Nintendo fix for drifting stick
|
||||
|
// buffer = ReadSPI(0x60, 0x98 , 16);
|
||||
|
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
|
||||
|
// Set a valid default calibration if data is missing
|
||||
|
ValidateCalibration(calibration); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> buffer; |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
calibration = {}; |
||||
|
|
||||
|
result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer); |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; |
||||
|
if (has_user_calibration) { |
||||
|
result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer); |
||||
|
} else { |
||||
|
result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
IMUCalibration device_calibration{}; |
||||
|
memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration)); |
||||
|
calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0]; |
||||
|
calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1]; |
||||
|
calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2]; |
||||
|
|
||||
|
calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0]; |
||||
|
calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1]; |
||||
|
calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2]; |
||||
|
|
||||
|
calibration.gyro[0].offset = device_calibration.gyroscope_offset[0]; |
||||
|
calibration.gyro[1].offset = device_calibration.gyroscope_offset[1]; |
||||
|
calibration.gyro[2].offset = device_calibration.gyroscope_offset[2]; |
||||
|
|
||||
|
calibration.gyro[0].scale = device_calibration.gyroscope_scale[0]; |
||||
|
calibration.gyro[1].scale = device_calibration.gyroscope_scale[1]; |
||||
|
calibration.gyro[2].scale = device_calibration.gyroscope_scale[2]; |
||||
|
} |
||||
|
|
||||
|
ValidateCalibration(calibration); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
ring_data_max = std::max(ring_data_max, current_value); |
||||
|
ring_data_min = std::min(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}; |
||||
|
|
||||
|
if (calibration.x.center == 0xFFF || calibration.x.center == 0) { |
||||
|
calibration.x.center = DefaultStickCenter; |
||||
|
} |
||||
|
if (calibration.x.max == 0xFFF || calibration.x.max == 0) { |
||||
|
calibration.x.max = DefaultStickRange; |
||||
|
} |
||||
|
if (calibration.x.min == 0xFFF || calibration.x.min == 0) { |
||||
|
calibration.x.min = DefaultStickRange; |
||||
|
} |
||||
|
|
||||
|
if (calibration.y.center == 0xFFF || calibration.y.center == 0) { |
||||
|
calibration.y.center = DefaultStickCenter; |
||||
|
} |
||||
|
if (calibration.y.max == 0xFFF || calibration.y.max == 0) { |
||||
|
calibration.y.max = DefaultStickRange; |
||||
|
} |
||||
|
if (calibration.y.min == 0xFFF || calibration.y.min == 0) { |
||||
|
calibration.y.min = DefaultStickRange; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) { |
||||
|
for (auto& sensor : calibration.accelerometer) { |
||||
|
if (sensor.scale == 0) { |
||||
|
sensor.scale = 0x4000; |
||||
|
} |
||||
|
} |
||||
|
for (auto& sensor : calibration.gyro) { |
||||
|
if (sensor.scale == 0) { |
||||
|
sensor.scale = 0x3be7; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,64 @@ |
|||||
|
// 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" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
enum class DriverResult; |
||||
|
struct JoyStickCalibration; |
||||
|
struct IMUCalibration; |
||||
|
struct JoyconHandle; |
||||
|
} // namespace InputCommon::Joycon |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
/// Driver functions related to retrieving calibration data from the device |
||||
|
class CalibrationProtocol final : private JoyconCommonProtocol { |
||||
|
public: |
||||
|
explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the left stick calibration from memory |
||||
|
* @param is_factory_calibration if true factory values will be returned |
||||
|
* @returns JoyStickCalibration of the left joystick |
||||
|
*/ |
||||
|
DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the right stick calibration from memory |
||||
|
* @param is_factory_calibration if true factory values will be returned |
||||
|
* @returns JoyStickCalibration of the right joystick |
||||
|
*/ |
||||
|
DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the motion calibration from memory |
||||
|
* @returns ImuCalibration of the motion sensor |
||||
|
*/ |
||||
|
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 |
||||
@ -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/common_protocol.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_) |
||||
|
: hidapi_handle{std::move(hidapi_handle_)} {} |
||||
|
|
||||
|
u8 JoyconCommonProtocol::GetCounter() { |
||||
|
hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F; |
||||
|
return hidapi_handle->packet_counter; |
||||
|
} |
||||
|
|
||||
|
void JoyconCommonProtocol::SetBlocking() { |
||||
|
SDL_hid_set_nonblocking(hidapi_handle->handle, 0); |
||||
|
} |
||||
|
|
||||
|
void JoyconCommonProtocol::SetNonBlocking() { |
||||
|
SDL_hid_set_nonblocking(hidapi_handle->handle, 1); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) { |
||||
|
std::vector<u8> buffer; |
||||
|
const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer); |
||||
|
controller_type = ControllerType::None; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
controller_type = static_cast<ControllerType>(buffer[0]); |
||||
|
// Fallback to 3rd party pro controllers
|
||||
|
if (controller_type == ControllerType::None) { |
||||
|
controller_type = ControllerType::Pro; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) { |
||||
|
ControllerType controller_type{ControllerType::None}; |
||||
|
const auto result = GetDeviceType(controller_type); |
||||
|
if (result != DriverResult::Success || controller_type == ControllerType::None) { |
||||
|
return DriverResult::UnsupportedControllerType; |
||||
|
} |
||||
|
|
||||
|
hidapi_handle->handle = |
||||
|
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); |
||||
|
|
||||
|
if (!hidapi_handle->handle) { |
||||
|
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", |
||||
|
device_info->vendor_id, device_info->product_id); |
||||
|
return DriverResult::HandleInUse; |
||||
|
} |
||||
|
|
||||
|
SetNonBlocking(); |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) { |
||||
|
const std::array<u8, 1> buffer{static_cast<u8>(report_mode)}; |
||||
|
return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) { |
||||
|
const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size()); |
||||
|
|
||||
|
if (result == -1) { |
||||
|
return DriverResult::ErrorWritingData; |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) { |
||||
|
constexpr int timeout_mili = 66; |
||||
|
constexpr int MaxTries = 15; |
||||
|
int tries = 0; |
||||
|
output.resize(MaxSubCommandResponseSize); |
||||
|
|
||||
|
do { |
||||
|
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), |
||||
|
MaxSubCommandResponseSize, timeout_mili); |
||||
|
|
||||
|
if (result < 1) { |
||||
|
LOG_ERROR(Input, "No response from joycon"); |
||||
|
} |
||||
|
if (tries++ > MaxTries) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} while (output[0] != 0x21 && output[14] != static_cast<u8>(sc)); |
||||
|
|
||||
|
if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer, |
||||
|
std::vector<u8>& output) { |
||||
|
std::vector<u8> local_buffer(MaxResponseSize); |
||||
|
|
||||
|
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD); |
||||
|
local_buffer[1] = GetCounter(); |
||||
|
local_buffer[10] = static_cast<u8>(sc); |
||||
|
for (std::size_t i = 0; i < buffer.size(); ++i) { |
||||
|
local_buffer[11 + i] = buffer[i]; |
||||
|
} |
||||
|
|
||||
|
auto result = SendData(local_buffer); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
result = GetSubCommandResponse(sc, output); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) { |
||||
|
std::vector<u8> output; |
||||
|
return SendSubCommand(sc, buffer, output); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) { |
||||
|
std::vector<u8> local_buffer(MaxResponseSize); |
||||
|
|
||||
|
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA); |
||||
|
local_buffer[1] = GetCounter(); |
||||
|
local_buffer[10] = static_cast<u8>(sc); |
||||
|
for (std::size_t i = 0; i < buffer.size(); ++i) { |
||||
|
local_buffer[11 + i] = buffer[i]; |
||||
|
} |
||||
|
|
||||
|
return SendData(local_buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) { |
||||
|
std::vector<u8> local_buffer(MaxResponseSize); |
||||
|
|
||||
|
local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY); |
||||
|
local_buffer[1] = GetCounter(); |
||||
|
|
||||
|
memcpy(local_buffer.data() + 2, buffer.data(), buffer.size()); |
||||
|
|
||||
|
return SendData(local_buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) { |
||||
|
constexpr std::size_t MaxTries = 10; |
||||
|
std::size_t tries = 0; |
||||
|
std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size}; |
||||
|
std::vector<u8> local_buffer(size + 20); |
||||
|
|
||||
|
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF); |
||||
|
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8); |
||||
|
do { |
||||
|
const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer); |
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
if (tries++ > MaxTries) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]); |
||||
|
|
||||
|
// Remove header from output
|
||||
|
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size); |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::EnableMCU(bool enable) { |
||||
|
const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)}; |
||||
|
const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
LOG_ERROR(Input, "SendMCUData failed with error {}", result); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) { |
||||
|
LOG_DEBUG(Input, "ConfigureMCU"); |
||||
|
std::array<u8, sizeof(MCUConfig)> config_buffer; |
||||
|
memcpy(config_buffer.data(), &config, sizeof(MCUConfig)); |
||||
|
config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36); |
||||
|
|
||||
|
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
LOG_ERROR(Input, "Set MCU config failed with error {}", result); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_, |
||||
|
std::vector<u8>& output) { |
||||
|
const int report_mode = static_cast<u8>(report_mode_); |
||||
|
constexpr int TimeoutMili = 200; |
||||
|
constexpr int MaxTries = 9; |
||||
|
int tries = 0; |
||||
|
output.resize(0x170); |
||||
|
|
||||
|
do { |
||||
|
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili); |
||||
|
|
||||
|
if (result < 1) { |
||||
|
LOG_ERROR(Input, "No response from joycon attempt {}", tries); |
||||
|
} |
||||
|
if (tries++ > MaxTries) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} while (output[0] != report_mode || output[49] == 0xFF); |
||||
|
|
||||
|
if (output[0] != report_mode || output[49] == 0xFF) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc, |
||||
|
std::span<const u8> buffer, |
||||
|
std::vector<u8>& output) { |
||||
|
std::vector<u8> local_buffer(MaxResponseSize); |
||||
|
|
||||
|
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA); |
||||
|
local_buffer[1] = GetCounter(); |
||||
|
local_buffer[9] = static_cast<u8>(sc); |
||||
|
for (std::size_t i = 0; i < buffer.size(); ++i) { |
||||
|
local_buffer[10 + i] = buffer[i]; |
||||
|
} |
||||
|
|
||||
|
auto result = SendData(local_buffer); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
result = GetMCUDataResponse(report_mode, output); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { |
||||
|
std::vector<u8> output; |
||||
|
constexpr std::size_t MaxTries{8}; |
||||
|
std::size_t tries{}; |
||||
|
|
||||
|
do { |
||||
|
const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)}; |
||||
|
const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
if (tries++ > MaxTries) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
} while (output[49] != 1 || output[56] != static_cast<u8>(mode)); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
// crc-8-ccitt / polynomial 0x07 look up table
|
||||
|
constexpr std::array<u8, 256> mcu_crc8_table = { |
||||
|
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, |
||||
|
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, |
||||
|
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, |
||||
|
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, |
||||
|
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, |
||||
|
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, |
||||
|
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, |
||||
|
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, |
||||
|
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, |
||||
|
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, |
||||
|
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, |
||||
|
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, |
||||
|
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, |
||||
|
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, |
||||
|
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, |
||||
|
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3}; |
||||
|
|
||||
|
u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const { |
||||
|
u8 crc8 = 0x0; |
||||
|
|
||||
|
for (int i = 0; i < size; ++i) { |
||||
|
crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])]; |
||||
|
} |
||||
|
return crc8; |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,173 @@ |
|||||
|
// 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 <memory> |
||||
|
#include <span> |
||||
|
#include <vector> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
/// Joycon driver functions that handle low level communication |
||||
|
class JoyconCommonProtocol { |
||||
|
public: |
||||
|
explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_); |
||||
|
|
||||
|
/** |
||||
|
* Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is |
||||
|
* data to read before returning. |
||||
|
*/ |
||||
|
void SetBlocking(); |
||||
|
|
||||
|
/** |
||||
|
* Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return |
||||
|
* immediately with a value of 0 if there is no data to be read |
||||
|
*/ |
||||
|
void SetNonBlocking(); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the joycon type from device |
||||
|
* @returns controller type of the joycon |
||||
|
*/ |
||||
|
DriverResult GetDeviceType(ControllerType& controller_type); |
||||
|
|
||||
|
/** |
||||
|
* Verifies and sets the joycon_handle if device is valid |
||||
|
* @param device info from the driver |
||||
|
* @returns success if the device is valid |
||||
|
*/ |
||||
|
DriverResult CheckDeviceAccess(SDL_hid_device_info* device); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to set the polling mode of the joycon |
||||
|
* @param report_mode polling mode to be set |
||||
|
*/ |
||||
|
DriverResult SetReportMode(Joycon::ReportMode report_mode); |
||||
|
|
||||
|
/** |
||||
|
* Sends data to the joycon device |
||||
|
* @param buffer data to be send |
||||
|
*/ |
||||
|
DriverResult SendData(std::span<const u8> buffer); |
||||
|
|
||||
|
/** |
||||
|
* Waits for incoming data of the joycon device that matchs the subcommand |
||||
|
* @param sub_command type of data to be returned |
||||
|
* @returns a buffer containing the responce |
||||
|
*/ |
||||
|
DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output); |
||||
|
|
||||
|
/** |
||||
|
* Sends a sub command to the device and waits for it's reply |
||||
|
* @param sc sub command to be send |
||||
|
* @param buffer data to be send |
||||
|
* @returns output buffer containing the responce |
||||
|
*/ |
||||
|
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output); |
||||
|
|
||||
|
/** |
||||
|
* Sends a sub command to the device and waits for it's reply and ignores the output |
||||
|
* @param sc sub command to be send |
||||
|
* @param buffer data to be send |
||||
|
*/ |
||||
|
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer); |
||||
|
|
||||
|
/** |
||||
|
* Sends a mcu command to the device |
||||
|
* @param sc sub command to be send |
||||
|
* @param buffer data to be send |
||||
|
*/ |
||||
|
DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer); |
||||
|
|
||||
|
/** |
||||
|
* Sends vibration data to the joycon |
||||
|
* @param buffer data to be send |
||||
|
*/ |
||||
|
DriverResult SendVibrationReport(std::span<const u8> buffer); |
||||
|
|
||||
|
/** |
||||
|
* Reads the SPI memory stored on the joycon |
||||
|
* @param Initial address location |
||||
|
* @param size in bytes to be read |
||||
|
* @returns output buffer containing the responce |
||||
|
*/ |
||||
|
DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output); |
||||
|
|
||||
|
/** |
||||
|
* Enables MCU chip on the joycon |
||||
|
* @param enable if true the chip will be enabled |
||||
|
*/ |
||||
|
DriverResult EnableMCU(bool enable); |
||||
|
|
||||
|
/** |
||||
|
* Configures the MCU to the correspoinding mode |
||||
|
* @param MCUConfig configuration |
||||
|
*/ |
||||
|
DriverResult ConfigureMCU(const MCUConfig& config); |
||||
|
|
||||
|
/** |
||||
|
* Waits until there's MCU data available. On timeout returns error |
||||
|
* @param report mode of the expected reply |
||||
|
* @returns a buffer containing the responce |
||||
|
*/ |
||||
|
DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output); |
||||
|
|
||||
|
/** |
||||
|
* Sends data to the MCU chip and waits for it's reply |
||||
|
* @param report mode of the expected reply |
||||
|
* @param sub command to be send |
||||
|
* @param buffer data to be send |
||||
|
* @returns output buffer containing the responce |
||||
|
*/ |
||||
|
DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer, |
||||
|
std::vector<u8>& output); |
||||
|
|
||||
|
/** |
||||
|
* Wait's until the MCU chip is on the specified mode |
||||
|
* @param report mode of the expected reply |
||||
|
* @param MCUMode configuration |
||||
|
*/ |
||||
|
DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode); |
||||
|
|
||||
|
/** |
||||
|
* Calculates the checksum from the MCU data |
||||
|
* @param buffer containing the data to be send |
||||
|
* @param size of the buffer in bytes |
||||
|
* @returns byte with the correct checksum |
||||
|
*/ |
||||
|
u8 CalculateMCU_CRC8(u8* buffer, u8 size) const; |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Increments and returns the packet counter of the handle |
||||
|
* @param joycon_handle device to send the data |
||||
|
* @returns packet counter value |
||||
|
*/ |
||||
|
u8 GetCounter(); |
||||
|
|
||||
|
std::shared_ptr<JoyconHandle> hidapi_handle; |
||||
|
}; |
||||
|
|
||||
|
class ScopedSetBlocking { |
||||
|
public: |
||||
|
explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} { |
||||
|
m_self->SetBlocking(); |
||||
|
} |
||||
|
|
||||
|
~ScopedSetBlocking() { |
||||
|
m_self->SetNonBlocking(); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
JoyconCommonProtocol* m_self{}; |
||||
|
}; |
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,125 @@ |
|||||
|
// 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/generic_functions.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle) |
||||
|
: JoyconCommonProtocol(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult GenericProtocol::EnablePassiveMode() { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
return SetReportMode(ReportMode::SIMPLE_HID_MODE); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::EnableActiveMode() { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
return SetReportMode(ReportMode::STANDARD_FULL_60HZ); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> output; |
||||
|
|
||||
|
const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output); |
||||
|
|
||||
|
device_info = {}; |
||||
|
if (result == DriverResult::Success) { |
||||
|
memcpy(&device_info, output.data(), sizeof(DeviceInfo)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) { |
||||
|
return GetDeviceType(controller_type); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::EnableImu(bool enable) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; |
||||
|
return SendSubCommand(SubCommand::ENABLE_IMU, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, |
||||
|
AccelerometerSensitivity asen, |
||||
|
AccelerometerPerformance afrec) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen), |
||||
|
static_cast<u8>(gfrec), static_cast<u8>(afrec)}; |
||||
|
return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetBattery(u32& battery_level) { |
||||
|
// This function is meant to request the high resolution battery status
|
||||
|
battery_level = 0; |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetColor(Color& color) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> buffer; |
||||
|
const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer); |
||||
|
|
||||
|
color = {}; |
||||
|
if (result == DriverResult::Success) { |
||||
|
color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]); |
||||
|
color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]); |
||||
|
color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]); |
||||
|
color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
std::vector<u8> buffer; |
||||
|
const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer); |
||||
|
|
||||
|
serial_number = {}; |
||||
|
if (result == DriverResult::Success) { |
||||
|
memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetTemperature(u32& temperature) { |
||||
|
// Not all devices have temperature sensor
|
||||
|
temperature = 25; |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) { |
||||
|
DeviceInfo device_info{}; |
||||
|
|
||||
|
const auto result = GetDeviceInfo(device_info); |
||||
|
version = device_info.firmware; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::SetHomeLight() { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00}; |
||||
|
return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::SetLedBusy() { |
||||
|
return DriverResult::NotSupported; |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::SetLedPattern(u8 leds) { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
const std::array<u8, 1> buffer{leds}; |
||||
|
return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) { |
||||
|
return SetLedPattern(static_cast<u8>(leds << 4)); |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,108 @@ |
|||||
|
// 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 "input_common/helpers/joycon_protocol/common_protocol.h" |
||||
|
#include "input_common/helpers/joycon_protocol/joycon_types.h" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
/// Joycon driver functions that easily implemented |
||||
|
class GenericProtocol final : private JoyconCommonProtocol { |
||||
|
public: |
||||
|
explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
/// Enables passive mode. This mode only sends button data on change. Sticks will return digital |
||||
|
/// data instead of analog. Motion will be disabled |
||||
|
DriverResult EnablePassiveMode(); |
||||
|
|
||||
|
/// Enables active mode. This mode will return the current status every 5-15ms |
||||
|
DriverResult EnableActiveMode(); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the joycon firmware and mac from handle |
||||
|
* @returns controller device info |
||||
|
*/ |
||||
|
DriverResult GetDeviceInfo(DeviceInfo& controller_type); |
||||
|
|
||||
|
/** |
||||
|
* Sends a request to obtain the joycon type from handle |
||||
|
* @returns controller type of the joycon |
||||
|
*/ |
||||
|
DriverResult GetControllerType(ControllerType& controller_type); |
||||
|
|
||||
|
/** |
||||
|
* Enables motion input |
||||
|
* @param enable if true motion data will be enabled |
||||
|
*/ |
||||
|
DriverResult EnableImu(bool enable); |
||||
|
|
||||
|
/** |
||||
|
* Configures the motion sensor with the specified parameters |
||||
|
* @param gsen gyroscope sensor sensitvity in degrees per second |
||||
|
* @param gfrec gyroscope sensor frequency in hertz |
||||
|
* @param asen accelerometer sensitivity in G force |
||||
|
* @param afrec accelerometer frequency in hertz |
||||
|
*/ |
||||
|
DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, |
||||
|
AccelerometerSensitivity asen, AccelerometerPerformance afrec); |
||||
|
|
||||
|
/** |
||||
|
* Request battery level from the device |
||||
|
* @returns battery level |
||||
|
*/ |
||||
|
DriverResult GetBattery(u32& battery_level); |
||||
|
|
||||
|
/** |
||||
|
* Request joycon colors from the device |
||||
|
* @returns colors of the body and buttons |
||||
|
*/ |
||||
|
DriverResult GetColor(Color& color); |
||||
|
|
||||
|
/** |
||||
|
* Request joycon serial number from the device |
||||
|
* @returns 16 byte serial number |
||||
|
*/ |
||||
|
DriverResult GetSerialNumber(SerialNumber& serial_number); |
||||
|
|
||||
|
/** |
||||
|
* Request joycon serial number from the device |
||||
|
* @returns 16 byte serial number |
||||
|
*/ |
||||
|
DriverResult GetTemperature(u32& temperature); |
||||
|
|
||||
|
/** |
||||
|
* Request joycon serial number from the device |
||||
|
* @returns 16 byte serial number |
||||
|
*/ |
||||
|
DriverResult GetVersionNumber(FirmwareVersion& version); |
||||
|
|
||||
|
/** |
||||
|
* Sets home led behaviour |
||||
|
*/ |
||||
|
DriverResult SetHomeLight(); |
||||
|
|
||||
|
/** |
||||
|
* Sets home led into a slow breathing state |
||||
|
*/ |
||||
|
DriverResult SetLedBusy(); |
||||
|
|
||||
|
/** |
||||
|
* Sets the 4 player leds on the joycon on a solid state |
||||
|
* @params bit flag containing the led state |
||||
|
*/ |
||||
|
DriverResult SetLedPattern(u8 leds); |
||||
|
|
||||
|
/** |
||||
|
* Sets the 4 player leds on the joycon on a blinking state |
||||
|
* @returns bit flag containing the led state |
||||
|
*/ |
||||
|
DriverResult SetLedBlinkPattern(u8 leds); |
||||
|
}; |
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,298 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <thread>
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/irs.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle) |
||||
|
: JoyconCommonProtocol(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult IrsProtocol::EnableIrs() { |
||||
|
LOG_INFO(Input, "Enable IRS"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = EnableMCU(true); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
const MCUConfig config{ |
||||
|
.command = MCUCommand::ConfigureMCU, |
||||
|
.sub_command = MCUSubCommand::SetMCUMode, |
||||
|
.mode = MCUMode::IR, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
result = ConfigureMCU(config); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = ConfigureIrs(); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WriteRegistersStep1(); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WriteRegistersStep2(); |
||||
|
} |
||||
|
|
||||
|
is_enabled = true; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::DisableIrs() { |
||||
|
LOG_DEBUG(Input, "Disable IRS"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = EnableMCU(false); |
||||
|
} |
||||
|
|
||||
|
is_enabled = false; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) { |
||||
|
irs_mode = mode; |
||||
|
switch (format) { |
||||
|
case IrsResolution::Size320x240: |
||||
|
resolution_code = IrsResolutionCode::Size320x240; |
||||
|
fragments = IrsFragments::Size320x240; |
||||
|
resolution = IrsResolution::Size320x240; |
||||
|
break; |
||||
|
case IrsResolution::Size160x120: |
||||
|
resolution_code = IrsResolutionCode::Size160x120; |
||||
|
fragments = IrsFragments::Size160x120; |
||||
|
resolution = IrsResolution::Size160x120; |
||||
|
break; |
||||
|
case IrsResolution::Size80x60: |
||||
|
resolution_code = IrsResolutionCode::Size80x60; |
||||
|
fragments = IrsFragments::Size80x60; |
||||
|
resolution = IrsResolution::Size80x60; |
||||
|
break; |
||||
|
case IrsResolution::Size20x15: |
||||
|
resolution_code = IrsResolutionCode::Size20x15; |
||||
|
fragments = IrsFragments::Size20x15; |
||||
|
resolution = IrsResolution::Size20x15; |
||||
|
break; |
||||
|
case IrsResolution::Size40x30: |
||||
|
default: |
||||
|
resolution_code = IrsResolutionCode::Size40x30; |
||||
|
fragments = IrsFragments::Size40x30; |
||||
|
resolution = IrsResolution::Size40x30; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Restart feature
|
||||
|
if (is_enabled) { |
||||
|
DisableIrs(); |
||||
|
return EnableIrs(); |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) { |
||||
|
const u8 next_packet_fragment = |
||||
|
static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1)); |
||||
|
|
||||
|
if (buffer[0] == 0x31 && buffer[49] == 0x03) { |
||||
|
u8 new_packet_fragment = buffer[52]; |
||||
|
if (new_packet_fragment == next_packet_fragment) { |
||||
|
packet_fragment = next_packet_fragment; |
||||
|
memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300); |
||||
|
|
||||
|
return RequestFrame(packet_fragment); |
||||
|
} |
||||
|
|
||||
|
if (new_packet_fragment == packet_fragment) { |
||||
|
return RequestFrame(packet_fragment); |
||||
|
} |
||||
|
|
||||
|
return ResendFrame(next_packet_fragment); |
||||
|
} |
||||
|
|
||||
|
return RequestFrame(packet_fragment); |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::ConfigureIrs() { |
||||
|
LOG_DEBUG(Input, "Configure IRS"); |
||||
|
constexpr std::size_t max_tries = 28; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
const IrsConfigure irs_configuration{ |
||||
|
.command = MCUCommand::ConfigureIR, |
||||
|
.sub_command = MCUSubCommand::SetDeviceMode, |
||||
|
.irs_mode = IrsMode::ImageTransfer, |
||||
|
.number_of_fragments = fragments, |
||||
|
.mcu_major_version = 0x0500, |
||||
|
.mcu_minor_version = 0x1800, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
buf_image.resize((static_cast<u8>(fragments) + 1) * 300); |
||||
|
|
||||
|
std::array<u8, sizeof(IrsConfigure)> request_data{}; |
||||
|
memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
do { |
||||
|
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
if (tries++ >= max_tries) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
} while (output[15] != 0x0b); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::WriteRegistersStep1() { |
||||
|
LOG_DEBUG(Input, "WriteRegistersStep1"); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
constexpr std::size_t max_tries = 28; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
const IrsWriteRegisters irs_registers{ |
||||
|
.command = MCUCommand::ConfigureIR, |
||||
|
.sub_command = MCUSubCommand::WriteDeviceRegisters, |
||||
|
.number_of_registers = 0x9, |
||||
|
.registers = |
||||
|
{ |
||||
|
IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)}, |
||||
|
{IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)}, |
||||
|
{IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)}, |
||||
|
{IrRegistersAddress::ExposureTime, 0x00}, |
||||
|
{IrRegistersAddress::Leds, static_cast<u8>(leds)}, |
||||
|
{IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)}, |
||||
|
{IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)}, |
||||
|
{IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)}, |
||||
|
{IrRegistersAddress::WhitePixelThreshold, 0xc8}, |
||||
|
}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; |
||||
|
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
|
||||
|
std::array<u8, 38> mcu_request{0x02}; |
||||
|
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); |
||||
|
mcu_request[37] = 0xFF; |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
do { |
||||
|
result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); |
||||
|
|
||||
|
// First time we need to set the report mode
|
||||
|
if (result == DriverResult::Success && tries == 0) { |
||||
|
result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); |
||||
|
} |
||||
|
if (result == DriverResult::Success && tries == 0) { |
||||
|
GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output); |
||||
|
} |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
if (tries++ >= max_tries) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
} while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::WriteRegistersStep2() { |
||||
|
LOG_DEBUG(Input, "WriteRegistersStep2"); |
||||
|
constexpr std::size_t max_tries = 28; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
const IrsWriteRegisters irs_registers{ |
||||
|
.command = MCUCommand::ConfigureIR, |
||||
|
.sub_command = MCUSubCommand::WriteDeviceRegisters, |
||||
|
.number_of_registers = 0x8, |
||||
|
.registers = |
||||
|
{ |
||||
|
IrsRegister{IrRegistersAddress::LedIntensitiyMSB, |
||||
|
static_cast<u8>(led_intensity >> 8)}, |
||||
|
{IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)}, |
||||
|
{IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)}, |
||||
|
{IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)}, |
||||
|
{IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)}, |
||||
|
{IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)}, |
||||
|
{IrRegistersAddress::UpdateTime, 0x2d}, |
||||
|
{IrRegistersAddress::FinalizeConfig, 0x01}, |
||||
|
}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; |
||||
|
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
do { |
||||
|
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
if (tries++ >= max_tries) { |
||||
|
return DriverResult::WrongReply; |
||||
|
} |
||||
|
} while (output[15] != 0x13 && output[15] != 0x23); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::RequestFrame(u8 frame) { |
||||
|
std::array<u8, 38> mcu_request{}; |
||||
|
mcu_request[3] = frame; |
||||
|
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); |
||||
|
mcu_request[37] = 0xFF; |
||||
|
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); |
||||
|
} |
||||
|
|
||||
|
DriverResult IrsProtocol::ResendFrame(u8 frame) { |
||||
|
std::array<u8, 38> mcu_request{}; |
||||
|
mcu_request[1] = 0x1; |
||||
|
mcu_request[2] = frame; |
||||
|
mcu_request[3] = 0x0; |
||||
|
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); |
||||
|
mcu_request[37] = 0xFF; |
||||
|
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); |
||||
|
} |
||||
|
|
||||
|
std::vector<u8> IrsProtocol::GetImage() const { |
||||
|
return buf_image; |
||||
|
} |
||||
|
|
||||
|
IrsResolution IrsProtocol::GetIrsFormat() const { |
||||
|
return resolution; |
||||
|
} |
||||
|
|
||||
|
bool IrsProtocol::IsEnabled() const { |
||||
|
return is_enabled; |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,63 @@ |
|||||
|
// 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 IrsProtocol final : private JoyconCommonProtocol { |
||||
|
public: |
||||
|
explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
DriverResult EnableIrs(); |
||||
|
|
||||
|
DriverResult DisableIrs(); |
||||
|
|
||||
|
DriverResult SetIrsConfig(IrsMode mode, IrsResolution format); |
||||
|
|
||||
|
DriverResult RequestImage(std::span<u8> buffer); |
||||
|
|
||||
|
std::vector<u8> GetImage() const; |
||||
|
|
||||
|
IrsResolution GetIrsFormat() const; |
||||
|
|
||||
|
bool IsEnabled() const; |
||||
|
|
||||
|
private: |
||||
|
DriverResult ConfigureIrs(); |
||||
|
|
||||
|
DriverResult WriteRegistersStep1(); |
||||
|
DriverResult WriteRegistersStep2(); |
||||
|
|
||||
|
DriverResult RequestFrame(u8 frame); |
||||
|
DriverResult ResendFrame(u8 frame); |
||||
|
|
||||
|
IrsMode irs_mode{IrsMode::ImageTransfer}; |
||||
|
IrsResolution resolution{IrsResolution::Size40x30}; |
||||
|
IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30}; |
||||
|
IrsFragments fragments{IrsFragments::Size40x30}; |
||||
|
IrLeds leds{IrLeds::BrightAndDim}; |
||||
|
IrExLedFilter led_filter{IrExLedFilter::Enabled}; |
||||
|
IrImageFlip image_flip{IrImageFlip::Normal}; |
||||
|
u8 digital_gain{0x01}; |
||||
|
u16 exposure{0x2490}; |
||||
|
u16 led_intensity{0x0f10}; |
||||
|
u32 denoise{0x012344}; |
||||
|
|
||||
|
u8 packet_fragment{}; |
||||
|
std::vector<u8> buf_image; // 8bpp greyscale image. |
||||
|
|
||||
|
bool is_enabled{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,612 @@ |
|||||
|
// 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 <array> |
||||
|
#include <functional> |
||||
|
#include <SDL_hidapi.h> |
||||
|
|
||||
|
#include "common/bit_field.h" |
||||
|
#include "common/common_funcs.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
constexpr u32 MaxErrorCount = 50; |
||||
|
constexpr u32 MaxBufferSize = 368; |
||||
|
constexpr u32 MaxResponseSize = 49; |
||||
|
constexpr u32 MaxSubCommandResponseSize = 64; |
||||
|
constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; |
||||
|
|
||||
|
using MacAddress = std::array<u8, 6>; |
||||
|
using SerialNumber = std::array<u8, 15>; |
||||
|
|
||||
|
enum class ControllerType { |
||||
|
None, |
||||
|
Left, |
||||
|
Right, |
||||
|
Pro, |
||||
|
Grip, |
||||
|
Dual, |
||||
|
}; |
||||
|
|
||||
|
enum class PadAxes { |
||||
|
LeftStickX, |
||||
|
LeftStickY, |
||||
|
RightStickX, |
||||
|
RightStickY, |
||||
|
Undefined, |
||||
|
}; |
||||
|
|
||||
|
enum class PadMotion { |
||||
|
LeftMotion, |
||||
|
RightMotion, |
||||
|
Undefined, |
||||
|
}; |
||||
|
|
||||
|
enum class PadButton : u32 { |
||||
|
Down = 0x000001, |
||||
|
Up = 0x000002, |
||||
|
Right = 0x000004, |
||||
|
Left = 0x000008, |
||||
|
LeftSR = 0x000010, |
||||
|
LeftSL = 0x000020, |
||||
|
L = 0x000040, |
||||
|
ZL = 0x000080, |
||||
|
Y = 0x000100, |
||||
|
X = 0x000200, |
||||
|
B = 0x000400, |
||||
|
A = 0x000800, |
||||
|
RightSR = 0x001000, |
||||
|
RightSL = 0x002000, |
||||
|
R = 0x004000, |
||||
|
ZR = 0x008000, |
||||
|
Minus = 0x010000, |
||||
|
Plus = 0x020000, |
||||
|
StickR = 0x040000, |
||||
|
StickL = 0x080000, |
||||
|
Home = 0x100000, |
||||
|
Capture = 0x200000, |
||||
|
}; |
||||
|
|
||||
|
enum class PasivePadButton : u32 { |
||||
|
Down_A = 0x0001, |
||||
|
Right_X = 0x0002, |
||||
|
Left_B = 0x0004, |
||||
|
Up_Y = 0x0008, |
||||
|
SL = 0x0010, |
||||
|
SR = 0x0020, |
||||
|
Minus = 0x0100, |
||||
|
Plus = 0x0200, |
||||
|
StickL = 0x0400, |
||||
|
StickR = 0x0800, |
||||
|
Home = 0x1000, |
||||
|
Capture = 0x2000, |
||||
|
L_R = 0x4000, |
||||
|
ZL_ZR = 0x8000, |
||||
|
}; |
||||
|
|
||||
|
enum class OutputReport : u8 { |
||||
|
RUMBLE_AND_SUBCMD = 0x01, |
||||
|
FW_UPDATE_PKT = 0x03, |
||||
|
RUMBLE_ONLY = 0x10, |
||||
|
MCU_DATA = 0x11, |
||||
|
USB_CMD = 0x80, |
||||
|
}; |
||||
|
|
||||
|
enum class InputReport : u8 { |
||||
|
SUBCMD_REPLY = 0x21, |
||||
|
STANDARD_FULL_60HZ = 0x30, |
||||
|
NFC_IR_MODE_60HZ = 0x31, |
||||
|
SIMPLE_HID_MODE = 0x3F, |
||||
|
INPUT_USB_RESPONSE = 0x81, |
||||
|
}; |
||||
|
|
||||
|
enum class FeatureReport : u8 { |
||||
|
Last_SUBCMD = 0x02, |
||||
|
OTA_GW_UPGRADE = 0x70, |
||||
|
SETUP_MEM_READ = 0x71, |
||||
|
MEM_READ = 0x72, |
||||
|
ERASE_MEM_SECTOR = 0x73, |
||||
|
MEM_WRITE = 0x74, |
||||
|
LAUNCH = 0x75, |
||||
|
}; |
||||
|
|
||||
|
enum class SubCommand : u8 { |
||||
|
STATE = 0x00, |
||||
|
MANUAL_BT_PAIRING = 0x01, |
||||
|
REQ_DEV_INFO = 0x02, |
||||
|
SET_REPORT_MODE = 0x03, |
||||
|
TRIGGERS_ELAPSED = 0x04, |
||||
|
GET_PAGE_LIST_STATE = 0x05, |
||||
|
SET_HCI_STATE = 0x06, |
||||
|
RESET_PAIRING_INFO = 0x07, |
||||
|
LOW_POWER_MODE = 0x08, |
||||
|
SPI_FLASH_READ = 0x10, |
||||
|
SPI_FLASH_WRITE = 0x11, |
||||
|
RESET_MCU = 0x20, |
||||
|
SET_MCU_CONFIG = 0x21, |
||||
|
SET_MCU_STATE = 0x22, |
||||
|
SET_PLAYER_LIGHTS = 0x30, |
||||
|
GET_PLAYER_LIGHTS = 0x31, |
||||
|
SET_HOME_LIGHT = 0x38, |
||||
|
ENABLE_IMU = 0x40, |
||||
|
SET_IMU_SENSITIVITY = 0x41, |
||||
|
WRITE_IMU_REG = 0x42, |
||||
|
READ_IMU_REG = 0x43, |
||||
|
ENABLE_VIBRATION = 0x48, |
||||
|
GET_REGULATED_VOLTAGE = 0x50, |
||||
|
SET_EXTERNAL_CONFIG = 0x58, |
||||
|
UNKNOWN_RINGCON = 0x59, |
||||
|
UNKNOWN_RINGCON2 = 0x5A, |
||||
|
UNKNOWN_RINGCON3 = 0x5C, |
||||
|
}; |
||||
|
|
||||
|
enum class UsbSubCommand : u8 { |
||||
|
CONN_STATUS = 0x01, |
||||
|
HADSHAKE = 0x02, |
||||
|
BAUDRATE_3M = 0x03, |
||||
|
NO_TIMEOUT = 0x04, |
||||
|
EN_TIMEOUT = 0x05, |
||||
|
RESET = 0x06, |
||||
|
PRE_HANDSHAKE = 0x91, |
||||
|
SEND_UART = 0x92, |
||||
|
}; |
||||
|
|
||||
|
enum class CalMagic : u8 { |
||||
|
USR_MAGIC_0 = 0xB2, |
||||
|
USR_MAGIC_1 = 0xA1, |
||||
|
USRR_MAGI_SIZE = 2, |
||||
|
}; |
||||
|
|
||||
|
enum class CalAddr { |
||||
|
SERIAL_NUMBER = 0X6000, |
||||
|
DEVICE_TYPE = 0X6012, |
||||
|
COLOR_EXIST = 0X601B, |
||||
|
FACT_LEFT_DATA = 0X603d, |
||||
|
FACT_RIGHT_DATA = 0X6046, |
||||
|
COLOR_DATA = 0X6050, |
||||
|
FACT_IMU_DATA = 0X6020, |
||||
|
USER_LEFT_MAGIC = 0X8010, |
||||
|
USER_LEFT_DATA = 0X8012, |
||||
|
USER_RIGHT_MAGIC = 0X801B, |
||||
|
USER_RIGHT_DATA = 0X801D, |
||||
|
USER_IMU_MAGIC = 0X8026, |
||||
|
USER_IMU_DATA = 0X8028, |
||||
|
}; |
||||
|
|
||||
|
enum class ReportMode : u8 { |
||||
|
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00, |
||||
|
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01, |
||||
|
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02, |
||||
|
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03, |
||||
|
MCU_UPDATE_STATE = 0x23, |
||||
|
STANDARD_FULL_60HZ = 0x30, |
||||
|
NFC_IR_MODE_60HZ = 0x31, |
||||
|
SIMPLE_HID_MODE = 0x3F, |
||||
|
}; |
||||
|
|
||||
|
enum class GyroSensitivity : u8 { |
||||
|
DPS250, |
||||
|
DPS500, |
||||
|
DPS1000, |
||||
|
DPS2000, // Default |
||||
|
}; |
||||
|
|
||||
|
enum class AccelerometerSensitivity : u8 { |
||||
|
G8, // Default |
||||
|
G4, |
||||
|
G2, |
||||
|
G16, |
||||
|
}; |
||||
|
|
||||
|
enum class GyroPerformance : u8 { |
||||
|
HZ833, |
||||
|
HZ208, // Default |
||||
|
}; |
||||
|
|
||||
|
enum class AccelerometerPerformance : u8 { |
||||
|
HZ200, |
||||
|
HZ100, // Default |
||||
|
}; |
||||
|
|
||||
|
enum class MCUCommand : u8 { |
||||
|
ConfigureMCU = 0x21, |
||||
|
ConfigureIR = 0x23, |
||||
|
}; |
||||
|
|
||||
|
enum class MCUSubCommand : u8 { |
||||
|
SetMCUMode = 0x0, |
||||
|
SetDeviceMode = 0x1, |
||||
|
ReadDeviceMode = 0x02, |
||||
|
WriteDeviceRegisters = 0x4, |
||||
|
}; |
||||
|
|
||||
|
enum class MCUMode : u8 { |
||||
|
Suspend = 0, |
||||
|
Standby = 1, |
||||
|
Ringcon = 3, |
||||
|
NFC = 4, |
||||
|
IR = 5, |
||||
|
MaybeFWUpdate = 6, |
||||
|
}; |
||||
|
|
||||
|
enum class MCURequest : u8 { |
||||
|
GetMCUStatus = 1, |
||||
|
GetNFCData = 2, |
||||
|
GetIRData = 3, |
||||
|
}; |
||||
|
|
||||
|
enum class MCUReport : u8 { |
||||
|
Empty = 0x00, |
||||
|
StateReport = 0x01, |
||||
|
IRData = 0x03, |
||||
|
BusyInitializing = 0x0b, |
||||
|
IRStatus = 0x13, |
||||
|
IRRegisters = 0x1b, |
||||
|
NFCState = 0x2a, |
||||
|
NFCReadData = 0x3a, |
||||
|
EmptyAwaitingCmd = 0xff, |
||||
|
}; |
||||
|
|
||||
|
enum class MCUPacketFlag : u8 { |
||||
|
MorePacketsRemaining = 0x00, |
||||
|
LastCommandPacket = 0x08, |
||||
|
}; |
||||
|
|
||||
|
enum class NFCReadCommand : u8 { |
||||
|
CancelAll = 0x00, |
||||
|
StartPolling = 0x01, |
||||
|
StopPolling = 0x02, |
||||
|
StartWaitingRecieve = 0x04, |
||||
|
Ntag = 0x06, |
||||
|
Mifare = 0x0F, |
||||
|
}; |
||||
|
|
||||
|
enum class NFCTagType : u8 { |
||||
|
AllTags = 0x00, |
||||
|
Ntag215 = 0x01, |
||||
|
}; |
||||
|
|
||||
|
enum class NFCPages { |
||||
|
Block0 = 0, |
||||
|
Block45 = 45, |
||||
|
Block135 = 135, |
||||
|
Block231 = 231, |
||||
|
}; |
||||
|
|
||||
|
enum class NFCStatus : u8 { |
||||
|
LastPackage = 0x04, |
||||
|
TagLost = 0x07, |
||||
|
}; |
||||
|
|
||||
|
enum class IrsMode : u8 { |
||||
|
None = 0x02, |
||||
|
Moment = 0x03, |
||||
|
Dpd = 0x04, |
||||
|
Clustering = 0x06, |
||||
|
ImageTransfer = 0x07, |
||||
|
Silhouette = 0x08, |
||||
|
TeraImage = 0x09, |
||||
|
SilhouetteTeraImage = 0x0A, |
||||
|
}; |
||||
|
|
||||
|
enum class IrsResolution { |
||||
|
Size320x240, |
||||
|
Size160x120, |
||||
|
Size80x60, |
||||
|
Size40x30, |
||||
|
Size20x15, |
||||
|
None, |
||||
|
}; |
||||
|
|
||||
|
enum class IrsResolutionCode : u8 { |
||||
|
Size320x240 = 0x00, // Full pixel array |
||||
|
Size160x120 = 0x50, // Sensor Binning [2 X 2] |
||||
|
Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2] |
||||
|
Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4] |
||||
|
Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4] |
||||
|
}; |
||||
|
|
||||
|
// Size of image divided by 300 |
||||
|
enum class IrsFragments : u8 { |
||||
|
Size20x15 = 0x00, |
||||
|
Size40x30 = 0x03, |
||||
|
Size80x60 = 0x0f, |
||||
|
Size160x120 = 0x3f, |
||||
|
Size320x240 = 0xFF, |
||||
|
}; |
||||
|
|
||||
|
enum class IrLeds : u8 { |
||||
|
BrightAndDim = 0x00, |
||||
|
Bright = 0x20, |
||||
|
Dim = 0x10, |
||||
|
None = 0x30, |
||||
|
}; |
||||
|
|
||||
|
enum class IrExLedFilter : u8 { |
||||
|
Disabled = 0x00, |
||||
|
Enabled = 0x03, |
||||
|
}; |
||||
|
|
||||
|
enum class IrImageFlip : u8 { |
||||
|
Normal = 0x00, |
||||
|
Inverted = 0x02, |
||||
|
}; |
||||
|
|
||||
|
enum class IrRegistersAddress : u16 { |
||||
|
UpdateTime = 0x0400, |
||||
|
FinalizeConfig = 0x0700, |
||||
|
LedFilter = 0x0e00, |
||||
|
Leds = 0x1000, |
||||
|
LedIntensitiyMSB = 0x1100, |
||||
|
LedIntensitiyLSB = 0x1200, |
||||
|
ImageFlip = 0x2d00, |
||||
|
Resolution = 0x2e00, |
||||
|
DigitalGainLSB = 0x2e01, |
||||
|
DigitalGainMSB = 0x2f01, |
||||
|
ExposureLSB = 0x3001, |
||||
|
ExposureMSB = 0x3101, |
||||
|
ExposureTime = 0x3201, |
||||
|
WhitePixelThreshold = 0x4301, |
||||
|
DenoiseSmoothing = 0x6701, |
||||
|
DenoiseEdge = 0x6801, |
||||
|
DenoiseColor = 0x6901, |
||||
|
}; |
||||
|
|
||||
|
enum class DriverResult { |
||||
|
Success, |
||||
|
WrongReply, |
||||
|
Timeout, |
||||
|
UnsupportedControllerType, |
||||
|
HandleInUse, |
||||
|
ErrorReadingData, |
||||
|
ErrorWritingData, |
||||
|
NoDeviceDetected, |
||||
|
InvalidHandle, |
||||
|
NotSupported, |
||||
|
Disabled, |
||||
|
Unknown, |
||||
|
}; |
||||
|
|
||||
|
struct MotionSensorCalibration { |
||||
|
s16 offset; |
||||
|
s16 scale; |
||||
|
}; |
||||
|
|
||||
|
struct MotionCalibration { |
||||
|
std::array<MotionSensorCalibration, 3> accelerometer; |
||||
|
std::array<MotionSensorCalibration, 3> gyro; |
||||
|
}; |
||||
|
|
||||
|
// Basic motion data containing data from the sensors and a timestamp in microseconds |
||||
|
struct MotionData { |
||||
|
float gyro_x{}; |
||||
|
float gyro_y{}; |
||||
|
float gyro_z{}; |
||||
|
float accel_x{}; |
||||
|
float accel_y{}; |
||||
|
float accel_z{}; |
||||
|
u64 delta_timestamp{}; |
||||
|
}; |
||||
|
|
||||
|
struct JoyStickAxisCalibration { |
||||
|
u16 max{1}; |
||||
|
u16 min{1}; |
||||
|
u16 center{0}; |
||||
|
}; |
||||
|
|
||||
|
struct JoyStickCalibration { |
||||
|
JoyStickAxisCalibration x; |
||||
|
JoyStickAxisCalibration y; |
||||
|
}; |
||||
|
|
||||
|
struct RingCalibration { |
||||
|
s16 default_value; |
||||
|
s16 max_value; |
||||
|
s16 min_value; |
||||
|
}; |
||||
|
|
||||
|
struct Color { |
||||
|
u32 body; |
||||
|
u32 buttons; |
||||
|
u32 left_grip; |
||||
|
u32 right_grip; |
||||
|
}; |
||||
|
|
||||
|
struct Battery { |
||||
|
union { |
||||
|
u8 raw{}; |
||||
|
|
||||
|
BitField<0, 4, u8> unknown; |
||||
|
BitField<4, 1, u8> charging; |
||||
|
BitField<5, 3, u8> status; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
struct VibrationValue { |
||||
|
f32 low_amplitude; |
||||
|
f32 low_frequency; |
||||
|
f32 high_amplitude; |
||||
|
f32 high_frequency; |
||||
|
}; |
||||
|
|
||||
|
struct JoyconHandle { |
||||
|
SDL_hid_device* handle = nullptr; |
||||
|
u8 packet_counter{}; |
||||
|
}; |
||||
|
|
||||
|
struct MCUConfig { |
||||
|
MCUCommand command; |
||||
|
MCUSubCommand sub_command; |
||||
|
MCUMode mode; |
||||
|
INSERT_PADDING_BYTES(0x22); |
||||
|
u8 crc; |
||||
|
}; |
||||
|
static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size"); |
||||
|
|
||||
|
#pragma pack(push, 1) |
||||
|
struct InputReportPassive { |
||||
|
InputReport report_mode; |
||||
|
u16 button_input; |
||||
|
u8 stick_state; |
||||
|
std::array<u8, 10> unknown_data; |
||||
|
}; |
||||
|
static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size"); |
||||
|
|
||||
|
struct InputReportActive { |
||||
|
InputReport report_mode; |
||||
|
u8 packet_id; |
||||
|
Battery battery_status; |
||||
|
std::array<u8, 3> button_input; |
||||
|
std::array<u8, 3> left_stick_state; |
||||
|
std::array<u8, 3> right_stick_state; |
||||
|
u8 vibration_code; |
||||
|
std::array<s16, 6 * 2> motion_input; |
||||
|
INSERT_PADDING_BYTES(0x2); |
||||
|
s16 ring_input; |
||||
|
}; |
||||
|
static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size"); |
||||
|
|
||||
|
struct InputReportNfcIr { |
||||
|
InputReport report_mode; |
||||
|
u8 packet_id; |
||||
|
Battery battery_status; |
||||
|
std::array<u8, 3> button_input; |
||||
|
std::array<u8, 3> left_stick_state; |
||||
|
std::array<u8, 3> right_stick_state; |
||||
|
u8 vibration_code; |
||||
|
std::array<s16, 6 * 2> motion_input; |
||||
|
INSERT_PADDING_BYTES(0x4); |
||||
|
}; |
||||
|
static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size"); |
||||
|
#pragma pack(pop) |
||||
|
|
||||
|
struct IMUCalibration { |
||||
|
std::array<s16, 3> accelerometer_offset; |
||||
|
std::array<s16, 3> accelerometer_scale; |
||||
|
std::array<s16, 3> gyroscope_offset; |
||||
|
std::array<s16, 3> gyroscope_scale; |
||||
|
}; |
||||
|
static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size"); |
||||
|
|
||||
|
struct NFCReadBlock { |
||||
|
u8 start; |
||||
|
u8 end; |
||||
|
}; |
||||
|
static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size"); |
||||
|
|
||||
|
struct NFCReadBlockCommand { |
||||
|
u8 block_count{}; |
||||
|
std::array<NFCReadBlock, 4> blocks{}; |
||||
|
}; |
||||
|
static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size"); |
||||
|
|
||||
|
struct NFCReadCommandData { |
||||
|
u8 unknown; |
||||
|
u8 uuid_length; |
||||
|
u8 unknown_2; |
||||
|
std::array<u8, 6> uid; |
||||
|
NFCTagType tag_type; |
||||
|
NFCReadBlockCommand read_block; |
||||
|
}; |
||||
|
static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); |
||||
|
|
||||
|
struct NFCPollingCommandData { |
||||
|
u8 enable_mifare; |
||||
|
u8 unknown_1; |
||||
|
u8 unknown_2; |
||||
|
u8 unknown_3; |
||||
|
u8 unknown_4; |
||||
|
}; |
||||
|
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); |
||||
|
|
||||
|
struct NFCRequestState { |
||||
|
MCUSubCommand sub_command; |
||||
|
NFCReadCommand command_argument; |
||||
|
u8 packet_id; |
||||
|
INSERT_PADDING_BYTES(0x1); |
||||
|
MCUPacketFlag packet_flag; |
||||
|
u8 data_length; |
||||
|
union { |
||||
|
std::array<u8, 0x1F> raw_data; |
||||
|
NFCReadCommandData nfc_read; |
||||
|
NFCPollingCommandData nfc_polling; |
||||
|
}; |
||||
|
u8 crc; |
||||
|
}; |
||||
|
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); |
||||
|
|
||||
|
struct IrsConfigure { |
||||
|
MCUCommand command; |
||||
|
MCUSubCommand sub_command; |
||||
|
IrsMode irs_mode; |
||||
|
IrsFragments number_of_fragments; |
||||
|
u16 mcu_major_version; |
||||
|
u16 mcu_minor_version; |
||||
|
INSERT_PADDING_BYTES(0x1D); |
||||
|
u8 crc; |
||||
|
}; |
||||
|
static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size"); |
||||
|
|
||||
|
#pragma pack(push, 1) |
||||
|
struct IrsRegister { |
||||
|
IrRegistersAddress address; |
||||
|
u8 value; |
||||
|
}; |
||||
|
static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size"); |
||||
|
|
||||
|
struct IrsWriteRegisters { |
||||
|
MCUCommand command; |
||||
|
MCUSubCommand sub_command; |
||||
|
u8 number_of_registers; |
||||
|
std::array<IrsRegister, 9> registers; |
||||
|
INSERT_PADDING_BYTES(0x7); |
||||
|
u8 crc; |
||||
|
}; |
||||
|
static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size"); |
||||
|
#pragma pack(pop) |
||||
|
|
||||
|
struct FirmwareVersion { |
||||
|
u8 major; |
||||
|
u8 minor; |
||||
|
}; |
||||
|
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); |
||||
|
|
||||
|
struct DeviceInfo { |
||||
|
FirmwareVersion firmware; |
||||
|
MacAddress mac_address; |
||||
|
}; |
||||
|
static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size"); |
||||
|
|
||||
|
struct MotionStatus { |
||||
|
bool is_enabled; |
||||
|
u64 delta_time; |
||||
|
GyroSensitivity gyro_sensitivity; |
||||
|
AccelerometerSensitivity accelerometer_sensitivity; |
||||
|
}; |
||||
|
|
||||
|
struct RingStatus { |
||||
|
bool is_enabled; |
||||
|
s16 default_value; |
||||
|
s16 max_value; |
||||
|
s16 min_value; |
||||
|
}; |
||||
|
|
||||
|
struct JoyconCallbacks { |
||||
|
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, const MotionData&)> on_motion_data; |
||||
|
std::function<void(f32)> on_ring_data; |
||||
|
std::function<void(const std::vector<u8>&)> on_amiibo_data; |
||||
|
std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,400 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <thread>
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/nfc.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) |
||||
|
: JoyconCommonProtocol(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult NfcProtocol::EnableNfc() { |
||||
|
LOG_INFO(Input, "Enable NFC"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = EnableMCU(true); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
const MCUConfig config{ |
||||
|
.command = MCUCommand::ConfigureMCU, |
||||
|
.sub_command = MCUSubCommand::SetMCUMode, |
||||
|
.mode = MCUMode::NFC, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
result = ConfigureMCU(config); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::DisableNfc() { |
||||
|
LOG_DEBUG(Input, "Disable NFC"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = EnableMCU(false); |
||||
|
} |
||||
|
|
||||
|
is_enabled = false; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::StartNFCPollingMode() { |
||||
|
LOG_DEBUG(Input, "Start NFC pooling Mode"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
TagFoundData tag_data{}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitUntilNfcIsReady(); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
is_enabled = true; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { |
||||
|
LOG_DEBUG(Input, "Start NFC pooling Mode"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
TagFoundData tag_data{}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = StartPolling(tag_data); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = ReadTag(tag_data); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = WaitUntilNfcIsReady(); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = StartPolling(tag_data); |
||||
|
} |
||||
|
if (result == DriverResult::Success) { |
||||
|
result = GetAmiiboData(data); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
bool NfcProtocol::HasAmiibo() { |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
TagFoundData tag_data{}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = StartPolling(tag_data); |
||||
|
} |
||||
|
|
||||
|
return result == DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::WaitUntilNfcIsReady() { |
||||
|
constexpr std::size_t timeout_limit = 10; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
do { |
||||
|
auto result = SendStartWaitingRecieveRequest(output); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
if (tries++ > timeout_limit) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 || |
||||
|
output[56] != 0x00); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::StartPolling(TagFoundData& data) { |
||||
|
LOG_DEBUG(Input, "Start Polling for tag"); |
||||
|
constexpr std::size_t timeout_limit = 7; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
do { |
||||
|
const auto result = SendStartPollingRequest(output); |
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
if (tries++ > timeout_limit) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09); |
||||
|
|
||||
|
data.type = output[62]; |
||||
|
data.uuid.resize(output[64]); |
||||
|
memcpy(data.uuid.data(), output.data() + 65, data.uuid.size()); |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { |
||||
|
constexpr std::size_t timeout_limit = 10; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
std::string uuid_string; |
||||
|
for (auto& content : data.uuid) { |
||||
|
uuid_string += fmt::format(" {:02x}", content); |
||||
|
} |
||||
|
|
||||
|
LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string); |
||||
|
|
||||
|
tries = 0; |
||||
|
NFCPages ntag_pages = NFCPages::Block0; |
||||
|
// Read Tag data
|
||||
|
while (true) { |
||||
|
auto result = SendReadAmiiboRequest(output, ntag_pages); |
||||
|
const auto mcu_report = static_cast<MCUReport>(output[49]); |
||||
|
const auto nfc_status = static_cast<NFCStatus>(output[56]); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) && |
||||
|
nfc_status == NFCStatus::TagLost) { |
||||
|
return DriverResult::ErrorReadingData; |
||||
|
} |
||||
|
|
||||
|
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) { |
||||
|
if (data.type != 2) { |
||||
|
continue; |
||||
|
} |
||||
|
switch (output[74]) { |
||||
|
case 0: |
||||
|
ntag_pages = NFCPages::Block135; |
||||
|
break; |
||||
|
case 3: |
||||
|
ntag_pages = NFCPages::Block45; |
||||
|
break; |
||||
|
case 4: |
||||
|
ntag_pages = NFCPages::Block231; |
||||
|
break; |
||||
|
default: |
||||
|
return DriverResult::ErrorReadingData; |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { |
||||
|
// finished
|
||||
|
SendStopPollingRequest(output); |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
// Ignore other state reports
|
||||
|
if (mcu_report == MCUReport::NFCState) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (tries++ > timeout_limit) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { |
||||
|
constexpr std::size_t timeout_limit = 10; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
|
||||
|
NFCPages ntag_pages = NFCPages::Block135; |
||||
|
std::size_t ntag_buffer_pos = 0; |
||||
|
// Read Tag data
|
||||
|
while (true) { |
||||
|
auto result = SendReadAmiiboRequest(output, ntag_pages); |
||||
|
const auto mcu_report = static_cast<MCUReport>(output[49]); |
||||
|
const auto nfc_status = static_cast<NFCStatus>(output[56]); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) && |
||||
|
nfc_status == NFCStatus::TagLost) { |
||||
|
return DriverResult::ErrorReadingData; |
||||
|
} |
||||
|
|
||||
|
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) { |
||||
|
std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF; |
||||
|
if (output[52] == 0x01) { |
||||
|
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60); |
||||
|
ntag_buffer_pos += payload_size - 60; |
||||
|
} else { |
||||
|
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size); |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { |
||||
|
LOG_INFO(Input, "Finished reading amiibo"); |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
// Ignore other state reports
|
||||
|
if (mcu_report == MCUReport::NFCState) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (tries++ > timeout_limit) { |
||||
|
return DriverResult::Timeout; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) { |
||||
|
NFCRequestState request{ |
||||
|
.sub_command = MCUSubCommand::ReadDeviceMode, |
||||
|
.command_argument = NFCReadCommand::StartPolling, |
||||
|
.packet_id = 0x0, |
||||
|
.packet_flag = MCUPacketFlag::LastCommandPacket, |
||||
|
.data_length = sizeof(NFCPollingCommandData), |
||||
|
.nfc_polling = |
||||
|
{ |
||||
|
.enable_mifare = 0x01, |
||||
|
.unknown_1 = 0x00, |
||||
|
.unknown_2 = 0x00, |
||||
|
.unknown_3 = 0x2c, |
||||
|
.unknown_4 = 0x01, |
||||
|
}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::array<u8, sizeof(NFCRequestState)> request_data{}; |
||||
|
memcpy(request_data.data(), &request, sizeof(NFCRequestState)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) { |
||||
|
NFCRequestState request{ |
||||
|
.sub_command = MCUSubCommand::ReadDeviceMode, |
||||
|
.command_argument = NFCReadCommand::StopPolling, |
||||
|
.packet_id = 0x0, |
||||
|
.packet_flag = MCUPacketFlag::LastCommandPacket, |
||||
|
.data_length = 0, |
||||
|
.raw_data = {}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::array<u8, sizeof(NFCRequestState)> request_data{}; |
||||
|
memcpy(request_data.data(), &request, sizeof(NFCRequestState)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) { |
||||
|
NFCRequestState request{ |
||||
|
.sub_command = MCUSubCommand::ReadDeviceMode, |
||||
|
.command_argument = NFCReadCommand::StartWaitingRecieve, |
||||
|
.packet_id = 0x0, |
||||
|
.packet_flag = MCUPacketFlag::LastCommandPacket, |
||||
|
.data_length = 0, |
||||
|
.raw_data = {}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::vector<u8> request_data(sizeof(NFCRequestState)); |
||||
|
memcpy(request_data.data(), &request, sizeof(NFCRequestState)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); |
||||
|
} |
||||
|
|
||||
|
DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) { |
||||
|
NFCRequestState request{ |
||||
|
.sub_command = MCUSubCommand::ReadDeviceMode, |
||||
|
.command_argument = NFCReadCommand::Ntag, |
||||
|
.packet_id = 0x0, |
||||
|
.packet_flag = MCUPacketFlag::LastCommandPacket, |
||||
|
.data_length = sizeof(NFCReadCommandData), |
||||
|
.nfc_read = |
||||
|
{ |
||||
|
.unknown = 0xd0, |
||||
|
.uuid_length = 0x07, |
||||
|
.unknown_2 = 0x00, |
||||
|
.uid = {}, |
||||
|
.tag_type = NFCTagType::AllTags, |
||||
|
.read_block = GetReadBlockCommand(ntag_pages), |
||||
|
}, |
||||
|
.crc = {}, |
||||
|
}; |
||||
|
|
||||
|
std::array<u8, sizeof(NFCRequestState)> request_data{}; |
||||
|
memcpy(request_data.data(), &request, sizeof(NFCRequestState)); |
||||
|
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); |
||||
|
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); |
||||
|
} |
||||
|
|
||||
|
NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { |
||||
|
switch (pages) { |
||||
|
case NFCPages::Block0: |
||||
|
return { |
||||
|
.block_count = 1, |
||||
|
}; |
||||
|
case NFCPages::Block45: |
||||
|
return { |
||||
|
.block_count = 1, |
||||
|
.blocks = |
||||
|
{ |
||||
|
NFCReadBlock{0x00, 0x2C}, |
||||
|
}, |
||||
|
}; |
||||
|
case NFCPages::Block135: |
||||
|
return { |
||||
|
.block_count = 3, |
||||
|
.blocks = |
||||
|
{ |
||||
|
NFCReadBlock{0x00, 0x3b}, |
||||
|
{0x3c, 0x77}, |
||||
|
{0x78, 0x86}, |
||||
|
}, |
||||
|
}; |
||||
|
case NFCPages::Block231: |
||||
|
return { |
||||
|
.block_count = 4, |
||||
|
.blocks = |
||||
|
{ |
||||
|
NFCReadBlock{0x00, 0x3b}, |
||||
|
{0x3c, 0x77}, |
||||
|
{0x78, 0x83}, |
||||
|
{0xb4, 0xe6}, |
||||
|
}, |
||||
|
}; |
||||
|
default: |
||||
|
return {}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
bool NfcProtocol::IsEnabled() const { |
||||
|
return is_enabled; |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -0,0 +1,61 @@ |
|||||
|
// 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 NfcProtocol final : private JoyconCommonProtocol { |
||||
|
public: |
||||
|
explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
DriverResult EnableNfc(); |
||||
|
|
||||
|
DriverResult DisableNfc(); |
||||
|
|
||||
|
DriverResult StartNFCPollingMode(); |
||||
|
|
||||
|
DriverResult ScanAmiibo(std::vector<u8>& data); |
||||
|
|
||||
|
bool HasAmiibo(); |
||||
|
|
||||
|
bool IsEnabled() const; |
||||
|
|
||||
|
private: |
||||
|
struct TagFoundData { |
||||
|
u8 type; |
||||
|
std::vector<u8> uuid; |
||||
|
}; |
||||
|
|
||||
|
DriverResult WaitUntilNfcIsReady(); |
||||
|
|
||||
|
DriverResult StartPolling(TagFoundData& data); |
||||
|
|
||||
|
DriverResult ReadTag(const TagFoundData& data); |
||||
|
|
||||
|
DriverResult GetAmiiboData(std::vector<u8>& data); |
||||
|
|
||||
|
DriverResult SendStartPollingRequest(std::vector<u8>& output); |
||||
|
|
||||
|
DriverResult SendStopPollingRequest(std::vector<u8>& output); |
||||
|
|
||||
|
DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output); |
||||
|
|
||||
|
DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages); |
||||
|
|
||||
|
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; |
||||
|
|
||||
|
bool is_enabled{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,341 @@ |
|||||
|
// 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, |
||||
|
const RingStatus& ring_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; |
||||
|
} |
||||
|
|
||||
|
if (ring_status.is_enabled) { |
||||
|
UpdateRing(data.ring_input, ring_status); |
||||
|
} |
||||
|
|
||||
|
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::UpdateAmiibo(const std::vector<u8>& amiibo_data) { |
||||
|
callbacks.on_amiibo_data(amiibo_data); |
||||
|
} |
||||
|
|
||||
|
void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) { |
||||
|
callbacks.on_camera_data(camera_data, format); |
||||
|
} |
||||
|
|
||||
|
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{ |
||||
|
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 (auto left_button : left_buttons) { |
||||
|
const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0; |
||||
|
const int button = static_cast<int>(left_button); |
||||
|
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 (auto right_button : right_buttons) { |
||||
|
const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0; |
||||
|
const int button = static_cast<int>(right_button); |
||||
|
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 (auto pro_button : pro_buttons) { |
||||
|
const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0; |
||||
|
const int button = static_cast<int>(pro_button); |
||||
|
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,81 @@ |
|||||
|
// 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, |
||||
|
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); |
||||
|
void UpdateAmiibo(const std::vector<u8>& amiibo_data); |
||||
|
void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format); |
||||
|
|
||||
|
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,117 @@ |
|||||
|
// 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(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult RingConProtocol::EnableRingCon() { |
||||
|
LOG_DEBUG(Input, "Enable Ringcon"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult RingConProtocol::DisableRingCon() { |
||||
|
LOG_DEBUG(Input, "Disable RingCon"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
|
||||
|
if (result == DriverResult::Success) { |
||||
|
result = EnableMCU(false); |
||||
|
} |
||||
|
|
||||
|
is_enabled = false; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult RingConProtocol::StartRingconPolling() { |
||||
|
LOG_DEBUG(Input, "Enable Ringcon"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
DriverResult result{DriverResult::Success}; |
||||
|
bool is_connected = false; |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
DriverResult RingConProtocol::IsRingConnected(bool& is_connected) { |
||||
|
LOG_DEBUG(Input, "IsRingConnected"); |
||||
|
constexpr std::size_t max_tries = 28; |
||||
|
constexpr u8 ring_controller_id = 0x20; |
||||
|
std::vector<u8> output; |
||||
|
std::size_t tries = 0; |
||||
|
is_connected = false; |
||||
|
|
||||
|
do { |
||||
|
std::array<u8, 1> empty_data{}; |
||||
|
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[16] != ring_controller_id); |
||||
|
|
||||
|
is_connected = true; |
||||
|
return DriverResult::Success; |
||||
|
} |
||||
|
|
||||
|
DriverResult RingConProtocol::ConfigureRing() { |
||||
|
LOG_DEBUG(Input, "ConfigureRing"); |
||||
|
|
||||
|
static constexpr std::array<u8, 37> 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}; |
||||
|
|
||||
|
const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config); |
||||
|
|
||||
|
if (result != DriverResult::Success) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02}; |
||||
|
return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data); |
||||
|
} |
||||
|
|
||||
|
bool RingConProtocol::IsEnabled() const { |
||||
|
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: |
||||
|
explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
DriverResult EnableRingCon(); |
||||
|
|
||||
|
DriverResult DisableRingCon(); |
||||
|
|
||||
|
DriverResult StartRingconPolling(); |
||||
|
|
||||
|
bool IsEnabled() const; |
||||
|
|
||||
|
private: |
||||
|
DriverResult IsRingConnected(bool& is_connected); |
||||
|
|
||||
|
DriverResult ConfigureRing(); |
||||
|
|
||||
|
bool is_enabled{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon::Joycon |
||||
@ -0,0 +1,299 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <cmath>
|
||||
|
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "input_common/helpers/joycon_protocol/rumble.h"
|
||||
|
|
||||
|
namespace InputCommon::Joycon { |
||||
|
|
||||
|
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle) |
||||
|
: JoyconCommonProtocol(std::move(handle)) {} |
||||
|
|
||||
|
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) { |
||||
|
LOG_DEBUG(Input, "Enable Rumble"); |
||||
|
ScopedSetBlocking sb(this); |
||||
|
const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)}; |
||||
|
return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer); |
||||
|
} |
||||
|
|
||||
|
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) { |
||||
|
std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{}; |
||||
|
|
||||
|
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
|
||||
|
|
||||
|
static 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
|
||||
|
|
||||
|
static 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: |
||||
|
explicit 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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue