Skip to content

Conversation

@chrcraven
Copy link

Made several changes to work on Raspberry Pi Trixie, attempted cross platform work. Full disclosure: Claude Code did heavy lifting.

  • Refactor to use bleak 1.1.1

Updated bleak dependency from 0.20.2 to 1.1.1 and refactored the BLE scanner code to use the modern API:

  • Updated requirements.txt to bleak==1.1.1
  • Refactored discover_trains() to use BleakScanner.discover() with return_adv=True
  • Simplified device discovery logic by using service_uuids parameter for filtering
  • Removed deprecated scanner.start() pattern in favor of the cleaner discover() API
  • Improved code clarity and reduced complexity in the discovery loop

All syntax checks pass successfully.

  • Add robust error handling for 'Operation already in progress' BLE errors

Enhanced BLE scanner with comprehensive error handling and retry logic to address "Operation already in progress" errors commonly seen on Raspberry Pi:

  • Import BleakError exception for proper error handling
  • Add try-except blocks around BleakScanner.discover() calls
  • Implement exponential backoff retry logic (starts at 1s, caps at 8-10s)
  • Add max_retries parameter (default: 10) to prevent infinite loops
  • Handle "Operation already in progress" and "busy" adapter errors specifically
  • Add detailed logging for retry attempts and error conditions
  • Catch and handle all unexpected exceptions gracefully

The exponential backoff uses different strategies:

  • Normal retries: 1.5x multiplier, capped at 8 seconds
  • Adapter busy errors: 2.0x multiplier, capped at 10 seconds

This should significantly improve reliability on Raspberry Pi systems where the Bluetooth adapter can become busy or have overlapping operations.

  • Prevent concurrent BLE scans with asyncio.Lock to avoid BlueZ errors

Based on research from GitHub issue hbldh/bleak#1629 and related discussions, the "Operation already in progress" error is primarily a BlueZ system-level issue that occurs when multiple BLE scan operations overlap.

Changes:

  • Added global _scan_lock (asyncio.Lock) to serialize all BLE scanning operations
  • Wrapped BleakScanner.discover() calls in async with _scan_lock context
  • Added 100ms delay after lock release to allow BlueZ to fully clean up state
  • Updated docstring to document the locking mechanism

This is the recommended solution from the Bleak maintainers for preventing concurrent scanner operations that can confuse BlueZ's internal state machine.

The combination of:

  1. Global lock (prevents concurrent scans from multiple calls)
  2. Exponential backoff retry logic (handles transient errors)
  3. Post-scan cleanup delay (gives BlueZ time to release resources)

Should provide robust operation on Raspberry Pi Trixie systems.

Additional recommendations for users:

  • Update Raspberry Pi firmware with 'sudo rpi-update'
  • Check for system-level BLE scanning services that may conflict
  • Use 'btmon' to debug underlying BlueZ issues if problems persist

Ref: hbldh/bleak#1629

  • Add platform-aware Raspberry Pi D-Bus adapter cleanup

Implements intelligent platform detection to apply D-Bus adapter cleanup only on Raspberry Pi systems, addressing "Operation already in progress" errors without affecting other Linux systems.

Changes:

  • Added _is_raspberry_pi() detection function checking:
    • /sys/firmware/devicetree/base/model for RPi identifier
    • /proc/cpuinfo for BCM chip (fallback method)
  • Added optional pydbus import with graceful degradation
  • Created _ensure_clean_adapter_state() to stop stuck BlueZ discovery
    • Only runs on Raspberry Pi with pydbus installed
    • Calls BlueZ D-Bus StopDiscovery() if adapter is stuck scanning
    • Logs helpful messages for debugging and installation hints
  • Integrated cleanup into discover_trains() before lock acquisition
  • Added platform detection logging at module load
  • Created requirements-rpi.txt for optional Raspberry Pi dependencies

