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
25 changes: 19 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
## Project Overview
libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 correspondence. Cross-platform library for Direct Output Framework tasks, used by Visual Pinball Standalone.

**Current Status**: ~99% complete - Core architecture, effects system, device management, all controller types, shape effects with bitmap rendering at 100% 1:1 correspondence.
**Current Status**: ~99.5% complete - Core architecture, effects system, device management, all controller types, shape effects with bitmap rendering at 100% 1:1 correspondence.

**Recent Major Implementation**: Matrix toy effects configuration completely fixed to achieve perfect 1:1 C# correspondence - corrected conditional logic for RGBAMatrixColorEffect creation, fixed case-insensitive color lookup, resolved E142/E145 effect creation issues, and cross-platform image loading via stb_image.h for bitmap shapes system.
**Recent Major Implementation**: Complete PAC controllers libusb migration - PacLed64, PacDrive, PacUIO migrated from HIDAPI to libusb via PacDriveSingleton for unified USB device management. Fixed PacLed64 individual LED protocol with 0-based numbering to match Ultimarc specification. All PAC controllers now use libusb control transfers with proper device permissions. Serial port handling improved with Linux-specific fixes for USB CDC device cleanup to prevent hanging on close operations.

## Core Coding Principles

Expand Down Expand Up @@ -68,16 +68,15 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden
- **Memory Safety**: All heap corruption issues resolved via proper value semantics
- **Effects System**: All effect types with correct chain ordering and nested blink support
- **Toys System**: All toy types with correct interface implementations
- **Output Controllers**: LedWiz, Pinscape, PinscapePico, FTDI, ComPort, DudesCab, DMX, PinOne, LED Strips
- **Output Controllers**: LedWiz, Pinscape, PinscapePico, FTDI, ComPort, DudesCab, DMX, PinOne, LED Strips, PAC Controllers (PacLed64, PacDrive, PacUIO)
- **Matrix Effects**: Flicker, Plasma, and Shift effects with exact timing correspondence
- **Bitmap Effects System**: Complete bitmap loading, FastImage.Frames, DirectOutputShapes.png support
- **Shape Effects System**: SHP code parsing, shape resolution, RGBAMatrixShapeEffect implementation
- **Image Loading System**: Cross-platform image loading via stb_image.h with PNG/GIF/BMP support
- **Configuration**: LedControl loader, GlobalConfig, ScheduledSettings system with corrected matrix effect logic
- **Cross-Platform**: Windows/Linux/macOS builds with manual dependency compilation

### ❌ MISSING COMPONENTS (1% remaining)
- **PAC Controllers**: PacDrive, PacLed64, PacUIO
### ❌ MISSING COMPONENTS (0.5% remaining)
- **SSF Controllers**: 7 variants with feedback systems
- **Philips Hue Controllers**: Smart lighting integration
- **Extensions Utilities**: 11 utility classes
Expand All @@ -86,7 +85,8 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden

### Hardware Requirements
- **PinscapePico**: Requires sudo access for HID enumeration
- **Unit Bias**: LedWiz(0), Pinscape(50), PinscapePico(119), DudesCab(90-94), PinOne(1-16)
- **PAC Controllers**: Use libusb with udev rules for device access without sudo
- **Unit Bias**: LedWiz(0), Pinscape(50), PinscapePico(119), DudesCab(90-94), PinOne(1-16), PAC(19-27)

### Windows API Conflicts
- Use `SendPipeMessage()` not `SendMessage()`
Expand All @@ -105,6 +105,18 @@ 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
- **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)
- **USB CDC Devices**: Can hang on `sp_close()` - requires RTS/DTR reset before closing
- **Device Existence Check**: Always verify device files exist before opening (prevents stale libserialport cache issues)
- **Cleanup Sequence**: `sp_flush(SP_BUF_BOTH)` → `sp_set_rts(SP_RTS_OFF)` → `sp_set_dtr(SP_DTR_OFF)` → `sp_close()`
- **Timeout Values**: Must match C# exactly (PinOne: 100ms, PinOneCommunication: 100ms server connection)

