From 00a30eaa1cd82fc39b40d5a5ea4ca9bf085640ee Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Sat, 20 Sep 2025 10:11:13 -0400 Subject: [PATCH] ledwiz: fix ledwiz protocol, and add ledwiz and pacdrive64 test tools --- .github/workflows/libdof.yml | 6 + CLAUDE.md | 1 + CMakeLists.txt | 72 +++++- include/DOF/DOF.h | 2 +- src/cab/out/lw/LedWiz.cpp | 40 ++- src/cab/out/lw/LedWiz.h | 2 +- src/{test.cpp => tools/dof_test.cpp} | 0 src/tools/ledwiz_test.cpp | 364 +++++++++++++++++++++++++++ src/tools/pacled64_test.cpp | 245 ++++++++++++++++++ 9 files changed, 702 insertions(+), 30 deletions(-) rename src/{test.cpp => tools/dof_test.cpp} (100%) create mode 100644 src/tools/ledwiz_test.cpp create mode 100644 src/tools/pacled64_test.cpp diff --git a/.github/workflows/libdof.yml b/.github/workflows/libdof.yml index 23a7de9..4605cd0 100644 --- a/.github/workflows/libdof.yml +++ b/.github/workflows/libdof.yml @@ -96,6 +96,8 @@ jobs: cp build/Release/dof_static.lib tmp cp build/Release/dof_test_s.exe tmp cp build/Release/dof_test.exe tmp + cp build/Release/ledwiz_test.exe tmp + cp build/Release/pacled64_test.exe tmp else ARTIFACT_PATH="libdof-${{ needs.version.outputs.tag }}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" if [[ "${{ matrix.platform }}" == "macos" ]]; then @@ -103,11 +105,15 @@ jobs: cp -a build/*.dylib tmp cp build/dof_test_s tmp cp build/dof_test tmp + cp build/ledwiz_test tmp + cp build/pacled64_test tmp elif [[ "${{ matrix.platform }}" == "linux" ]]; then cp build/libdof.a tmp cp -a build/*.{so,so.*} tmp cp build/dof_test_s tmp cp build/dof_test tmp + cp build/ledwiz_test tmp + cp build/pacled64_test tmp elif [[ "${{ matrix.platform }}" == "ios" || "${{ matrix.platform }}" == "ios-simulator" || "${{ matrix.platform }}" == "tvos" ]]; then cp build/libdof.a tmp cp -a build/*.dylib tmp diff --git a/CLAUDE.md b/CLAUDE.md index 699c49d..dcd063e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,7 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden - **DudesCab**: hidapi HID multi-part messages - **DMX**: UDP ArtNet broadcast port 6454 - **PinOne**: Named pipes with Base64 encoding over text +- **LedWiz**: HIDAPI 9-byte packets (SBA: `[0x00, 0x40, bank0-3, globalSpeed, 0, 0]`, PBA: 4 chunks of `[0x00, bright1-8]` with 0-49 PWM range, ZebsBoards VID 0x20A0 supported) - **PAC Controllers**: libusb control transfers (PacLed64: individual LED intensity with 0-based numbering, PacDrive: 16-bit LED bitmask, PacUIO: via PacDriveSingleton) ### Linux Serial Port Handling (Critical) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2e46f2..2f9a026 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -333,7 +333,7 @@ if(BUILD_SHARED) if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "macos" OR PLATFORM STREQUAL "linux") add_executable(dof_test - src/test.cpp + src/tools/dof_test.cpp ) target_link_libraries(dof_test PUBLIC dof_shared) @@ -399,7 +399,7 @@ if(BUILD_STATIC) if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "macos" OR PLATFORM STREQUAL "linux") add_executable(dof_test_s - src/test.cpp + src/tools/dof_test.cpp ) if(PLATFORM STREQUAL "win") @@ -462,3 +462,71 @@ if(BUILD_STATIC) endif() endif() endif() + +if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "macos" OR PLATFORM STREQUAL "linux") + add_executable(ledwiz_test + src/tools/ledwiz_test.cpp + ) + + target_include_directories(ledwiz_test PRIVATE + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/third-party/include + ) + + if(PLATFORM STREQUAL "win") + target_link_directories(ledwiz_test PUBLIC + third-party/build-libs/${PLATFORM}/${ARCH} + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + if(ARCH STREQUAL "x64") + target_link_libraries(ledwiz_test PUBLIC hidapi64) + else() + target_link_libraries(ledwiz_test PUBLIC hidapi) + endif() + elseif(PLATFORM STREQUAL "macos") + target_link_directories(ledwiz_test PUBLIC + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + target_link_libraries(ledwiz_test PUBLIC hidapi) + elseif(PLATFORM STREQUAL "linux") + target_link_directories(ledwiz_test PUBLIC + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + target_link_libraries(ledwiz_test PUBLIC hidapi-hidraw) + endif() + +endif() + +if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "macos" OR PLATFORM STREQUAL "linux") + add_executable(pacled64_test + src/tools/pacled64_test.cpp + ) + + target_include_directories(pacled64_test PRIVATE + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/third-party/include + ) + + if(PLATFORM STREQUAL "win") + target_link_directories(pacled64_test PUBLIC + third-party/build-libs/${PLATFORM}/${ARCH} + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + if(ARCH STREQUAL "x64") + target_link_libraries(pacled64_test PUBLIC libusb64-1.0) + else() + target_link_libraries(pacled64_test PUBLIC libusb-1.0) + endif() + elseif(PLATFORM STREQUAL "macos") + target_link_directories(pacled64_test PUBLIC + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + target_link_libraries(pacled64_test PUBLIC usb-1.0) + elseif(PLATFORM STREQUAL "linux") + target_link_directories(pacled64_test PUBLIC + third-party/runtime-libs/${PLATFORM}/${ARCH} + ) + target_link_libraries(pacled64_test PUBLIC usb-1.0) + endif() + +endif() diff --git a/include/DOF/DOF.h b/include/DOF/DOF.h index 7676a25..ee75dbe 100644 --- a/include/DOF/DOF.h +++ b/include/DOF/DOF.h @@ -2,7 +2,7 @@ #define LIBDOF_VERSION_MAJOR 0 // X Digits #define LIBDOF_VERSION_MINOR 4 // Max 2 Digits -#define LIBDOF_VERSION_PATCH 4 // Max 2 Digits +#define LIBDOF_VERSION_PATCH 5 // Max 2 Digits #define _LIBDOF_STR(x) #x #define LIBDOF_STR(x) _LIBDOF_STR(x) diff --git a/src/cab/out/lw/LedWiz.cpp b/src/cab/out/lw/LedWiz.cpp index e0b87a1..2a98198 100644 --- a/src/cab/out/lw/LedWiz.cpp +++ b/src/cab/out/lw/LedWiz.cpp @@ -136,7 +136,7 @@ void LedWiz::AllOff() if (m_fp) { std::vector buf = { 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - WriteUSB("ALL OFF", buf); + WriteUSB(buf); } } @@ -219,49 +219,37 @@ void LedWiz::UpdateOutputs(const std::vector& newOutputValues) } } - WriteUSB("SBA", sbaCmd); + WriteUSB(sbaCmd); - for (int startOutput = 0; startOutput < 32; startOutput += 8) + for (int ofs = 0; ofs < 32; ofs += 8) { - std::vector pbaCmd = { 0x00, static_cast(0x20 + (startOutput / 8)), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + std::vector pbaCmd = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - for (int i = 0; i < 8 && (startOutput + i) < static_cast(newOutputValues.size()); ++i) + for (int i = 0; i < 8; ++i) { - pbaCmd[2 + i] = static_cast((newOutputValues[startOutput + i] * 48) / 255); + int outputIndex = ofs + i; + if (outputIndex < static_cast(newOutputValues.size())) + { + pbaCmd[1 + i] = static_cast((newOutputValues[outputIndex] * 49) / 255); + } } - WriteUSB(StringExtensions::Build("PBA{0}", std::to_string(startOutput / 8)), pbaCmd); + WriteUSB(pbaCmd); } m_oldOutputValues = newOutputValues; } } -bool LedWiz::WriteUSB(const std::string& desc, const std::vector& data) +bool LedWiz::WriteUSB(const std::vector& data) { if (!m_fp || data.empty()) return false; - std::vector hexBytes; - for (uint8_t b : data) - { - char hexStr[4]; - snprintf(hexStr, sizeof(hexStr), "%02X", b); - hexBytes.push_back(std::string(hexStr)); - } - std::string hexString; - for (size_t i = 0; i < hexBytes.size(); ++i) - { - if (i > 0) - hexString += ", "; - hexString += hexBytes[i]; - } - Log::Instrumentation("LedWiz", StringExtensions::Build("LedWiz Write {0} : {1}", desc, hexString)); - int result = hid_write(m_fp, data.data(), data.size()); if (result < 0) { - Log::Write(StringExtensions::Build("LedWiz USB write failed for {0}: {1}", desc, hid_error(m_fp) ? reinterpret_cast(hid_error(m_fp)) : "Unknown error")); + Log::Write(StringExtensions::Build("LedWiz {0} WriteUSB failed after retries", std::to_string(m_number))); return false; } @@ -297,7 +285,7 @@ void LedWiz::FindDevices() } else if (curDev->vendor_id == 0x20A0) { - std::regex zebsPattern("(?i)zebsboards"); + std::regex zebsPattern("zebsboards", std::regex::ECMAScript | std::regex::icase); if (std::regex_search(manufacturerName, zebsPattern)) { ok = true; diff --git a/src/cab/out/lw/LedWiz.h b/src/cab/out/lw/LedWiz.h index 2a460db..0f071b9 100644 --- a/src/cab/out/lw/LedWiz.h +++ b/src/cab/out/lw/LedWiz.h @@ -57,7 +57,7 @@ class LedWiz : public OutputControllerCompleteBase static std::string GetDeviceProductName(hid_device_info* dev); static std::string GetDeviceManufacturerName(hid_device_info* dev); - bool WriteUSB(const std::string& desc, const std::vector& data); + bool WriteUSB(const std::vector& data); int m_number; int m_minCommandIntervalMs; diff --git a/src/test.cpp b/src/tools/dof_test.cpp similarity index 100% rename from src/test.cpp rename to src/tools/dof_test.cpp diff --git a/src/tools/ledwiz_test.cpp b/src/tools/ledwiz_test.cpp new file mode 100644 index 0000000..67507f0 --- /dev/null +++ b/src/tools/ledwiz_test.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint16_t LEDWIZ_VENDOR_ID = 0xFAFA; +const uint16_t ZEBSBOARDS_VENDOR_ID = 0x20A0; + +struct LedWizDevice +{ + int unitNo; + std::string path; + std::string productName; + uint16_t vendorId; + uint16_t productId; +}; + +class LedWizDirectTest +{ +private: + hid_device* m_deviceHandle; + std::vector m_outputValues; + std::vector m_oldOutputValues; + LedWizDevice m_device; + +public: + LedWizDirectTest() + : m_deviceHandle(nullptr) + { + m_outputValues.resize(32, 0); + m_oldOutputValues.resize(32, 255); // Initialize to 255 to match C# exactly + } + + ~LedWizDirectTest() { Disconnect(); } + + std::vector FindDevices() + { + std::vector devices; + + if (hid_init() != 0) + { + std::cout << "ERROR: Failed to initialize HIDAPI" << std::endl; + return devices; + } + + hid_device_info* devs = hid_enumerate(0x0000, 0x0000); + hid_device_info* curDev = devs; + + while (curDev) + { + std::string productName; + std::string manufacturerName; + + if (curDev->product_string) + { + std::wstring wproduct(curDev->product_string); + productName = std::string(wproduct.begin(), wproduct.end()); + } + + if (curDev->manufacturer_string) + { + std::wstring wmanufacturer(curDev->manufacturer_string); + manufacturerName = std::string(wmanufacturer.begin(), wmanufacturer.end()); + } + + + bool ok = false; + std::string okBecause; + + if (curDev->vendor_id == LEDWIZ_VENDOR_ID) + { + ok = true; + okBecause = "recognized by LedWiz vendor ID"; + } + else if (curDev->vendor_id == ZEBSBOARDS_VENDOR_ID) + { + std::regex zebsPattern("zebsboards", std::regex::ECMAScript | std::regex::icase); + if (std::regex_search(manufacturerName, zebsPattern)) + { + ok = true; + okBecause = "recognized by ZebsBoards manufacturer"; + } + } + + if (ok) + { + int unitNo = (curDev->product_id & 0x0F) + 1; + if (unitNo < 1 || unitNo > 16) + unitNo = 1; + + std::cout << "Found LedWiz device: VID:0x" << std::hex << curDev->vendor_id << " PID:0x" << curDev->product_id << std::dec << " Unit:" << unitNo << " (" << okBecause << ")" + << std::endl; + + LedWizDevice device; + device.unitNo = unitNo; + device.path = curDev->path ? curDev->path : ""; + device.productName = productName; + device.vendorId = curDev->vendor_id; + device.productId = curDev->product_id; + devices.push_back(device); + } + + curDev = curDev->next; + } + + hid_free_enumeration(devs); + return devices; + } + + bool Connect(const LedWizDevice& device) + { + m_device = device; + + m_deviceHandle = hid_open_path(device.path.c_str()); + if (!m_deviceHandle) + { + std::cout << "ERROR: Failed to open LedWiz device unit " << device.unitNo << std::endl; + return false; + } + + std::cout << "Successfully connected to LedWiz unit " << device.unitNo << std::endl; + return true; + } + + void Disconnect() + { + if (m_deviceHandle) + { + TurnOffAllOutputs(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + hid_close(m_deviceHandle); + m_deviceHandle = nullptr; + } + } + + bool WriteUSB(const std::vector& data) + { + if (!m_deviceHandle || data.empty()) + return false; + + int result = hid_write(m_deviceHandle, data.data(), data.size()); + if (result < 0) + { + std::cout << "ERROR: LedWiz WriteUSB failed" << std::endl; + return false; + } + + return true; + } + + bool UpdateOutputs() + { + bool hasChanges = false; + for (int i = 0; i < 32; ++i) + { + if (m_outputValues[i] != m_oldOutputValues[i]) + { + hasChanges = true; + break; + } + } + + if (!hasChanges) + return true; + + std::vector sbaCmd = { 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + for (int i = 0; i < 32; ++i) + { + int byteIndex = 2 + (i / 8); + int bitIndex = i % 8; + + if (m_outputValues[i] > 127) + { + sbaCmd[byteIndex] |= (1 << bitIndex); + } + } + + if (!WriteUSB(sbaCmd)) + return false; + + for (int ofs = 0; ofs < 32; ofs += 8) + { + std::vector pbaCmd = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + for (int i = 0; i < 8; ++i) + { + int outputIndex = ofs + i; + pbaCmd[1 + i] = static_cast((m_outputValues[outputIndex] * 49) / 255); + } + + if (!WriteUSB(pbaCmd)) + return false; + } + + m_oldOutputValues = m_outputValues; + return true; + } + + void SetOutput(int outputIndex, uint8_t intensity) + { + if (outputIndex >= 0 && outputIndex < 32) + { + m_outputValues[outputIndex] = intensity; + } + } + + void TurnOffAllOutputs() + { + std::fill(m_outputValues.begin(), m_outputValues.end(), 0); + UpdateOutputs(); + } + + void RunTest() + { + std::cout << "\nStarting LedWiz output test..." << std::endl; + std::cout << "Controls:" << std::endl; + std::cout << " [Enter] or 'n' = Next output" << std::endl; + std::cout << " 'r' = Repeat current output" << std::endl; + std::cout << " 'q' = Quit" << std::endl; + std::cout << "\nPress Enter to start...\n"; + std::cin.ignore(); // Clear any pending input + std::cin.get(); // Wait for initial Enter + + int outputIndex = 0; + while (outputIndex < 32) + { + std::cout << "\nTesting Output " << (outputIndex + 1) << " (0-based index: " << outputIndex << ")... " << std::flush; + + SetOutput(outputIndex, 255); + if (!UpdateOutputs()) + { + std::cout << "FAILED" << std::endl; + outputIndex++; + continue; + } + + std::cout << "ON... " << std::flush; + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + SetOutput(outputIndex, 0); + if (!UpdateOutputs()) + { + std::cout << "FAILED TO TURN OFF" << std::endl; + outputIndex++; + continue; + } + + std::cout << "OFF" << std::endl; + std::cout << "Press Enter/n=next, r=repeat, q=quit: " << std::flush; + + std::string input; + std::getline(std::cin, input); + + if (!input.empty()) + { + char firstChar = std::tolower(input[0]); + if (firstChar == 'q') + { + std::cout << "Quitting test..." << std::endl; + break; + } + else if (firstChar == 'r') + { + std::cout << "Repeating Output " << (outputIndex + 1) << std::endl; + continue; + } + } + + outputIndex++; + } + + TurnOffAllOutputs(); + + std::cout << "\nTest completed!" << std::endl; + std::cout << "All outputs should now be OFF" << std::endl; + } +}; + +int main(int argc, char* argv[]) +{ + std::cout << "LedWiz Direct HID Test Program" << std::endl; + std::cout << "==============================" << std::endl; + std::cout << "This program directly communicates with LedWiz devices via HID" << std::endl; + std::cout << "Supports both original LedWiz (VID:0xFAFA) and ZebsBoards (VID:0x20A0)" << std::endl; + + LedWizDirectTest* tester = nullptr; + + try + { + tester = new LedWizDirectTest(); + + std::vector devices = tester->FindDevices(); + + if (devices.empty()) + { + std::cout << "\nNo LedWiz devices found!" << std::endl; + std::cout << "\nTroubleshooting:" << std::endl; + std::cout << "1. Check USB connection" << std::endl; + std::cout << "2. Verify device permissions (you may need to run as root)" << std::endl; + std::cout << "3. Make sure LedWiz is properly powered" << std::endl; + std::cout << "4. Check for LedWiz VID:0xFAFA or ZebsBoards VID:0x20A0" << std::endl; + delete tester; + return 1; + } + + LedWizDevice selectedDevice; + if (devices.size() == 1) + { + selectedDevice = devices[0]; + std::cout << "\nUsing the only LedWiz device found (Unit " << selectedDevice.unitNo << ")" << std::endl; + } + else + { + std::cout << "\nMultiple LedWiz devices found:" << std::endl; + for (size_t i = 0; i < devices.size(); ++i) + { + std::cout << (i + 1) << ". Unit " << devices[i].unitNo << " (VID:0x" << std::hex << devices[i].vendorId << " PID:0x" << devices[i].productId << std::dec << ")" << std::endl; + } + + int choice = 0; + while (choice < 1 || choice > static_cast(devices.size())) + { + std::cout << "Select device (1-" << devices.size() << "): "; + std::cin >> choice; + } + selectedDevice = devices[choice - 1]; + } + + if (!tester->Connect(selectedDevice)) + { + delete tester; + return 1; + } + + tester->RunTest(); + delete tester; + } + catch (const std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; + if (tester) + delete tester; + return 1; + } + catch (...) + { + std::cout << "ERROR: Unknown exception occurred" << std::endl; + if (tester) + delete tester; + return 1; + } + + hid_exit(); + return 0; +} \ No newline at end of file diff --git a/src/tools/pacled64_test.cpp b/src/tools/pacled64_test.cpp new file mode 100644 index 0000000..50f329b --- /dev/null +++ b/src/tools/pacled64_test.cpp @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include + +const uint16_t PACLED64_VENDOR_ID = 0xD209; +const uint16_t PACLED64_PRODUCT_ID = 0x1401; // Updated to match your device + +class PacLed64DirectTest +{ +private: + libusb_context* m_context; + libusb_device_handle* m_deviceHandle; + std::vector m_ledValues; + +public: + PacLed64DirectTest() + : m_context(nullptr) + , m_deviceHandle(nullptr) + { + m_ledValues.resize(64, 0); + } + + ~PacLed64DirectTest() { Disconnect(); } + + bool Connect() + { + int result = libusb_init(&m_context); + if (result < 0) + { + std::cout << "ERROR: Failed to initialize libusb: " << libusb_error_name(result) << std::endl; + return false; + } + + m_deviceHandle = libusb_open_device_with_vid_pid(m_context, PACLED64_VENDOR_ID, PACLED64_PRODUCT_ID); + if (!m_deviceHandle) + { + std::cout << "ERROR: PacLed64 device not found (VID:0x" << std::hex << PACLED64_VENDOR_ID << " PID:0x" << PACLED64_PRODUCT_ID << std::dec << ")" << std::endl; + libusb_exit(m_context); + return false; + } + + if (libusb_kernel_driver_active(m_deviceHandle, 0) == 1) + { + if (libusb_detach_kernel_driver(m_deviceHandle, 0) != 0) + { + std::cout << "WARNING: Could not detach kernel driver" << std::endl; + } + } + + result = libusb_claim_interface(m_deviceHandle, 0); + if (result < 0) + { + std::cout << "ERROR: Failed to claim interface: " << libusb_error_name(result) << std::endl; + libusb_close(m_deviceHandle); + libusb_exit(m_context); + return false; + } + + std::cout << "Successfully connected to PacLed64" << std::endl; + return true; + } + + void Disconnect() + { + if (m_deviceHandle) + { + TurnOffAllLeds(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + libusb_release_interface(m_deviceHandle, 0); + libusb_close(m_deviceHandle); + m_deviceHandle = nullptr; + } + + if (m_context) + { + libusb_exit(m_context); + m_context = nullptr; + } + } + + bool SendLedUpdate() + { + if (!m_deviceHandle) + return false; + + + for (int i = 0; i < 64; i++) + { + uint8_t data[2] = { static_cast(i), m_ledValues[i] }; + + int result = libusb_control_transfer(m_deviceHandle, + 0x21, // bmRequestType + 9, // bRequest + 0x0200, // wValue + 0, // wIndex + data, + 2, // wLength + 2000 // timeout + ); + + if (result < 0) + { + std::cout << "ERROR: Failed to send LED command for LED " << i << ": " << libusb_error_name(result) << std::endl; + return false; + } + } + + return true; + } + + void SetLed(int ledIndex, uint8_t intensity) + { + if (ledIndex >= 0 && ledIndex < 64) + { + m_ledValues[ledIndex] = intensity; + } + } + + void TurnOffAllLeds() + { + std::fill(m_ledValues.begin(), m_ledValues.end(), 0); + SendLedUpdate(); + } + + char GetUserInput() + { + char ch; + std::cin >> ch; + return std::tolower(ch); + } + + void RunTest() + { + std::cout << "\nStarting PacLed64 LED test..." << std::endl; + std::cout << "Controls:" << std::endl; + std::cout << " [Enter] or 'n' = Next LED" << std::endl; + std::cout << " 'r' = Repeat current LED" << std::endl; + std::cout << " 'q' = Quit" << std::endl; + std::cout << "\nPress Enter to start...\n"; + std::cin.get(); // Wait for initial Enter + + int ledIndex = 0; + while (ledIndex < 64) + { + std::cout << "\nTesting LED " << (ledIndex + 1) << " (0-based index: " << ledIndex << ")... " << std::flush; + + SetLed(ledIndex, 255); + if (!SendLedUpdate()) + { + std::cout << "FAILED" << std::endl; + ledIndex++; + continue; + } + + std::cout << "ON... " << std::flush; + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + SetLed(ledIndex, 0); + if (!SendLedUpdate()) + { + std::cout << "FAILED TO TURN OFF" << std::endl; + ledIndex++; + continue; + } + + std::cout << "OFF" << std::endl; + std::cout << "Press Enter/n=next, r=repeat, q=quit: " << std::flush; + + char input; + std::cin >> input; + input = std::tolower(input); + + if (input == 'q') + { + std::cout << "Quitting test..." << std::endl; + break; + } + else if (input == 'r') + { + std::cout << "Repeating LED " << (ledIndex + 1) << std::endl; + } + else + { + ledIndex++; + } + } + + TurnOffAllLeds(); + + std::cout << "\nTest completed!" << std::endl; + std::cout << "All LEDs should now be OFF" << std::endl; + } +}; + +int main(int argc, char* argv[]) +{ + std::cout << "PacLed64 Direct USB Test Program" << std::endl; + std::cout << "=================================" << std::endl; + std::cout << "This program directly communicates with PacLed64 via USB" << std::endl; + std::cout << "Make sure you have permissions to access USB devices" << std::endl; + std::cout << "(you may need to run as root or configure udev rules)" << std::endl; + + PacLed64DirectTest* tester = nullptr; + + try + { + tester = new PacLed64DirectTest(); + + if (!tester->Connect()) + { + std::cout << "\nTroubleshooting:" << std::endl; + std::cout << "1. Check USB connection" << std::endl; + std::cout << "2. Verify device permissions (try running as root)" << std::endl; + std::cout << "3. Make sure PacLed64 is properly powered" << std::endl; + std::cout << "4. Verify PID (found: 0x1401)" << std::endl; + delete tester; + return 1; + } + + tester->RunTest(); + delete tester; + } + catch (const std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; + if (tester) + delete tester; + return 1; + } + catch (...) + { + std::cout << "ERROR: Unknown exception occurred" << std::endl; + if (tester) + delete tester; + return 1; + } + + return 0; +}