From 10241886ddbead1a24a5924debf83a4fad0ade0d Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:33:19 -0500 Subject: input_common: Rewrite udp client --- src/input_common/CMakeLists.txt | 6 +- src/input_common/drivers/udp_client.cpp | 364 ++++++++++++++++++++++ src/input_common/drivers/udp_client.h | 127 ++++++++ src/input_common/udp/client.cpp | 526 -------------------------------- src/input_common/udp/client.h | 183 ----------- src/input_common/udp/udp.cpp | 110 ------- src/input_common/udp/udp.h | 57 ---- 7 files changed, 493 insertions(+), 880 deletions(-) create mode 100644 src/input_common/drivers/udp_client.cpp create mode 100644 src/input_common/drivers/udp_client.h delete mode 100644 src/input_common/udp/client.cpp delete mode 100644 src/input_common/udp/client.h delete mode 100644 src/input_common/udp/udp.cpp delete mode 100644 src/input_common/udp/udp.h (limited to 'src') diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 19270dc84..9c0c82138 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -9,6 +9,8 @@ add_library(input_common STATIC drivers/tas_input.h drivers/touch_screen.cpp drivers/touch_screen.h + drivers/udp_client.cpp + drivers/udp_client.h helpers/stick_from_buttons.cpp helpers/stick_from_buttons.h helpers/touch_from_buttons.cpp @@ -29,10 +31,6 @@ add_library(input_common STATIC motion_input.h sdl/sdl.cpp sdl/sdl.h - udp/client.cpp - udp/client.h - udp/udp.cpp - udp/udp.h ) if (MSVC) diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 000000000..6fcc3a01b --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp @@ -0,0 +1,364 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/udp_protocol.h" + +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { + std::function version; + std::function port_info; + std::function pad_data; +}; + +class Socket { +public: + using clock = std::chrono::system_clock; + + explicit Socket(const std::string& host, u16 port, SocketCallback callback_) + : callback(std::move(callback_)), timer(io_service), + socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { + boost::system::error_code ec{}; + auto ipv4 = boost::asio::ip::make_address_v4(host, ec); + if (ec.value() != boost::system::errc::success) { + LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); + ipv4 = boost::asio::ip::address_v4{}; + } + + send_endpoint = {udp::endpoint(ipv4, port)}; + } + + void Stop() { + io_service.stop(); + } + + void Loop() { + io_service.run(); + } + + void StartSend(const clock::time_point& from) { + timer.expires_at(from + std::chrono::seconds(3)); + timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); + } + + void StartReceive() { + socket.async_receive_from( + boost::asio::buffer(receive_buffer), receive_endpoint, + [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + HandleReceive(error, bytes_transferred); + }); + } + +private: + u32 GenerateRandomClientId() const { + std::random_device device; + return device(); + } + + void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { + if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { + switch (*type) { + case Type::Version: { + Response::Version version; + std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); + callback.version(std::move(version)); + break; + } + case Type::PortInfo: { + Response::PortInfo port_info; + std::memcpy(&port_info, &receive_buffer[sizeof(Header)], + sizeof(Response::PortInfo)); + callback.port_info(std::move(port_info)); + break; + } + case Type::PadData: { + Response::PadData pad_data; + std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + callback.pad_data(std::move(pad_data)); + break; + } + } + } + StartReceive(); + } + + void HandleSend(const boost::system::error_code&) { + boost::system::error_code _ignored{}; + // Send a request for getting port info for the pad + const Request::PortInfo port_info{4, {0, 1, 2, 3}}; + const auto port_message = Request::Create(port_info, client_id); + std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); + socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); + + // Send a request for getting pad data for the pad + const Request::PadData pad_data{ + Request::PadData::Flags::AllPorts, + 0, + EMPTY_MAC_ADDRESS, + }; + const auto pad_message = Request::Create(pad_data, client_id); + std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); + socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); + StartSend(timer.expiry()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer timer; + udp::socket socket; + + const u32 client_id; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); + std::array send_buffer1; + std::array send_buffer2; + udp::endpoint send_endpoint; + + std::array receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(input_engine_) { + LOG_INFO(Input, "Udp Initialization started"); + ReloadSockets(); +} + +UDPClient::~UDPClient() { + Reset(); +} + +UDPClient::ClientConnection::ClientConnection() = default; + +UDPClient::ClientConnection::~ClientConnection() = default; + +void UDPClient::ReloadSockets() { + Reset(); + + std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue()); + std::string server_token; + std::size_t client = 0; + while (std::getline(servers_ss, server_token, ',')) { + if (client == MAX_UDP_CLIENTS) { + break; + } + std::stringstream server_ss(server_token); + std::string token; + std::getline(server_ss, token, ':'); + std::string udp_input_address = token; + std::getline(server_ss, token, ':'); + char* temp; + const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); + if (*temp != '\0') { + LOG_ERROR(Input, "Port number is not valid {}", token); + continue; + } + + const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); + if (client_number != MAX_UDP_CLIENTS) { + LOG_ERROR(Input, "Duplicated UDP servers found"); + continue; + } + StartCommunication(client++, udp_input_address, udp_input_port); + } +} + +std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active == -1) { + continue; + } + if (clients[client].host == host && clients[client].port == port) { + return client; + } + } + return MAX_UDP_CLIENTS; +} + +void UDPClient::OnVersion([[maybe_unused]] Response::Version data) { + LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) { + LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void UDPClient::OnPadData(Response::PadData data, std::size_t client) { + const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; + + if (pad_index >= pads.size()) { + LOG_ERROR(Input, "Invalid pad id {}", data.info.id); + return; + } + + LOG_TRACE(Input, "PadData packet received"); + if (data.packet_counter == pads[pad_index].packet_sequence) { + LOG_WARNING( + Input, + "PadData packet dropped because its stale info. Current count: {} Packet count: {}", + pads[pad_index].packet_sequence, data.packet_counter); + pads[pad_index].connected = false; + return; + } + + clients[client].active = 1; + pads[pad_index].connected = true; + pads[pad_index].packet_sequence = data.packet_counter; + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast( + std::chrono::duration_cast(now - pads[pad_index].last_update) + .count()); + pads[pad_index].last_update = now; + + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + const float gyro_scale = 1.0f / 312.0f; + + const BasicMotion motion{ + .gyro_x = data.gyro.pitch * gyro_scale, + .gyro_y = data.gyro.roll * gyro_scale, + .gyro_z = -data.gyro.yaw * gyro_scale, + .accel_x = data.accel.x, + .accel_y = -data.accel.z, + .accel_z = data.accel.y, + .delta_timestamp = time_difference, + }; + const PadIdentifier identifier = GetPadIdentifier(pad_index); + SetMotion(identifier, 0, motion); +} + +void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this, client](Response::PadData data) { OnPadData(data, client); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); + clients[client].host = host; + clients[client].port = port; + clients[client].active = 0; + clients[client].socket = std::make_unique(host, port, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; + for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { + const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); + PreSetController(identifier); + } +} + +const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { + const std::size_t client = pad_index / PADS_PER_CLIENT; + return { + .guid = Common::UUID{clients[client].host}, + .port = static_cast(clients[client].port), + .pad = pad_index, + }; +} + +void UDPClient::Reset() { + for (auto& client : clients) { + if (client.thread.joinable()) { + client.active = -1; + client.socket->Stop(); + client.thread.join(); + } + } +} + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback) { + std::thread([=] { + Common::Event success_event; + SocketCallback callback{ + .version = [](Response::Version) {}, + .port_info = [](Response::PortInfo) {}, + .pad_data = [&](Response::PadData) { success_event.Set(); }, + }; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + const bool result = + success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); + socket.Stop(); + worker_thread.join(); + if (result) { + success_callback(); + } else { + failure_callback(); + } + }).detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( + const std::string& host, u16 port, std::function status_callback, + std::function data_callback) { + + std::thread([=, this] { + Status current_status{Status::Initialized}; + SocketCallback callback{ + [](Response::Version) {}, [](Response::PortInfo) {}, + [&](Response::PadData data) { + static constexpr u16 CALIBRATION_THRESHOLD = 100; + static constexpr u16 MAX_VALUE = UINT16_MAX; + + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + const auto& touchpad_0 = data.touch[0]; + if (touchpad_0.is_active == 0) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); + const u16 min_x = std::min(MAX_VALUE, static_cast(touchpad_0.x)); + const u16 min_y = std::min(MAX_VALUE, static_cast(touchpad_0.y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && + touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes configuration + const u16 max_x = touchpad_0.x; + const u16 max_y = touchpad_0.y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); + + complete_event.Set(); + } + }}; + Socket socket{host, port, std::move(callback)}; + std::thread worker_thread{SocketLoop, &socket}; + complete_event.Wait(); + socket.Stop(); + worker_thread.join(); + }).detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { + Stop(); +} + +void CalibrationConfigurationJob::Stop() { + complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h new file mode 100644 index 000000000..58b2e921d --- /dev/null +++ b/src/input_common/drivers/udp_client.h @@ -0,0 +1,127 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/thread.h" +#include "input_common/input_engine.h" + +namespace InputCommon::CemuhookUDP { + +class Socket; + +namespace Response { +struct PadData; +struct PortInfo; +struct TouchPad; +struct Version; +} // namespace Response + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + std::string host{"127.0.0.1"}; + u16 port{26760}; + std::size_t pad_index{}; +}; + +struct DeviceStatus { + std::mutex update_mutex; + + // calibration data for scaling the device's touch area to 3ds + struct CalibrationData { + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; + }; + std::optional touch_calibration; +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class UDPClient final : public InputCommon::InputEngine { +public: + explicit UDPClient(const std::string& input_engine_); + ~UDPClient(); + + void ReloadSockets(); + +private: + struct PadData { + std::size_t pad_index{}; + bool connected{}; + DeviceStatus status; + u64 packet_sequence{}; + + std::chrono::time_point last_update; + }; + + struct ClientConnection { + ClientConnection(); + ~ClientConnection(); + std::string host{"127.0.0.1"}; + u16 port{26760}; + s8 active{-1}; + std::unique_ptr socket; + std::thread thread; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + + // Translates configuration to client number + std::size_t GetClientNumber(std::string_view host, u16 port) const; + + void OnVersion(Response::Version); + void OnPortInfo(Response::PortInfo); + void OnPadData(Response::PadData, std::size_t client); + void StartCommunication(std::size_t client, const std::string& host, u16 port); + const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + + // Allocate clients for 8 udp servers + static constexpr std::size_t MAX_UDP_CLIENTS = 8; + static constexpr std::size_t PADS_PER_CLIENT = 4; + std::array pads{}; + std::array clients{}; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: + enum class Status { + Initialized, + Ready, + Stage1Completed, + Completed, + }; + /** + * Constructs and starts the job with the specified parameter. + * + * @param status_callback Callback for job status updates + * @param data_callback Called when calibration data is ready + */ + explicit CalibrationConfigurationJob(const std::string& host, u16 port, + std::function status_callback, + std::function data_callback); + ~CalibrationConfigurationJob(); + void Stop(); + +private: + Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp deleted file mode 100644 index bcc29c4e0..000000000 --- a/src/input_common/udp/client.cpp +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/udp/client.h" -#include "input_common/helpers/udp_protocol.h" - -using boost::asio::ip::udp; - -namespace InputCommon::CemuhookUDP { - -struct SocketCallback { - std::function version; - std::function port_info; - std::function pad_data; -}; - -class Socket { -public: - using clock = std::chrono::system_clock; - - explicit Socket(const std::string& host, u16 port, SocketCallback callback_) - : callback(std::move(callback_)), timer(io_service), - socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { - boost::system::error_code ec{}; - auto ipv4 = boost::asio::ip::make_address_v4(host, ec); - if (ec.value() != boost::system::errc::success) { - LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); - ipv4 = boost::asio::ip::address_v4{}; - } - - send_endpoint = {udp::endpoint(ipv4, port)}; - } - - void Stop() { - io_service.stop(); - } - - void Loop() { - io_service.run(); - } - - void StartSend(const clock::time_point& from) { - timer.expires_at(from + std::chrono::seconds(3)); - timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); - } - - void StartReceive() { - socket.async_receive_from( - boost::asio::buffer(receive_buffer), receive_endpoint, - [this](const boost::system::error_code& error, std::size_t bytes_transferred) { - HandleReceive(error, bytes_transferred); - }); - } - -private: - u32 GenerateRandomClientId() const { - std::random_device device; - return device(); - } - - void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { - if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { - switch (*type) { - case Type::Version: { - Response::Version version; - std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); - callback.version(std::move(version)); - break; - } - case Type::PortInfo: { - Response::PortInfo port_info; - std::memcpy(&port_info, &receive_buffer[sizeof(Header)], - sizeof(Response::PortInfo)); - callback.port_info(std::move(port_info)); - break; - } - case Type::PadData: { - Response::PadData pad_data; - std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); - SanitizeMotion(pad_data); - callback.pad_data(std::move(pad_data)); - break; - } - } - } - StartReceive(); - } - - void HandleSend(const boost::system::error_code&) { - boost::system::error_code _ignored{}; - // Send a request for getting port info for the pad - const Request::PortInfo port_info{4, {0, 1, 2, 3}}; - const auto port_message = Request::Create(port_info, client_id); - std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); - socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); - - // Send a request for getting pad data for the pad - const Request::PadData pad_data{ - Request::PadData::Flags::AllPorts, - 0, - EMPTY_MAC_ADDRESS, - }; - const auto pad_message = Request::Create(pad_data, client_id); - std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); - socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); - StartSend(timer.expiry()); - } - - void SanitizeMotion(Response::PadData& data) { - // Zero out any non number value - if (!std::isnormal(data.gyro.pitch)) { - data.gyro.pitch = 0; - } - if (!std::isnormal(data.gyro.roll)) { - data.gyro.roll = 0; - } - if (!std::isnormal(data.gyro.yaw)) { - data.gyro.yaw = 0; - } - if (!std::isnormal(data.accel.x)) { - data.accel.x = 0; - } - if (!std::isnormal(data.accel.y)) { - data.accel.y = 0; - } - if (!std::isnormal(data.accel.z)) { - data.accel.z = 0; - } - } - - SocketCallback callback; - boost::asio::io_service io_service; - boost::asio::basic_waitable_timer timer; - udp::socket socket; - - const u32 client_id; - - static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); - static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); - std::array send_buffer1; - std::array send_buffer2; - udp::endpoint send_endpoint; - - std::array receive_buffer; - udp::endpoint receive_endpoint; -}; - -static void SocketLoop(Socket* socket) { - socket->StartReceive(); - socket->StartSend(Socket::clock::now()); - socket->Loop(); -} - -Client::Client() { - LOG_INFO(Input, "Udp Initialization started"); - finger_id.fill(MAX_TOUCH_FINGERS); - ReloadSockets(); -} - -Client::~Client() { - Reset(); -} - -Client::ClientConnection::ClientConnection() = default; - -Client::ClientConnection::~ClientConnection() = default; - -std::vector Client::GetInputDevices() const { - std::vector devices; - for (std::size_t pad = 0; pad < pads.size(); pad++) { - if (!DeviceConnected(pad)) { - continue; - } - std::string name = fmt::format("UDP Controller {}", pad); - devices.emplace_back(Common::ParamPackage{ - {"class", "cemuhookudp"}, - {"display", std::move(name)}, - {"port", std::to_string(pad)}, - }); - } - return devices; -} - -bool Client::DeviceConnected(std::size_t pad) const { - // Use last timestamp to detect if the socket has stopped sending data - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast( - std::chrono::duration_cast(now - pads[pad].last_update).count()); - return time_difference < 1000 && pads[pad].connected; -} - -void Client::ReloadSockets() { - Reset(); - - std::stringstream servers_ss(static_cast(Settings::values.udp_input_servers)); - std::string server_token; - std::size_t client = 0; - while (std::getline(servers_ss, server_token, ',')) { - if (client == MAX_UDP_CLIENTS) { - break; - } - std::stringstream server_ss(server_token); - std::string token; - std::getline(server_ss, token, ':'); - std::string udp_input_address = token; - std::getline(server_ss, token, ':'); - char* temp; - const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); - if (*temp != '\0') { - LOG_ERROR(Input, "Port number is not valid {}", token); - continue; - } - - const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); - if (client_number != MAX_UDP_CLIENTS) { - LOG_ERROR(Input, "Duplicated UDP servers found"); - continue; - } - StartCommunication(client++, udp_input_address, udp_input_port); - } -} - -std::size_t Client::GetClientNumber(std::string_view host, u16 port) const { - for (std::size_t client = 0; client < clients.size(); client++) { - if (clients[client].active == -1) { - continue; - } - if (clients[client].host == host && clients[client].port == port) { - return client; - } - } - return MAX_UDP_CLIENTS; -} - -void Client::OnVersion([[maybe_unused]] Response::Version data) { - LOG_TRACE(Input, "Version packet received: {}", data.version); -} - -void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { - LOG_TRACE(Input, "PortInfo packet received: {}", data.model); -} - -void Client::OnPadData(Response::PadData data, std::size_t client) { - const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; - - if (pad_index >= pads.size()) { - LOG_ERROR(Input, "Invalid pad id {}", data.info.id); - return; - } - - LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter == pads[pad_index].packet_sequence) { - LOG_WARNING( - Input, - "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - pads[pad_index].packet_sequence, data.packet_counter); - pads[pad_index].connected = false; - return; - } - - clients[client].active = 1; - pads[pad_index].connected = true; - pads[pad_index].packet_sequence = data.packet_counter; - - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast( - std::chrono::duration_cast(now - pads[pad_index].last_update) - .count()); - pads[pad_index].last_update = now; - - const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; - pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); - // Gyroscope values are not it the correct scale from better joy. - // Dividing by 312 allows us to make one full turn = 1 turn - // This must be a configurable valued called sensitivity - pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f); - pads[pad_index].motion.UpdateRotation(time_difference); - pads[pad_index].motion.UpdateOrientation(time_difference); - - { - std::lock_guard guard(pads[pad_index].status.update_mutex); - pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion(); - - for (std::size_t id = 0; id < data.touch.size(); ++id) { - UpdateTouchInput(data.touch[id], client, id); - } - - if (configuring) { - const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope(); - const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration(); - UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope); - } - } -} - -void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this, client](Response::PadData data) { OnPadData(data, client); }}; - LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - clients[client].host = host; - clients[client].port = port; - clients[client].active = 0; - clients[client].socket = std::make_unique(host, port, callback); - clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; - - // Set motion parameters - // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode - // Real HW values are unknown, 0.0001 is an approximate to Standard - for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) { - pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f); - } -} - -void Client::Reset() { - for (auto& client : clients) { - if (client.thread.joinable()) { - client.active = -1; - client.socket->Stop(); - client.thread.join(); - } - } -} - -void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3& acc, const Common::Vec3& gyro) { - if (gyro.Length() > 0.2f) { - LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client, - gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]); - } - UDPPadStatus pad{ - .host = clients[client].host, - .port = clients[client].port, - .pad_index = pad_index, - }; - for (std::size_t i = 0; i < 3; ++i) { - if (gyro[i] > 5.0f || gyro[i] < -5.0f) { - pad.motion = static_cast(i); - pad.motion_value = gyro[i]; - pad_queue.Push(pad); - } - if (acc[i] > 1.75f || acc[i] < -1.75f) { - pad.motion = static_cast(i + 3); - pad.motion_value = acc[i]; - pad_queue.Push(pad); - } - } -} - -std::optional Client::GetUnusedFingerID() const { - std::size_t first_free_id = 0; - while (first_free_id < MAX_TOUCH_FINGERS) { - if (!std::get<2>(touch_status[first_free_id])) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) { - // TODO: Use custom calibration per device - const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); - const u16 min_x = static_cast(touch_param.Get("min_x", 100)); - const u16 min_y = static_cast(touch_param.Get("min_y", 50)); - const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); - const u16 max_y = static_cast(touch_param.Get("max_y", 850)); - const std::size_t touch_id = client * 2 + id; - if (touch_pad.is_active) { - if (finger_id[touch_id] == MAX_TOUCH_FINGERS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id skip to next input - return; - } - finger_id[touch_id] = *first_free_id; - } - auto& [x, y, pressed] = touch_status[finger_id[touch_id]]; - x = static_cast(std::clamp(static_cast(touch_pad.x), min_x, max_x) - min_x) / - static_cast(max_x - min_x); - y = static_cast(std::clamp(static_cast(touch_pad.y), min_y, max_y) - min_y) / - static_cast(max_y - min_y); - pressed = true; - return; - } - - if (finger_id[touch_id] != MAX_TOUCH_FINGERS) { - touch_status[finger_id[touch_id]] = {}; - finger_id[touch_id] = MAX_TOUCH_FINGERS; - } -} - -void Client::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Client::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -Input::TouchStatus& Client::GetTouchState() { - return touch_status; -} - -const Input::TouchStatus& Client::GetTouchState() const { - return touch_status; -} - -Common::SPSCQueue& Client::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue& Client::GetPadQueue() const { - return pad_queue; -} - -void TestCommunication(const std::string& host, u16 port, - const std::function& success_callback, - const std::function& failure_callback) { - std::thread([=] { - Common::Event success_event; - SocketCallback callback{ - .version = [](Response::Version) {}, - .port_info = [](Response::PortInfo) {}, - .pad_data = [&](Response::PadData) { success_event.Set(); }, - }; - Socket socket{host, port, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - const bool result = - success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); - socket.Stop(); - worker_thread.join(); - if (result) { - success_callback(); - } else { - failure_callback(); - } - }).detach(); -} - -CalibrationConfigurationJob::CalibrationConfigurationJob( - const std::string& host, u16 port, std::function status_callback, - std::function data_callback) { - - std::thread([=, this] { - Status current_status{Status::Initialized}; - SocketCallback callback{ - [](Response::Version) {}, [](Response::PortInfo) {}, - [&](Response::PadData data) { - static constexpr u16 CALIBRATION_THRESHOLD = 100; - static constexpr u16 MAX_VALUE = UINT16_MAX; - - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - const auto& touchpad_0 = data.touch[0]; - if (touchpad_0.is_active == 0) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); - const u16 min_x = std::min(MAX_VALUE, static_cast(touchpad_0.x)); - const u16 min_y = std::min(MAX_VALUE, static_cast(touchpad_0.y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && - touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes configuration - const u16 max_x = touchpad_0.x; - const u16 max_y = touchpad_0.y; - current_status = Status::Completed; - data_callback(min_x, min_y, max_x, max_y); - status_callback(current_status); - - complete_event.Set(); - } - }}; - Socket socket{host, port, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - complete_event.Wait(); - socket.Stop(); - worker_thread.join(); - }).detach(); -} - -CalibrationConfigurationJob::~CalibrationConfigurationJob() { - Stop(); -} - -void CalibrationConfigurationJob::Stop() { - complete_event.Set(); -} - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h deleted file mode 100644 index 380f9bb76..000000000 --- a/src/input_common/udp/client.h +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/param_package.h" -#include "common/thread.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" - -namespace InputCommon::CemuhookUDP { - -class Socket; - -namespace Response { -struct PadData; -struct PortInfo; -struct TouchPad; -struct Version; -} // namespace Response - -enum class PadMotion { - GyroX, - GyroY, - GyroZ, - AccX, - AccY, - AccZ, - Undefined, -}; - -enum class PadTouch { - Click, - Undefined, -}; - -struct UDPPadStatus { - std::string host{"127.0.0.1"}; - u16 port{26760}; - std::size_t pad_index{}; - PadMotion motion{PadMotion::Undefined}; - f32 motion_value{0.0f}; -}; - -struct DeviceStatus { - std::mutex update_mutex; - Input::MotionStatus motion_status; - std::tuple touch_status; - - // calibration data for scaling the device's touch area to 3ds - struct CalibrationData { - u16 min_x{}; - u16 min_y{}; - u16 max_x{}; - u16 max_y{}; - }; - std::optional touch_calibration; -}; - -class Client { -public: - // Initialize the UDP client capture and read sequence - Client(); - - // Close and release the client - ~Client(); - - // Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - std::vector GetInputDevices() const; - - bool DeviceConnected(std::size_t pad) const; - void ReloadSockets(); - - Common::SPSCQueue& GetPadQueue(); - const Common::SPSCQueue& GetPadQueue() const; - - DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); - const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; - - Input::TouchStatus& GetTouchState(); - const Input::TouchStatus& GetTouchState() const; - -private: - struct PadData { - std::size_t pad_index{}; - bool connected{}; - DeviceStatus status; - u64 packet_sequence{}; - - // Realtime values - // motion is initalized with PID values for drift correction on joycons - InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; - std::chrono::time_point last_update; - }; - - struct ClientConnection { - ClientConnection(); - ~ClientConnection(); - std::string host{"127.0.0.1"}; - u16 port{26760}; - s8 active{-1}; - std::unique_ptr socket; - std::thread thread; - }; - - // For shutting down, clear all data, join all threads, release usb - void Reset(); - - // Translates configuration to client number - std::size_t GetClientNumber(std::string_view host, u16 port) const; - - void OnVersion(Response::Version); - void OnPortInfo(Response::PortInfo); - void OnPadData(Response::PadData, std::size_t client); - void StartCommunication(std::size_t client, const std::string& host, u16 port); - void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3& acc, const Common::Vec3& gyro); - - // Returns an unused finger id, if there is no fingers available std::nullopt will be - // returned - std::optional GetUnusedFingerID() const; - - // Merges and updates all touch inputs into the touch_status array - void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id); - - bool configuring = false; - - // Allocate clients for 8 udp servers - static constexpr std::size_t MAX_UDP_CLIENTS = 8; - static constexpr std::size_t PADS_PER_CLIENT = 4; - // Each client can have up 2 touch inputs - static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2; - std::array pads{}; - std::array clients{}; - Common::SPSCQueue pad_queue{}; - Input::TouchStatus touch_status{}; - std::array finger_id{}; -}; - -/// An async job allowing configuration of the touchpad calibration. -class CalibrationConfigurationJob { -public: - enum class Status { - Initialized, - Ready, - Stage1Completed, - Completed, - }; - /** - * Constructs and starts the job with the specified parameter. - * - * @param status_callback Callback for job status updates - * @param data_callback Called when calibration data is ready - */ - explicit CalibrationConfigurationJob(const std::string& host, u16 port, - std::function status_callback, - std::function data_callback); - ~CalibrationConfigurationJob(); - void Stop(); - -private: - Common::Event complete_event; -}; - -void TestCommunication(const std::string& host, u16 port, - const std::function& success_callback, - const std::function& failure_callback); - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp deleted file mode 100644 index 9829da6f0..000000000 --- a/src/input_common/udp/udp.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" - -namespace InputCommon { - -class UDPMotion final : public Input::MotionDevice { -public: - explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::MotionStatus GetStatus() const override { - return client->GetPadState(ip, port, pad).motion_status; - } - -private: - const std::string ip; - const u16 port; - const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPMotionFactory::UDPMotionFactory(std::shared_ptr client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr UDPMotionFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast(params.Get("port", 26760)); - const auto pad = static_cast(params.Get("pad_index", 0)); - - return std::make_unique(std::move(ip), port, pad, client.get()); -} - -void UDPMotionFactory::BeginConfiguration() { - polling = true; - client->BeginConfiguration(); -} - -void UDPMotionFactory::EndConfiguration() { - polling = false; - client->EndConfiguration(); -} - -Common::ParamPackage UDPMotionFactory::GetNextInput() { - Common::ParamPackage params; - CemuhookUDP::UDPPadStatus pad; - auto& queue = client->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { - continue; - } - params.Set("engine", "cemuhookudp"); - params.Set("ip", pad.host); - params.Set("port", static_cast(pad.port)); - params.Set("pad_index", static_cast(pad.pad_index)); - params.Set("motion", static_cast(pad.motion)); - return params; - } - return params; -} - -class UDPTouch final : public Input::TouchDevice { -public: - explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::TouchStatus GetStatus() const override { - return client->GetTouchState(); - } - -private: - const std::string ip; - [[maybe_unused]] const u16 port; - [[maybe_unused]] const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPTouchFactory::UDPTouchFactory(std::shared_ptr client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr UDPTouchFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast(params.Get("port", 26760)); - const auto pad = static_cast(params.Get("pad_index", 0)); - - return std::make_unique(std::move(ip), port, pad, client.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h deleted file mode 100644 index ea3fd4175..000000000 --- a/src/input_common/udp/udp.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/frontend/input.h" -#include "input_common/udp/client.h" - -namespace InputCommon { - -/// A motion device factory that creates motion devices from udp clients -class UDPMotionFactory final : public Input::Factory { -public: - explicit UDPMotionFactory(std::shared_ptr client_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr client; - bool polling = false; -}; - -/// A touch device factory that creates touch devices from udp clients -class UDPTouchFactory final : public Input::Factory { -public: - explicit UDPTouchFactory(std::shared_ptr client_); - - std::unique_ptr Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr client; - bool polling = false; -}; - -} // namespace InputCommon -- cgit v1.2.3