From 6f979a5a9398225d11eeb3a39903ada60e808890 Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Mon, 15 Dec 2025 17:33:08 -0500 Subject: [PATCH 1/4] misc: some pinone debugging --- src/cab/out/pinone/PinOneAutoConfigurator.cpp | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/cab/out/pinone/PinOneAutoConfigurator.cpp b/src/cab/out/pinone/PinOneAutoConfigurator.cpp index de35ca4..18f65a5 100644 --- a/src/cab/out/pinone/PinOneAutoConfigurator.cpp +++ b/src/cab/out/pinone/PinOneAutoConfigurator.cpp @@ -84,13 +84,16 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) try { if (sp_get_port_by_name(portName, &port) != SP_OK) + { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: sp_get_port_by_name failed for {0}", std::string(portName))); return ""; + } - // Check if port file actually exists before trying to open it std::string portPath(portName); std::ifstream portFile(portPath); if (!portFile.good()) { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Port file does not exist: {0}", portPath)); sp_free_port(port); return ""; } @@ -98,11 +101,13 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) if (sp_open(port, SP_MODE_READ_WRITE) != SP_OK) { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: sp_open failed for {0}", std::string(portName))); sp_free_port(port); return ""; } - // Set very short timeouts to prevent hanging + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Opened port {0}, configuring...", std::string(portName))); + sp_set_baudrate(port, 2000000); sp_set_bits(port, 8); sp_set_parity(port, SP_PARITY_NONE); @@ -118,24 +123,47 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) std::this_thread::sleep_for(std::chrono::milliseconds(100)); uint8_t command[] = { 0, 251, 0, 0, 0, 0, 0, 0, 0 }; + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Sending detection command to {0}", std::string(portName))); sp_blocking_write(port, command, 9, 100); char buffer[256]; int bytesRead = sp_blocking_read(port, buffer, sizeof(buffer) - 1, 100); + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Read {0} bytes from {1}", std::to_string(bytesRead), std::string(portName))); + if (bytesRead > 0) { buffer[bytesRead] = '\0'; std::string result(buffer); + + std::string hexDump; + for (int i = 0; i < bytesRead; i++) + hexDump += StringExtensions::Build("{0} ", std::to_string((unsigned char)buffer[i])); + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Raw bytes: {0}", hexDump)); + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Response string: [{0}]", result)); + + while (!result.empty() && (result.back() == '\r' || result.back() == '\n')) + result.pop_back(); + + Log::Write(StringExtensions::Build("PinOne TestSerialPort: After stripping line endings: [{0}]", result)); + if (result == "DEBUG,CSD Board Connected") { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: SUCCESS - PinOne detected on {0}", std::string(portName))); sp_close(port); sp_free_port(port); return std::string(portName); } + else + { + Log::Write("PinOne TestSerialPort: Response does not match expected string"); + } + } + else + { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: No response from {0}", std::string(portName))); } - // Force flush and set non-blocking mode before closing to prevent hang sp_flush(port, SP_BUF_BOTH); sp_set_rts(port, SP_RTS_OFF); sp_set_dtr(port, SP_DTR_OFF); @@ -145,6 +173,7 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) } catch (...) { + Log::Write(StringExtensions::Build("PinOne TestSerialPort: Exception while testing {0}", std::string(portName))); if (port) { try @@ -154,7 +183,7 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) sp_set_dtr(port, SP_DTR_OFF); } catch (...) - { /* Ignore errors in cleanup */ + { } sp_close(port); @@ -162,7 +191,6 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) } } - // Small delay to ensure port cleanup completes std::this_thread::sleep_for(std::chrono::milliseconds(10)); return ""; @@ -170,24 +198,33 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) std::string PinOneAutoConfigurator::GetDevice() { + Log::Write("PinOne GetDevice: Starting serial port enumeration"); + struct sp_port** portList; if (sp_list_ports(&portList) != SP_OK) + { + Log::Write("PinOne GetDevice: sp_list_ports failed"); return ""; + } int portCount = 0; while (portList[portCount] != nullptr) portCount++; + Log::Write(StringExtensions::Build("PinOne GetDevice: Found {0} serial ports", std::to_string(portCount))); + for (int i = 0; portList[i] != nullptr; i++) { char* portName = sp_get_port_name(portList[i]); if (!portName) continue; - // Test each port synchronously with timeouts matching C# version (100ms) + Log::Write(StringExtensions::Build("PinOne GetDevice: Testing port {0}", std::string(portName))); + std::string result = TestSerialPort(portName); if (!result.empty()) { + Log::Write(StringExtensions::Build("PinOne GetDevice: Found PinOne on {0}", result)); sp_free_port_list(portList); return result; } @@ -195,11 +232,18 @@ std::string PinOneAutoConfigurator::GetDevice() sp_free_port_list(portList); + Log::Write("PinOne GetDevice: No PinOne found on serial ports, trying named pipe server"); + std::string comPort = ""; PinOneCommunication communication(""); if (communication.ConnectToServer()) { comPort = communication.GetCOMPort(); + Log::Write(StringExtensions::Build("PinOne GetDevice: Got COM port from server: {0}", comPort)); + } + else + { + Log::Write("PinOne GetDevice: Named pipe server connection failed"); } return comPort; From d28935cde9f1c2171a723115e17ed180834c31bd Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Tue, 16 Dec 2025 14:27:58 -0500 Subject: [PATCH 2/4] misc: more pinone fixes --- src/general/StringExtensions.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/general/StringExtensions.cpp b/src/general/StringExtensions.cpp index 8127252..6032cab 100644 --- a/src/general/StringExtensions.cpp +++ b/src/general/StringExtensions.cpp @@ -176,24 +176,25 @@ std::string ReplaceArgument(const std::string& format, int argIndex, const std:: std::string result = format; std::string pattern = "{" + std::to_string(argIndex); - size_t pos = result.find(pattern); - if (pos != std::string::npos) + size_t pos = 0; + while ((pos = result.find(pattern, pos)) != std::string::npos) { size_t endPos = result.find("}", pos); - if (endPos != std::string::npos) - { - std::string placeholder = result.substr(pos, endPos - pos + 1); - std::string formattedArg = arg; + if (endPos == std::string::npos) + break; - size_t colonPos = placeholder.find(":"); - if (colonPos != std::string::npos) - { - std::string formatSpec = placeholder.substr(colonPos + 1, placeholder.length() - colonPos - 2); - formattedArg = FormatArgument(arg, formatSpec); - } + std::string placeholder = result.substr(pos, endPos - pos + 1); + std::string formattedArg = arg; - result.replace(pos, endPos - pos + 1, formattedArg); + size_t colonPos = placeholder.find(":"); + if (colonPos != std::string::npos) + { + std::string formatSpec = placeholder.substr(colonPos + 1, placeholder.length() - colonPos - 2); + formattedArg = FormatArgument(arg, formatSpec); } + + result.replace(pos, endPos - pos + 1, formattedArg); + pos += formattedArg.length(); } return result; } From 74dc29b839c133eee3fe327f2c0f80f33a9aa65f Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Wed, 17 Dec 2025 06:57:33 -0500 Subject: [PATCH 3/4] pinone: fix auto configuration --- src/cab/out/pinone/PinOneAutoConfigurator.cpp | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/src/cab/out/pinone/PinOneAutoConfigurator.cpp b/src/cab/out/pinone/PinOneAutoConfigurator.cpp index 18f65a5..558d99b 100644 --- a/src/cab/out/pinone/PinOneAutoConfigurator.cpp +++ b/src/cab/out/pinone/PinOneAutoConfigurator.cpp @@ -84,16 +84,12 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) try { if (sp_get_port_by_name(portName, &port) != SP_OK) - { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: sp_get_port_by_name failed for {0}", std::string(portName))); return ""; - } std::string portPath(portName); std::ifstream portFile(portPath); if (!portFile.good()) { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Port file does not exist: {0}", portPath)); sp_free_port(port); return ""; } @@ -101,13 +97,10 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) if (sp_open(port, SP_MODE_READ_WRITE) != SP_OK) { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: sp_open failed for {0}", std::string(portName))); sp_free_port(port); return ""; } - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Opened port {0}, configuring...", std::string(portName))); - sp_set_baudrate(port, 2000000); sp_set_bits(port, 8); sp_set_parity(port, SP_PARITY_NONE); @@ -123,45 +116,25 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) std::this_thread::sleep_for(std::chrono::milliseconds(100)); uint8_t command[] = { 0, 251, 0, 0, 0, 0, 0, 0, 0 }; - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Sending detection command to {0}", std::string(portName))); sp_blocking_write(port, command, 9, 100); char buffer[256]; int bytesRead = sp_blocking_read(port, buffer, sizeof(buffer) - 1, 100); - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Read {0} bytes from {1}", std::to_string(bytesRead), std::string(portName))); - if (bytesRead > 0) { buffer[bytesRead] = '\0'; std::string result(buffer); - std::string hexDump; - for (int i = 0; i < bytesRead; i++) - hexDump += StringExtensions::Build("{0} ", std::to_string((unsigned char)buffer[i])); - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Raw bytes: {0}", hexDump)); - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Response string: [{0}]", result)); - while (!result.empty() && (result.back() == '\r' || result.back() == '\n')) result.pop_back(); - Log::Write(StringExtensions::Build("PinOne TestSerialPort: After stripping line endings: [{0}]", result)); - if (result == "DEBUG,CSD Board Connected") { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: SUCCESS - PinOne detected on {0}", std::string(portName))); sp_close(port); sp_free_port(port); return std::string(portName); } - else - { - Log::Write("PinOne TestSerialPort: Response does not match expected string"); - } - } - else - { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: No response from {0}", std::string(portName))); } sp_flush(port, SP_BUF_BOTH); @@ -173,7 +146,6 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) } catch (...) { - Log::Write(StringExtensions::Build("PinOne TestSerialPort: Exception while testing {0}", std::string(portName))); if (port) { try @@ -198,20 +170,9 @@ std::string PinOneAutoConfigurator::TestSerialPort(const char* portName) std::string PinOneAutoConfigurator::GetDevice() { - Log::Write("PinOne GetDevice: Starting serial port enumeration"); - struct sp_port** portList; if (sp_list_ports(&portList) != SP_OK) - { - Log::Write("PinOne GetDevice: sp_list_ports failed"); return ""; - } - - int portCount = 0; - while (portList[portCount] != nullptr) - portCount++; - - Log::Write(StringExtensions::Build("PinOne GetDevice: Found {0} serial ports", std::to_string(portCount))); for (int i = 0; portList[i] != nullptr; i++) { @@ -219,12 +180,9 @@ std::string PinOneAutoConfigurator::GetDevice() if (!portName) continue; - Log::Write(StringExtensions::Build("PinOne GetDevice: Testing port {0}", std::string(portName))); - std::string result = TestSerialPort(portName); if (!result.empty()) { - Log::Write(StringExtensions::Build("PinOne GetDevice: Found PinOne on {0}", result)); sp_free_port_list(portList); return result; } @@ -232,19 +190,10 @@ std::string PinOneAutoConfigurator::GetDevice() sp_free_port_list(portList); - Log::Write("PinOne GetDevice: No PinOne found on serial ports, trying named pipe server"); - std::string comPort = ""; PinOneCommunication communication(""); if (communication.ConnectToServer()) - { comPort = communication.GetCOMPort(); - Log::Write(StringExtensions::Build("PinOne GetDevice: Got COM port from server: {0}", comPort)); - } - else - { - Log::Write("PinOne GetDevice: Named pipe server connection failed"); - } return comPort; } From 5734b14ddf3f2d1dcd64f67b086ebd9c6ebec83e Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Wed, 17 Dec 2025 07:05:07 -0500 Subject: [PATCH 4/4] misc: add PinOne url to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b51804..437d7d5 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ This library is currently used by [Visual Pinball Standalone](https://github.com - **[WemosD1MPStripController](https://github.com/aetios50/PincabLedStrip)** - Wemos D1 Mini Pro based WS2812 LED strip controller - **[PacLED64](https://www.ultimarc.com/output/led-and-output-controllers/pacled64/)** - Ultimarc's 64-output LED controller with PWM support - **[LedWiz](https://groovygamegear.com/webstore/index.php?main_page=product_info&products_id=239)** - LED-Wiz's 32-port USB compatible lighting and output controller +- **[PinOne](https://www.clevelandsoftwaredesign.com/pinball-parts/pinone)** - Cleveland Software Design controller with 63 outputs ### **Implemented & Ready To Test** - **DudesCab** - RP2040-based controller with 128 PWM outputs - **ArtNet/DMX** - Professional lighting control via Ethernet (all platforms) - **PinControl** - Arduino-based controller with 10 outputs -- **PinOne** - Cleveland Software Design controller with 63 outputs - **FTDI Controllers** - FT245R bitbang controllers - **WS2811/WS2812 LED Strips** - Addressable LED strip support - **PAC Controllers** (PacDrive, PacUIO) - Cross-platform libusb implementation