### Auto-Configuration Logging Philosophy
- **C# Pattern**: Auto-configurators are mostly silent - only log when devices are actually found/configured
- **No Scan Messages**: C# never logs "device scan found X devices" - remove all such logging
- **Debug vs Production**: Never leave debug logging in production code - maintain 1:1 C# correspondence for all output

### Bitmap Shapes Architecture
- **Image Loading**: Cross-platform loading via stb_image.h supporting PNG, GIF, BMP formats
Expand All @@ -117,3 +129,4 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden
## References
- **C# Source**: `/Users/jmillard/DirectOutput`
- **Documentation**: See NOTES.md for DirectOutput configuration parsing details
- **PAC Protocol Reference**: [Ultimarc-linux](https://github.com/katie-snow/Ultimarc-linux) - Essential for libusb PAC controller implementation
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "macos" OR PLATFORM STREQUAL "li
src/cab/out/ftdichip/FTDI.cpp
src/cab/out/lw/LedWiz.cpp
src/cab/out/lw/LedWizAutoConfigurator.cpp
src/cab/out/pac/PacLed64.cpp
src/cab/out/pac/PacLed64AutoConfigurator.cpp
src/cab/out/pac/PacDrive.cpp
src/cab/out/pac/PacDriveAutoConfigurator.cpp
src/cab/out/pac/PacUIO.cpp
src/cab/out/pac/PacUIOAutoConfigurator.cpp
src/cab/out/pac/PacDriveSingleton.cpp
src/cab/out/pinone/NamedPipeServer.cpp
src/cab/out/pinone/PinOne.cpp
src/cab/out/pinone/PinOneAutoConfigurator.cpp
Expand Down
76 changes: 75 additions & 1 deletion NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# NOTES.md

## USB Device Access Setup

To use libusb devices (PAC Controllers) without sudo privileges:

### Add user to plugdev group:
```bash
sudo usermod -a -G plugdev $USER
```

### Create udev rules for PAC devices:
```bash
sudo nano /etc/udev/rules.d/99-pacled64.rules
```

Add the following content:
```
# PacLed64 devices (VID:0xD209, PID:0x1401-0x1404)
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="1401", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="1402", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="1403", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="1404", MODE="0664", GROUP="plugdev"

# PacDrive devices (VID:0xD209, PID:0x1500)
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="1500", MODE="0664", GROUP="plugdev"
```

### Reload udev rules and logout/login:
```bash
sudo udevadm control --reload-rules
sudo udevadm trigger
# Then logout and login to apply group membership
```

## DirectOutput Framework Configuration & Debugging

### Official DirectOutput Documentation
Expand Down Expand Up @@ -61,4 +94,45 @@ PID=$!
sleep 2
leaks $PID
kill $PID
```
```

## Debug Iteration on Batocera

For testing libdof on Batocera (ARM-based retro gaming distribution), use this cross-compilation and deployment workflow:

### 1. Build on Ubuntu Host
```bash
# Cross-compile for Linux x64 (Batocera target architecture)
cmake -DPLATFORM=linux -DARCH=x64 -B build
cmake --build build -- -j10
```

### 2. Deploy to Batocera Box
```bash
# Sync built libraries and test executable to Batocera
rsync -avz --delete \
--exclude='libdof.a' \
--include='lib*' \
--include='dof_test' \
--exclude='*' \
build/ root@192.168.1.160:/userdata/dof
```

### 3. Test on Batocera Target
```bash
# SSH into Batocera box and run tests
ssh root@192.168.1.160
cd /userdata/dof

# Test with twenty4 ROM configuration
./dof_test twenty4 --base-path /userdata/system/configs/vpinball

# Deploy library to VPinballX installation
cp libdof.so.0.3.0 ~/configs/vpinball/VPinballX_GL-10.8.0-2070-c87ffe5-Release-linux-x64/
```

### Notes:
- **Target IP**: `192.168.1.160` - Update to match your Batocera box IP
- **VPinball Path**: Adjust version string (`10.8.0-2070-c87ffe5`) to match your VPinballX build
- **Config Path**: `/userdata/system/configs/vpinball` contains DOF configuration files
- **Architecture**: Batocera typically runs x64 architecture on PC hardware
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This library is currently used by [Visual Pinball Standalone](https://github.com
- **[Pinscape Pico](https://github.com/mjrgh/PinscapePico)** - RP2040-based version with enhanced features
- **[TeensyStripController](https://github.com/DirectOutput/TeensyStripController)** - Teensy based WS2812 LED strip controller
- **[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

### **Implemented & Ready To Test**
- **LedWiz** - Classic 32-output controller
Expand All @@ -31,9 +32,9 @@ This library is currently used by [Visual Pinball Standalone](https://github.com
- **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

### **Not Implemented**
- **PAC Controllers** (PacDrive, PacLed64, PacUIO) - *Windows DLL dependencies*
- **SSF Controllers** - *Audio-based feedback systems*
- **Philips Hue** - *Smart lighting integration*

Expand Down Expand Up @@ -154,6 +155,10 @@ cmake -DPLATFORM=android -DARCH=arm64-v8a -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build
```

## Acknowledgments:

PAC controller USB communication protocols were figured out with significant help from the [Ultimarc-linux](https://github.com/katie-snow/Ultimarc-linux) repository by @katie-snow. This project provided essential insights into the libusb control transfer implementation for PacLed64, PacDrive, and PacUIO devices.

## Configuration:

For custom setups, create a `CabinetConfig.xml` file in your DOF config directory:
Expand Down
5 changes: 3 additions & 2 deletions include/DOF/DOF.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#define LIBDOF_VERSION_MAJOR 0 // X Digits
#define LIBDOF_VERSION_MINOR 3 // Max 2 Digits
#define LIBDOF_VERSION_MINOR 4 // Max 2 Digits
#define LIBDOF_VERSION_PATCH 0 // Max 2 Digits

#define _LIBDOF_STR(x) #x
Expand Down Expand Up @@ -35,8 +35,9 @@

#if !((defined(__APPLE__) && ((defined(TARGET_OS_IOS) && TARGET_OS_IOS) || (defined(TARGET_OS_TV) && TARGET_OS_TV))) || defined(__ANDROID__))
#define __HIDAPI__
#define __LIBSERIALPORT__
#define __LIBUSB__
#define __LIBFTDI__
#define __LIBSERIALPORT__
#endif

#ifdef min
Expand Down
3 changes: 2 additions & 1 deletion platforms/linux/aarch64/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ cd libusb
./configure \
--enable-shared
make -j${NUM_PROCS}
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp -a libusb/.libs/libusb-1.0.{so,so.*} ../../third-party/runtime-libs/linux/aarch64/
cd ..

Expand Down
3 changes: 2 additions & 1 deletion platforms/linux/x64/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ cd libusb
./configure \
--enable-shared
make -j${NUM_PROCS}
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp -a libusb/.libs/libusb-1.0.{so,so.*} ../../third-party/runtime-libs/linux/x64/
cd ..

Expand Down
3 changes: 2 additions & 1 deletion platforms/macos/arm64/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ cd libusb
CFLAGS="-arch arm64" \
LDFLAGS="-Wl,-install_name,@rpath/libusb-1.0.dylib"
make -j${NUM_PROCS}
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp -a libusb/.libs/libusb*.dylib ../../third-party/runtime-libs/macos/arm64/
cd ..

Expand Down
3 changes: 2 additions & 1 deletion platforms/macos/x64/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ cd libusb
CFLAGS="-arch x86_64" \
LDFLAGS="-Wl,-install_name,@rpath/libusb-1.0.dylib"
make -j${NUM_PROCS}
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp -a libusb/.libs/libusb*.dylib ../../third-party/runtime-libs/macos/x64/
cd ..

Expand Down
3 changes: 2 additions & 1 deletion platforms/win/x64/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ msbuild.exe msvc/libusb_dll.vcxproj \
-p:TargetName=libusb64-1.0 \
-p:Platform=x64 \
-p:Configuration=Release
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp build/v143/x64/Release/libusb_dll/../dll/libusb64-1.0.lib ../../third-party/build-libs/win/x64
cp build/v143/x64/Release/libusb_dll/../dll/libusb64-1.0.dll ../../third-party/runtime-libs/win/x64
cd ..
Expand Down
3 changes: 2 additions & 1 deletion platforms/win/x86/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ cp ../../platforms/win/x86/libusb/libusb_dll.vcxproj msvc
msbuild.exe msvc/libusb_dll.vcxproj \
-p:Platform=x86 \
-p:Configuration=Release
cp libusb/libusb.h ../../third-party/include/
mkdir -p ../../third-party/include/libusb-1.0
cp libusb/libusb.h ../../third-party/include/libusb-1.0
cp build/v143/Win32/Release/libusb_dll/../dll/libusb-1.0.lib ../../third-party/build-libs/win/x86
cp build/v143/Win32/Release/libusb_dll/../dll/libusb-1.0.dll ../../third-party/runtime-libs/win/x86
cd ..
Expand Down
12 changes: 12 additions & 0 deletions src/cab/Cabinet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
#include "out/dudescab/DudesCabAutoConfigurator.h"
#endif

#ifdef __LIBUSB__
#include "out/pac/PacLed64AutoConfigurator.h"
#include "out/pac/PacDriveAutoConfigurator.h"
#include "out/pac/PacUIOAutoConfigurator.h"
#endif

#ifdef __LIBFTDI__
#include "out/ftdichip/FT245RBitbangControllerAutoConfigurator.h"
#endif
Expand Down Expand Up @@ -95,6 +101,12 @@ void Cabinet::AutoConfig()
items.push_back(new DudesCabAutoConfigurator());
#endif

#ifdef __LIBUSB__
items.push_back(new PacLed64AutoConfigurator());
items.push_back(new PacDriveAutoConfigurator());
items.push_back(new PacUIOAutoConfigurator());
#endif

#ifdef __LIBFTDI__
items.push_back(new FT245RBitbangControllerAutoConfigurator());
#endif
Expand Down
30 changes: 22 additions & 8 deletions src/cab/out/OutputControllerList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@
#include "dudescab/DudesCab.h"
#endif

#ifdef __LIBUSB__
#include "pac/PacLed64.h"
#include "pac/PacDrive.h"
#include "pac/PacUIO.h"
#endif

#ifdef __LIBFTDI__
#include "ftdichip/FT245RBitbangController.h"
#endif

#ifdef __LIBSERIALPORT__
#include "adressableledstrip/TeensyStripController.h"
#include "adressableledstrip/WemosD1StripController.h"
#include "comport/PinControl.h"
#include "pinone/PinOne.h"
#endif

#ifdef __LIBFTDI__
#include "ftdichip/FT245RBitbangController.h"
#endif

#include "dmx/ArtNet.h"

namespace DOF
Expand Down Expand Up @@ -110,6 +116,18 @@ IOutputController* OutputControllerList::CreateController(const std::string& typ
else if (typeName == "DudesCab")
return new DudesCab();
#endif
#ifdef __LIBUSB__
else if (typeName == "PacLed64")
return new PacLed64();
else if (typeName == "PacDrive")
return new PacDrive();
else if (typeName == "PacUIO")
return new PacUIO();
#endif
#ifdef __LIBFTDI__
else if (typeName == "FT245RBitbangController")
return new FT245RBitbangController();
#endif
#ifdef __LIBSERIALPORT__
else if (typeName == "TeensyStripController")
return new TeensyStripController();
Expand All @@ -119,10 +137,6 @@ IOutputController* OutputControllerList::CreateController(const std::string& typ
return new PinControl();
else if (typeName == "PinOne")
return new PinOne();
#endif
#ifdef __LIBFTDI__
else if (typeName == "FT245RBitbangController")
return new FT245RBitbangController();
#endif
else if (typeName == "ArtNet")
return new ArtNet();
Expand Down
Loading