Behavior:

  • Raspberry Pi + pydbus: Full D-Bus cleanup enabled
  • Raspberry Pi - pydbus: Logs install hint, uses lock + retry only
  • Other Linux: No-op, uses lock + retry only
  • All platforms: Maintains backward compatibility

This multi-layer approach ensures:

  1. D-Bus cleanup (RPi only) - prevents stuck discovery state
  2. asyncio.Lock - prevents concurrent scans
  3. Exponential backoff - handles transient errors
  4. Platform independence - safe on all systems

Install on Raspberry Pi for best results:
pip install -r requirements-rpi.txt

Ref: hbldh/bleak#1629

  • Add comprehensive help documentation and improved UX to CLI

Enhanced the CLI application with extensive help documentation and better user experience:

New Features:

  • Comprehensive HELP_TEXT constant with all available commands
  • Organized into sections: Motor, Lighting, Sound, Connection, Utility
  • Detailed documentation for each command including:
    • Command syntax
    • Argument descriptions with types and valid ranges
    • Multiple examples for each command
  • Quick start examples section with common use cases

UX Improvements:

  • Welcome banner on startup
  • Status messages during train discovery
  • Improved prompt: "LionChief> " instead of "Command:"
  • Support for 'help' and '?' to show documentation
  • Support for 'exit', 'quit', and 'q' to exit cleanly
  • Success indicators (✓) for executed commands
  • Better error messages with ❌ prefix
  • Helpful suggestions when commands fail
  • AttributeError handling for unknown methods
  • Improved shutdown messages
  • Better handling of empty input
  • Whitespace stripping for command arguments

Command Categories:

  • Motor: set_speed, set_movement_direction
  • Lighting: set_lights
  • Sound: set_horn, set_bell, set_steam_volume, set_horn_pitch, set_bell_pitch, set_voice_line_volume, set_engine_volume, play_voice_line
  • Connection: disconnect
  • Utility: help, exit, quit

The help system provides clear examples for users to understand how to control their LionChief trains through the CLI.

  • Improve cross-platform compatibility for Windows and macOS

Enhanced the codebase to work seamlessly across Windows, macOS, and Linux:

CLI Improvements (examples/cli.py):

  • Added platform-aware Unicode detection for terminal compatibility
  • Implemented ASCII fallbacks for Windows cmd.exe:
    • ✓ → [OK], ❌ → [X]
    • Box drawing characters (┌│└═) → ASCII equivalents (+|-=)
  • Created _supports_unicode() to detect UTF-8 terminal support
  • Created _get_help_text() to dynamically generate help with correct characters
  • All status symbols now use CHECK_MARK/CROSS_MARK constants
  • Ensures professional appearance on all platforms

Connection Module Cleanup (lionchief/connection.py):

  • Removed unused 'platform' import

Documentation (README.md):

  • Added comprehensive "Platform Support" section
  • Documented requirements for Windows, macOS, Linux, and Raspberry Pi
  • Included platform-specific setup instructions:
    • Windows: BLE requirements, admin privileges, terminal recommendations
    • macOS: Bluetooth permissions information
    • Linux: BlueZ requirements, user permissions
    • Raspberry Pi: Enhanced reliability with pydbus

Cross-Platform Testing:

  • Windows cmd.exe: ASCII symbols render correctly
  • Windows Terminal/PowerShell: Unicode symbols work
  • macOS/Linux terminals: Full Unicode support
  • All platforms: Syntax validation passes

The code now provides a consistent, professional user experience regardless of the platform or terminal being used.

  • Add command history and debug mode to CLI for troubleshooting

Enhanced the CLI with two major features:

  1. Command History Support (readline):

    • Users can now use up/down arrows to recall previous commands
    • Tab completion support
    • Standard readline editing (Emacs mode)
    • Graceful fallback if readline not available
  2. Debug Mode:

    • New 'debug' command to toggle debug logging
    • Shows raw BLE command bytes being sent to train
    • Helps troubleshoot protocol issues
    • Format: "DEBUG: Sending command: "
    • Can be toggled on/off during session

