Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions SerialPrograms/Source/Controllers/ControllerConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,30 @@ 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());
}
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());
}
Expand Down
10 changes: 9 additions & 1 deletion SerialPrograms/Source/Controllers/ControllerConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <font> tags and the two lines are joined with <br>.
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<ControllerType>& controller_list() noexcept{
Expand Down Expand Up @@ -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<StatusListener> m_status_listeners;
};

Expand Down
86 changes: 36 additions & 50 deletions SerialPrograms/Source/Integrations/PybindSwitchController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
{
Expand All @@ -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{
Expand All @@ -55,7 +55,11 @@ class PybindSwitchProControllerInternal final : public ControllerConnection::Sta
ProController* procon = dynamic_cast<ProController*>(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);
Expand All @@ -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;
}


Expand Down Expand Up @@ -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;
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand Down
22 changes: 11 additions & 11 deletions SerialPrograms/Source/Integrations/PybindSwitchController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -54,17 +54,17 @@ 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.
// Returns immediately if the controller is not ready.
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
Expand All @@ -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);

Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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);

Expand Down
Loading