diff --git a/SerialPrograms/Source/Controllers/ControllerConnection.cpp b/SerialPrograms/Source/Controllers/ControllerConnection.cpp index b37847d58d..f83b4fc0bb 100644 --- a/SerialPrograms/Source/Controllers/ControllerConnection.cpp +++ b/SerialPrograms/Source/Controllers/ControllerConnection.cpp @@ -34,12 +34,22 @@ std::string ControllerConnection::status_text() const{ } return str; } +std::string ControllerConnection::raw_status_text() const{ + ReadSpinLock lg(m_status_text_lock); + std::string str = m_raw_status_line0; + if (!str.empty() && !m_raw_status_line1.empty()){ + str += "\n"; + str += m_raw_status_line1; + } + return str; +} void ControllerConnection::set_status_line0(const std::string& text, Color color){ { WriteSpinLock lg(m_status_text_lock); m_status_line0 = html_color_text(text, color); + m_raw_status_line0 = text; } signal_status_text_changed(status_text()); } @@ -47,6 +57,7 @@ void ControllerConnection::set_status_line1(const std::string& text, Color color { WriteSpinLock lg(m_status_text_lock); m_status_line1 = html_color_text(text, color); + m_raw_status_line1 = text; } signal_status_text_changed(status_text()); } diff --git a/SerialPrograms/Source/Controllers/ControllerConnection.h b/SerialPrograms/Source/Controllers/ControllerConnection.h index 765ad542eb..e69252328d 100644 --- a/SerialPrograms/Source/Controllers/ControllerConnection.h +++ b/SerialPrograms/Source/Controllers/ControllerConnection.h @@ -60,7 +60,13 @@ class ControllerConnection : public CancellableScope{ } bool is_ready() const noexcept{ return status() == Status::READY; } Status status() const noexcept{ return m_status.load(std::memory_order_acquire); } + // The status shown in the GUI's controller panel, formatted as HTML: each line is + // colored with tags and the two lines are joined with
. std::string status_text() const; + // The same status as plain text, for non-GUI callers (e.g. the Python bindings): + // the text of each line as it was set, without colors, joined with a newline. + // e.g. "PABotBase2-Pico1W (UART) (2026062701)" + std::string raw_status_text() const; // It it not safe to call this until "is_ready()" is true. const std::vector& controller_list() noexcept{ @@ -95,8 +101,10 @@ class ControllerConnection : public CancellableScope{ private: mutable SpinLock m_status_text_lock; - std::string m_status_line0; + std::string m_status_line0; // HTML, for status_text() std::string m_status_line1; + std::string m_raw_status_line0; // plain text, for raw_status_text() + std::string m_raw_status_line1; ListenerSet m_status_listeners; }; diff --git a/SerialPrograms/Source/Integrations/PybindSwitchController.cpp b/SerialPrograms/Source/Integrations/PybindSwitchController.cpp index c17b21803f..77aae9d4a1 100644 --- a/SerialPrograms/Source/Integrations/PybindSwitchController.cpp +++ b/SerialPrograms/Source/Integrations/PybindSwitchController.cpp @@ -4,10 +4,11 @@ * */ +#include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Concurrency/Mutex.h" #include "Common/Cpp/Concurrency/ConditionVariable.h" +#include "Common/Cpp/Logging/GlobalLogger.h" #include "Common/Cpp/Logging/TaggedLogger.h" -#include "CommonFramework/Logging/Logger.h" #include "Controllers/ControllerConnection.h" #include "Controllers/PABotBase2/SerialPABotBase2_Descriptor.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" @@ -25,7 +26,7 @@ namespace NintendoSwitch{ class PybindSwitchProControllerInternal final : public ControllerConnection::StatusListener{ public: PybindSwitchProControllerInternal(const std::string& name) - : m_logger(global_logger_command_line(), "Pybind") + : m_logger(global_logger_raw(), "Pybind") , m_descriptor(name) , m_connection(m_descriptor.open_connection(m_logger)) { @@ -40,8 +41,7 @@ class PybindSwitchProControllerInternal final : public ControllerConnection::Sta m_cv.wait_for(lg, Milliseconds(timeout_millis), [this]{ return m_connected; }); - ProController* procon = m_procon.load(std::memory_order_relaxed); - return procon != nullptr && procon->is_ready(); + return controller_if_ready() != nullptr; } virtual void post_connection_ready(ControllerConnection& connection) override{ @@ -55,7 +55,11 @@ class PybindSwitchProControllerInternal final : public ControllerConnection::Sta ProController* procon = dynamic_cast(m_controller.get()); if (procon == nullptr){ // cout << "post_connection_ready() - incompatible" << endl; - m_connection->set_status_line1("Incompatible controller type.", COLOR_RED); + m_connection->set_status_line1( + "Incompatible controller type. Use the main program to set the device " + "to a Pro Controller or a wired controller.", + COLOR_RED + ); }else{ // cout << "post_connection_ready() - good" << endl; m_procon.store(procon, std::memory_order_release); @@ -68,8 +72,24 @@ class PybindSwitchProControllerInternal final : public ControllerConnection::Sta m_cv.notify_all(); } - ProController* controller(){ - return m_procon.load(std::memory_order_acquire); + ProController* controller_if_ready(){ + ProController* procon = m_procon.load(std::memory_order_acquire); + if (procon == nullptr || !procon->is_ready()){ + return nullptr; + } + return procon; + } + + // Return the current controller, or throw InvalidConnectionStateException if + // there is none. + ProController& controller(){ + ProController* procon = controller_if_ready(); + if (procon == nullptr){ + throw InvalidConnectionStateException( + "Controller is not ready: " + m_connection->status_text() + ); + } + return *procon; } @@ -103,21 +123,17 @@ bool PybindSwitchProController::wait_for_ready(uint64_t timeout_millis){ bool PybindSwitchProController::is_ready() const{ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - return false; - } - return controller->is_ready(); + return internal->controller_if_ready() != nullptr; } std::string PybindSwitchProController::current_status() const{ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - return internal->m_connection->status_text(); + return internal->m_connection->raw_status_text(); } void PybindSwitchProController::wait_for_all_requests(){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); + ProController* controller = internal->controller_if_ready(); if (controller == nullptr){ internal->m_logger.log("Controller is not ready.", COLOR_RED); return; @@ -126,21 +142,11 @@ void PybindSwitchProController::wait_for_all_requests(){ } void PybindSwitchProController::wait(uint64_t duration){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_nop(nullptr, Milliseconds(duration)); + internal->controller().issue_nop(nullptr, Milliseconds(duration)); } void PybindSwitchProController::push_button(uint64_t delay, uint64_t hold, uint64_t release, uint32_t bitfield){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_buttons( + internal->controller().issue_buttons( nullptr, Milliseconds(delay), Milliseconds(hold), @@ -150,12 +156,7 @@ void PybindSwitchProController::push_button(uint64_t delay, uint64_t hold, uint6 } void PybindSwitchProController::push_dpad(uint64_t delay, uint64_t hold, uint64_t release, uint8_t position){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_dpad( + internal->controller().issue_dpad( nullptr, Milliseconds(delay), Milliseconds(hold), @@ -165,12 +166,7 @@ void PybindSwitchProController::push_dpad(uint64_t delay, uint64_t hold, uint64_ } void PybindSwitchProController::push_left_joystick(uint64_t delay, uint64_t hold, uint64_t release, double x, double y){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_left_joystick( + internal->controller().issue_left_joystick( nullptr, Milliseconds(delay), Milliseconds(hold), @@ -180,12 +176,7 @@ void PybindSwitchProController::push_left_joystick(uint64_t delay, uint64_t hold } void PybindSwitchProController::push_right_joystick(uint64_t delay, uint64_t hold, uint64_t release, double x, double y){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_right_joystick( + internal->controller().issue_right_joystick( nullptr, Milliseconds(delay), Milliseconds(hold), @@ -201,12 +192,7 @@ void PybindSwitchProController::controller_state( double right_x, double right_y ){ PybindSwitchProControllerInternal* internal = (PybindSwitchProControllerInternal*)m_internals; - ProController* controller = internal->controller(); - if (controller == nullptr){ - internal->m_logger.log("Controller is not ready.", COLOR_RED); - return; - } - controller->issue_full_controller_state( + internal->controller().issue_full_controller_state( nullptr, true, Milliseconds(duration), diff --git a/SerialPrograms/Source/Integrations/PybindSwitchController.h b/SerialPrograms/Source/Integrations/PybindSwitchController.h index 0e6ea3abed..decbe6a252 100644 --- a/SerialPrograms/Source/Integrations/PybindSwitchController.h +++ b/SerialPrograms/Source/Integrations/PybindSwitchController.h @@ -42,8 +42,8 @@ class PybindSwitchProController{ // "cu.usbserial-0001" or "COM3". // The connection is established asynchronously; After the constructor, call // `wait_for_ready()` next to wait until it is ready. - // Log lines go to the command-line logger with tag "Pybind", which also prints - // them to stdout. + // Log lines go to the global logger (`global_logger_raw()`) with tag "Pybind", + // never to stdout. PybindSwitchProController(const std::string& port_name); ~PybindSwitchProController(); @@ -54,8 +54,8 @@ class PybindSwitchProController{ bool is_ready() const; - // The connection status text shown in the GUI (formatted as HTML), e.g. - // device name and firmware version, or the error message if the connection failed. + // The connection status text: device name and firmware version, + // or the error message if the connection failed. std::string current_status() const; // Block until every command queued so far has been executed by the device. @@ -63,8 +63,8 @@ class PybindSwitchProController{ void wait_for_all_requests(); public: - // Commands. If the controller is not ready, these log an error and do nothing. - // They block only if the device's command queue is full. + // Commands. These throw InvalidConnectionStateException if the controller is not + // ready, and block only if the device's command queue is full. // // `delay` is how long to wait before the next command may start, `hold` how long // the input is held, and `release` how long it must stay released before the same @@ -74,7 +74,7 @@ class PybindSwitchProController{ // Send a wait command to the controller. Nothing is pressed during the wait time. // duration: wait duration, milliseconds. - // If the controller is not ready, these log an error and do nothing. + // If the controller is not ready, it throws InvalidConnectionStateException. // The function will block only if the device's command queue is full. void wait(uint64_t duration); @@ -89,7 +89,7 @@ class PybindSwitchProController{ // // `delay = hold + release` runs commands back to back like `pbf_press_button()`; // `delay < hold` overlaps commands, e.g. to hold a button while moving a stick. - // If the controller is not ready, these log an error and do nothing. + // If the controller is not ready, it throws InvalidConnectionStateException. // The function will block only if the device's command queue is full. void push_button(uint64_t delay, uint64_t hold, uint64_t release, uint32_t bitfield); // Send a D-pad press command to the controller. It will press one or two D-pad buttons to @@ -102,7 +102,7 @@ class PybindSwitchProController{ // // `delay = hold + release` runs commands back to back like `pbf_press_button()`; // `delay < hold` overlaps commands, e.g. to hold a button while moving a stick. - // If the controller is not ready, these log an error and do nothing. + // If the controller is not ready, it throws InvalidConnectionStateException. // The function will block only if the device's command queue is full. void push_dpad(uint64_t delay, uint64_t hold, uint64_t release, uint8_t position); // Send a left joystick push command to the controller. @@ -114,7 +114,7 @@ class PybindSwitchProController{ // // `delay = hold + release` runs commands back to back like `pbf_press_button()`; // `delay < hold` overlaps commands, e.g. to hold a button while moving a stick. - // If the controller is not ready, these log an error and do nothing. + // If the controller is not ready, it throws InvalidConnectionStateException. // The function will block only if the device's command queue is full. void push_left_joystick(uint64_t delay, uint64_t hold, uint64_t release, double x, double y); // Send a right joystick push command to the controller. @@ -126,7 +126,7 @@ class PybindSwitchProController{ // // `delay = hold + release` runs commands back to back like `pbf_press_button()`; // `delay < hold` overlaps commands, e.g. to hold a button while moving a stick. - // If the controller is not ready, these log an error and do nothing. + // If the controller is not ready, it throws InvalidConnectionStateException. // The function will block only if the device's command queue is full. void push_right_joystick(uint64_t delay, uint64_t hold, uint64_t release, double x, double y);