Usage:
LionChief> debug # Enable debug mode
LionChief> motor,set_speed,100
DEBUG: Sending command: 004564d7
✓ Command executed successfully
LionChief> debug # Disable debug mode

This will help diagnose why motor commands aren't working while lighting commands work correctly - users can compare the actual bytes being sent and verify the protocol implementation.

Also updated help text to document the new debug command.

  • Fix motor speed range documentation (0-100, not 0-255)

Corrected the motor speed range in all documentation and docstrings. The actual range is 0-100, not 0-255 as previously documented.

Changes:

  • Updated CLI help text to show correct range (0-100)
  • Changed examples from motor,set_speed,128 to motor,set_speed,50
  • Updated motor.py docstring with correct range and description
  • Fixed quick start examples to use valid speed values

This was discovered during testing on Raspberry Pi where the train now moves correctly with speed values in the 0-100 range.

  • Fix debug mode and improve volume error messages

Fixed two issues discovered during Raspberry Pi testing:

  1. Debug Mode Not Working (examples/cli.py):

    • logging.basicConfig() doesn't reconfigure after first call
    • Changed to use logger.setLevel() for dynamic log level changes
    • Set up root logger with handler at startup
    • Debug mode now properly toggles and shows BLE commands
  2. Misleading Error Messages (lionchief/sound.py):

    • Error messages were printing the wrong value
    • "volume must be between 0 and 5" when it should be "0 and 4"
    • Fixed all volume/pitch error messages:
      • set_steam_volume
      • set_horn_pitch * set_bell_pitch * set_voice_line_volume * set_engine_volume
    • Now shows: "must be between 0 and 4 (got 5)" format

Testing Notes:

  • Debug mode now correctly displays: "DEBUG: Sending command: 0045641b"
  • Error messages are clear and show both range and invalid value
  • Motor commands work with 0-100 range
  • Lighting commands work correctly
  • Add gradual speed control and stop methods to motor controller

Added new motor control methods to address speed ramping issues:

Motor Controller (lionchief/motor.py):

  • Added _current_speed tracking to know current speed
  • Added gradual_speed_change(target, step=5, delay=0.2) method:
    • Smoothly ramps speed up or down to avoid sudden jumps
    • Configurable step size (how much to change per step)
    • Configurable delay between steps
    • Automatically detects whether to speed up or slow down
  • Added stop() convenience method to set speed to 0

CLI Help Documentation (examples/cli.py):

  • Documented motor,gradual_speed_change command with examples
  • Documented motor,stop command
  • Updated motor,set_speed description for clarity

Usage Examples:
motor,gradual_speed_change,50 # Smoothly ramp to 50
motor,gradual_speed_change,0,3,0.1 # Slow down gently
motor,gradual_speed_change,80,10,0.3 # Quick acceleration
motor,stop # Quick stop

This helps work around the reported issue where speeds 1-3 are normal but speed 4 causes a sudden jump. Users can now use gradual_speed_change to smoothly transition through problematic speed ranges.

Next step: Investigate why speed 4 causes sudden jump (possible byte conversion or protocol mapping issue).

  • Document that volume/pitch settings should use official LionChief app

Added clear documentation in README explaining:

  • Volume and pitch settings are persistent (stored on train firmware)
  • Best practice is to configure these once via official Lionel LionChief app
  • Settings are retained and don't need to be set each session
  • pyLionChief focuses on runtime operation, not persistent configuration

This clarifies the separation of concerns:

  • Official app: One-time setup/configuration (volumes, pitch)
  • pyLionChief: Runtime operation (speed, direction, lights, sound triggers)

Added new "CLI Example" section documenting what the CLI supports:

  • Motor control (speed, direction, gradual speed changes)
  • Lighting control
  • Sound effects (horn, bell, voice lines)
  • Command history with up/down arrows
  • Debug mode

This helps users understand what to use each tool for.


* Refactor to use bleak 1.1.1

Updated bleak dependency from 0.20.2 to 1.1.1 and refactored the BLE scanner code to use the modern API:

- Updated requirements.txt to bleak==1.1.1
- Refactored discover_trains() to use BleakScanner.discover() with return_adv=True
- Simplified device discovery logic by using service_uuids parameter for filtering
- Removed deprecated scanner.start() pattern in favor of the cleaner discover() API
- Improved code clarity and reduced complexity in the discovery loop

All syntax checks pass successfully.

* Add robust error handling for 'Operation already in progress' BLE errors

Enhanced BLE scanner with comprehensive error handling and retry logic to address
"Operation already in progress" errors commonly seen on Raspberry Pi:

- Import BleakError exception for proper error handling
- Add try-except blocks around BleakScanner.discover() calls
- Implement exponential backoff retry logic (starts at 1s, caps at 8-10s)
- Add max_retries parameter (default: 10) to prevent infinite loops
- Handle "Operation already in progress" and "busy" adapter errors specifically
- Add detailed logging for retry attempts and error conditions
- Catch and handle all unexpected exceptions gracefully

The exponential backoff uses different strategies:
- Normal retries: 1.5x multiplier, capped at 8 seconds
- Adapter busy errors: 2.0x multiplier, capped at 10 seconds

This should significantly improve reliability on Raspberry Pi systems where the
Bluetooth adapter can become busy or have overlapping operations.

* Prevent concurrent BLE scans with asyncio.Lock to avoid BlueZ errors

Based on research from GitHub issue hbldh/bleak#1629 and related discussions,
the "Operation already in progress" error is primarily a BlueZ system-level issue
that occurs when multiple BLE scan operations overlap.

Changes:
- Added global _scan_lock (asyncio.Lock) to serialize all BLE scanning operations
- Wrapped BleakScanner.discover() calls in async with _scan_lock context
- Added 100ms delay after lock release to allow BlueZ to fully clean up state
- Updated docstring to document the locking mechanism

This is the recommended solution from the Bleak maintainers for preventing
concurrent scanner operations that can confuse BlueZ's internal state machine.

The combination of:
1. Global lock (prevents concurrent scans from multiple calls)
2. Exponential backoff retry logic (handles transient errors)
3. Post-scan cleanup delay (gives BlueZ time to release resources)

Should provide robust operation on Raspberry Pi Trixie systems.

Additional recommendations for users:
- Update Raspberry Pi firmware with 'sudo rpi-update'
- Check for system-level BLE scanning services that may conflict
- Use 'btmon' to debug underlying BlueZ issues if problems persist

Ref: hbldh/bleak#1629

* Add platform-aware Raspberry Pi D-Bus adapter cleanup

Implements intelligent platform detection to apply D-Bus adapter cleanup
only on Raspberry Pi systems, addressing "Operation already in progress"
errors without affecting other Linux systems.

Changes:
- Added _is_raspberry_pi() detection function checking:
  * /sys/firmware/devicetree/base/model for RPi identifier
  * /proc/cpuinfo for BCM chip (fallback method)
- Added optional pydbus import with graceful degradation
- Created _ensure_clean_adapter_state() to stop stuck BlueZ discovery
  * Only runs on Raspberry Pi with pydbus installed
  * Calls BlueZ D-Bus StopDiscovery() if adapter is stuck scanning
  * Logs helpful messages for debugging and installation hints
- Integrated cleanup into discover_trains() before lock acquisition
- Added platform detection logging at module load
- Created requirements-rpi.txt for optional Raspberry Pi dependencies

Behavior:
- Raspberry Pi + pydbus: Full D-Bus cleanup enabled
- Raspberry Pi - pydbus: Logs install hint, uses lock + retry only
- Other Linux: No-op, uses lock + retry only
- All platforms: Maintains backward compatibility

This multi-layer approach ensures:
1. D-Bus cleanup (RPi only) - prevents stuck discovery state
2. asyncio.Lock - prevents concurrent scans
3. Exponential backoff - handles transient errors
4. Platform independence - safe on all systems

Install on Raspberry Pi for best results:
pip install -r requirements-rpi.txt

Ref: hbldh/bleak#1629

* Add comprehensive help documentation and improved UX to CLI

Enhanced the CLI application with extensive help documentation and
better user experience:

New Features:
- Comprehensive HELP_TEXT constant with all available commands
- Organized into sections: Motor, Lighting, Sound, Connection, Utility
- Detailed documentation for each command including:
  * Command syntax
  * Argument descriptions with types and valid ranges
  * Multiple examples for each command
- Quick start examples section with common use cases

UX Improvements:
- Welcome banner on startup
- Status messages during train discovery
- Improved prompt: "LionChief> " instead of "Command:"
- Support for 'help' and '?' to show documentation
- Support for 'exit', 'quit', and 'q' to exit cleanly
- Success indicators (✓) for executed commands
- Better error messages with ❌ prefix
- Helpful suggestions when commands fail
- AttributeError handling for unknown methods
- Improved shutdown messages
- Better handling of empty input
- Whitespace stripping for command arguments

Command Categories:
- Motor: set_speed, set_movement_direction
- Lighting: set_lights
- Sound: set_horn, set_bell, set_steam_volume, set_horn_pitch,
         set_bell_pitch, set_voice_line_volume, set_engine_volume,
         play_voice_line
- Connection: disconnect
- Utility: help, exit, quit

The help system provides clear examples for users to understand
how to control their LionChief trains through the CLI.

* Improve cross-platform compatibility for Windows and macOS

Enhanced the codebase to work seamlessly across Windows, macOS, and Linux:

CLI Improvements (examples/cli.py):
- Added platform-aware Unicode detection for terminal compatibility
- Implemented ASCII fallbacks for Windows cmd.exe:
  * ✓ → [OK], ❌ → [X]
  * Box drawing characters (┌│└═) → ASCII equivalents (+|-=)
- Created _supports_unicode() to detect UTF-8 terminal support
- Created _get_help_text() to dynamically generate help with correct characters
- All status symbols now use CHECK_MARK/CROSS_MARK constants
- Ensures professional appearance on all platforms

Connection Module Cleanup (lionchief/connection.py):
- Removed unused 'platform' import

Documentation (README.md):
- Added comprehensive "Platform Support" section
- Documented requirements for Windows, macOS, Linux, and Raspberry Pi
- Included platform-specific setup instructions:
  * Windows: BLE requirements, admin privileges, terminal recommendations
  * macOS: Bluetooth permissions information
  * Linux: BlueZ requirements, user permissions
  * Raspberry Pi: Enhanced reliability with pydbus

Cross-Platform Testing:
- Windows cmd.exe: ASCII symbols render correctly
- Windows Terminal/PowerShell: Unicode symbols work
- macOS/Linux terminals: Full Unicode support
- All platforms: Syntax validation passes

The code now provides a consistent, professional user experience
regardless of the platform or terminal being used.

* Add command history and debug mode to CLI for troubleshooting

Enhanced the CLI with two major features:

1. Command History Support (readline):
   - Users can now use up/down arrows to recall previous commands
   - Tab completion support
   - Standard readline editing (Emacs mode)
   - Graceful fallback if readline not available

2. Debug Mode:
   - New 'debug' command to toggle debug logging
   - Shows raw BLE command bytes being sent to train
   - Helps troubleshoot protocol issues
   - Format: "DEBUG: Sending command: <hex bytes>"
   - Can be toggled on/off during session

Usage:
  LionChief> debug              # Enable debug mode
  LionChief> motor,set_speed,100
  DEBUG: Sending command: 004564d7
  ✓ Command executed successfully
  LionChief> debug              # Disable debug mode

This will help diagnose why motor commands aren't working while
lighting commands work correctly - users can compare the actual
bytes being sent and verify the protocol implementation.

Also updated help text to document the new debug command.

* Fix motor speed range documentation (0-100, not 0-255)

Corrected the motor speed range in all documentation and docstrings.
The actual range is 0-100, not 0-255 as previously documented.

Changes:
- Updated CLI help text to show correct range (0-100)
- Changed examples from motor,set_speed,128 to motor,set_speed,50
- Updated motor.py docstring with correct range and description
- Fixed quick start examples to use valid speed values

This was discovered during testing on Raspberry Pi where the train
now moves correctly with speed values in the 0-100 range.

* Fix debug mode and improve volume error messages

Fixed two issues discovered during Raspberry Pi testing:

1. Debug Mode Not Working (examples/cli.py):
   - logging.basicConfig() doesn't reconfigure after first call
   - Changed to use logger.setLevel() for dynamic log level changes
   - Set up root logger with handler at startup
   - Debug mode now properly toggles and shows BLE commands

2. Misleading Error Messages (lionchief/sound.py):
   - Error messages were printing the wrong value
   - "volume must be between 0 and 5" when it should be "0 and 4"
   - Fixed all volume/pitch error messages:
     * set_steam_volume
     * set_horn_pitch
     * set_bell_pitch
     * set_voice_line_volume
     * set_engine_volume
   - Now shows: "must be between 0 and 4 (got 5)" format

Testing Notes:
- Debug mode now correctly displays: "DEBUG: Sending command: 0045641b"
- Error messages are clear and show both range and invalid value
- Motor commands work with 0-100 range
- Lighting commands work correctly

* Add gradual speed control and stop methods to motor controller

Added new motor control methods to address speed ramping issues:

Motor Controller (lionchief/motor.py):
- Added _current_speed tracking to know current speed
- Added gradual_speed_change(target, step=5, delay=0.2) method:
  * Smoothly ramps speed up or down to avoid sudden jumps
  * Configurable step size (how much to change per step)
  * Configurable delay between steps
  * Automatically detects whether to speed up or slow down
- Added stop() convenience method to set speed to 0

CLI Help Documentation (examples/cli.py):
- Documented motor,gradual_speed_change command with examples
- Documented motor,stop command
- Updated motor,set_speed description for clarity

Usage Examples:
  motor,gradual_speed_change,50         # Smoothly ramp to 50
  motor,gradual_speed_change,0,3,0.1    # Slow down gently
  motor,gradual_speed_change,80,10,0.3  # Quick acceleration
  motor,stop                            # Quick stop

This helps work around the reported issue where speeds 1-3 are normal
but speed 4 causes a sudden jump. Users can now use gradual_speed_change
to smoothly transition through problematic speed ranges.

Next step: Investigate why speed 4 causes sudden jump (possible byte
conversion or protocol mapping issue).

* Document that volume/pitch settings should use official LionChief app

Added clear documentation in README explaining:

- Volume and pitch settings are persistent (stored on train firmware)
- Best practice is to configure these once via official Lionel LionChief app
- Settings are retained and don't need to be set each session
- pyLionChief focuses on runtime operation, not persistent configuration

This clarifies the separation of concerns:
- Official app: One-time setup/configuration (volumes, pitch)
- pyLionChief: Runtime operation (speed, direction, lights, sound triggers)

Added new "CLI Example" section documenting what the CLI supports:
- Motor control (speed, direction, gradual speed changes)
- Lighting control
- Sound effects (horn, bell, voice lines)
- Command history with up/down arrows
- Debug mode

This helps users understand what to use each tool for.

---------

Co-authored-by: Claude <noreply@anthropic.com>
@thetestgame thetestgame self-assigned this Nov 20, 2025
@thetestgame
Copy link
Owner

I'm unfortunately not setup to test changes to pyLionChief at the moment. Though the code its self looks fine. I'll try and circle back to this as soon as I'm able.

@chrcraven
Copy link
Author

No worries! I'm working on an interactive display using a train, api on an rPi, and a UI for phones. Assuming that goes well I may contribute that as an example in the future

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants