Commit graph

24 commits

Author SHA1 Message Date
torlando-tech
130bd1a723 fix: abort USB start if old read thread won't stop
Prevents race condition where two threads compete for USB reads
if the previous thread doesn't terminate within timeout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 19:37:34 -05:00
torlando-tech
93bdec9ebd fix: address code review feedback for USB bridge
- Set readTimeout=100ms to reduce CPU usage from continuous polling
- Escalate thread leak warning to ERROR level for visibility
- Capture deviceId early in detach handler for stability across Android versions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 19:30:53 -05:00
torlando-tech
df4e7761f0 feat: add USB-assisted Bluetooth pairing with device selection
When pairing a USB-connected RNode via Bluetooth, the wizard now:
- Adds "Pair via USB" option to the Bluetooth tab
- Receives PIN from RNode over USB connection
- Scans for unbonded RNodes and shows device selection list
- Auto-selects if only one unbonded RNode is found
- Uses BroadcastReceiver to properly detect bond state changes

This solves the issue where multiple RNodes in range would cause
pairing failures because the PIN belongs to the USB-connected device,
not necessarily the first discovered one.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 12:21:04 -05:00
torlando-tech
07893238cd test: improve rnode_interface.py coverage and fix PIN parsing
Test coverage improvements (26% -> 47%):
- Add tests for _get_usb_bridge() success/failure cases
- Add tests for _start_usb() VID/PID lookup, connection, callbacks
- Add tests for _on_usb_connection_state_changed() callback
- Add tests for USB VID/PID config attributes
- Add tests for start() routing to correct implementation

PIN parsing fix:
- Fix CMD_BT_PIN parsing to use 4-byte big-endian integer format
- Matches Kotlin KotlinUSBBridge implementation
- Update test to verify correct frame structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 00:52:45 -05:00
torlando-tech
3acebc9c93 chore: remove unused CMD_DEV_HASH constant
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:56:14 -05:00
torlando-tech
21f64cc0ea feat: simplify USB RNode interface naming to "RNode USB"
Remove device hash query complexity for USB RNodes since CMD_DEV_HASH
returns a different hash than what's used for Bluetooth naming. USB
interfaces now simply use "RNode USB" as the default name.

The device hash query code is retained in KotlinUSBBridge for potential
future use (debugging, device identification).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:43:57 -05:00
torlando-tech
c6862914b8 fix: stop USB RNode read loop on disconnect to prevent thread leak
When switching between multiple USB RNodes, the read loop thread was not
being stopped on disconnect. This caused multiple threads to compete for
data from the shared USB buffer, leading to:
- KISS frames being split across threads
- Detection responses consumed by wrong/stale threads
- Detection timeout despite successful response

The fix:
- Clear _running flag in disconnect callback to stop read loop
- Add safety check in _start_usb() to stop any stale read thread
- Reset detection state on reconnection for clean configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 21:00:35 -05:00
torlando-tech
20a6c1b615 perf: remove 0.5s USB stabilization delay on RNode connect
USB connections are reliable enough to skip the stabilization delay
that was previously added for safety. This reduces reconnect time
by 0.5 seconds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 17:17:27 -05:00
torlando-tech
1f548d1781 fix: USB RNode auto-reconnect with permission-aware retry
When USB RNode is replugged:
- BroadcastReceiver catches USB attach event early
- Checks if USB permission is already granted
- If no permission: skips reconnect, allows retry via Activity intent
- If permission granted: reconnects immediately

Also:
- Clear stale USB connection in Python before reconnecting
- Track if reconnect was attempted to allow permission-based retry
- Listen for USB detach events to reset debounce state

This fixes the race condition where BroadcastReceiver fired before
Android granted USB permission, causing reconnect to fail.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 17:13:15 -05:00
torlando-tech
758754ddee fix: pass USB bridge to Python for RNode interface startup
The USB RNode interface was saved to database correctly but failed to
start because:

1. The usb_device_id was missing from _pending_rnode_config in
   reticulum_wrapper.py
2. The KotlinUSBBridge was never passed to the Python side (unlike
   KotlinRNodeBridge for Bluetooth)
3. The connect() call didn't pass baud rate (Chaquopy doesn't support
   Kotlin default parameters)

Changes:
- Add usb_device_id to _pending_rnode_config dictionary
- Add set_usb_bridge() method to reticulum_wrapper.py
- Set USB bridge in ReticulumServiceBinder before Python init
- Pass baud rate (115200) explicitly to usb_bridge.connect()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 12:15:26 -05:00
torlando-tech
3e645dd7d7 feat: add USB RNode support (Issue #173)
Add USB serial support for RNode devices with two modes:
1. USB Serial Mode: Operate RNode entirely over USB cable
2. Bluetooth Pairing Mode: Pair RNode via USB (for T114 and devices without user button)

Key changes:
- Add KotlinUSBBridge for USB serial communication using usb-serial-for-android library
- Support FTDI, CP210x, CH340, PL2303, and CDC-ACM USB serial drivers
- Create Python usb_bridge.py module for Python-side USB access
- Extend rnode_interface.py with MODE_USB and Bluetooth pairing commands
- Add USB tab to RNode wizard UI with device enumeration and permission handling
- Implement Bluetooth pairing mode flow with PIN display
- Add DiscoveredUsbDevice data class for USB device information
- Update InterfaceConfig.RNode with usbDeviceId field
- Add USB host feature declaration to AndroidManifest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 20:31:29 -05:00
torlando-tech
5a9fd950de fix: TDD fixes for BLE deduplication cooldown asymmetry and test stability
- Fix BLE cooldown asymmetry: set deduplication cooldown BEFORE deciding
  which connection to keep, ensuring cooldown is applied regardless of
  whether central or peripheral connection is retained
- Fix InterfaceManagementViewModel test OOM: make STATUS_POLL_INTERVAL_MS
  testable (const→var) and add conditional return when interval <= 0
- Fix KISS.unescape() to properly skip invalid escape sequences (0xDB
  followed by invalid byte) and trailing FESC bytes instead of corrupting
  the data buffer
- Add comprehensive unit tests for all fixes following strict TDD approach

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 18:56:16 -05:00
torlando-tech
4a70196e31 fix: TDD fixes for PR #36 and custom mode UX improvements
- Fix Python RSSI/SNR race condition with proper lock acquisition
- Add manual device name validation (32 char limit, RNode name check)
- Add regulatory warnings for custom mode without region selection
- Preserve user-modified field errors during wizard navigation
- Improve custom mode UX: highlight selection, skip modem/slot steps
- Auto-expand advanced settings in custom mode review
- Hide region/modem/slot summary cards in custom mode
- Split large test file to pass detekt LargeClass rule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 16:00:12 -05:00
torlando-tech
6fc4b519a6 fix: increase TX power validation limit to support regional maximums
The Python RNode interface hardcoded a 22 dBm maximum TX power, which
rejected valid regional settings (US allows 30 dBm, NZ allows 36 dBm).

Changes:
- Update Python validation to accept TX power up to 36 dBm (NZ max)
- Add Python tests for regional TX power limits (US 30, EU 27, NZ 36)
- Add Kotlin tests for NZ region validation (36 dBm max)

The Kotlin validator already correctly uses regional limits. The RNode
firmware provides final hardware validation and returns error 0x40 if
TX power exceeds device capability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 14:14:45 -05:00
torlando-tech
cf49225939 fix: Python RNode interface thread safety and CI improvements
- Add lock around self.online status in _set_online() and process_outgoing()
  to prevent frame loss during online/offline transitions
- Add lock around RSSI/SNR updates for consistency with other radio state
- Log warning for malformed KISS escape sequences instead of silent ignore
- Add Python unit tests for thread safety fixes (6 new tests, 68 total)
- Add Python pytest step to CI workflow before Gradle tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 13:58:30 -05:00
torlando-tech
166bb16300 fix: address 8 critical issues from PR review with TDD
Critical fixes:
- Fix memory leak: RSSI polling now stopped in onCleared()
- Fix silent BLE scan errors: user-friendly error messages added
- Fix double-check locking bug in Python RNode initialization
- Fix interface registration order: start() before Transport register
- Fix race condition: use threading.Event for read loop control
- Fix write retry: implement exponential backoff (0.3s, 1s, 3s)
- Fix BLE write latch race: null check prevents stale callbacks
- Fix MTU request hang: 2-second timeout falls back to discoverServices

Tests added:
- RSSI polling cancellation test
- BLE scan error handling test
- Thread safety tests for read loop
- Write retry exponential backoff tests

Also includes ktlint format auto-fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 13:07:10 -05:00
torlando-tech
d8300627a9 feat: add event-driven UI refresh for RNode connection status
- Register interface with RNS.Transport before start() to fix race
  condition where auto-reconnect succeeds but interface wasn't tracked
- Add online status callback chain: Python → KotlinRNodeBridge →
  ReticulumServiceBinder → ServiceReticulumProtocol → ViewModel
- ViewModel now observes interfaceStatusChanged flow for immediate
  refresh when RNode connects/disconnects
- Change diagnostic logs from INFO to DEBUG level for production
- Add unit tests for RNodeOnlineStatusListener functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 11:40:28 -05:00
torlando-tech
b6b34603ec fix: add BLE write synchronization to prevent dropped commands
Android BLE is asynchronous - writeCharacteristic() only queues the write.
Without waiting for onCharacteristicWrite() callback, subsequent writes
are silently dropped by the BLE stack.

Changes:
- Add CountDownLatch-based synchronization in KotlinRNodeBridge
- Each BLE write now waits for callback confirmation before proceeding
- Add small delays in Python for framebuffer writes (defensive)

This fixes reliability issues with RNode framebuffer commands on BLE.
Note: T114 still won't show framebuffer due to upstream firmware bug
(bt_ssp_pin set at startup, never cleared).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
af930401d6 feat: add human-readable RNode error messages with UI callback
Surface RNode hardware errors to users with helpful messages:
- 0x01: Radio initialization failed
- 0x02: Transmission failed
- 0x04: Data queue overflowed
- 0x40: Invalid configuration (suggests reducing TX power)

Add error callback mechanism to propagate errors from Python interface
through Kotlin bridge to the UI layer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
b72d0227e8 fix: disable external framebuffer when logo setting is turned off
When user disables the logo display setting and applies changes,
explicitly call disable_external_framebuffer() to restore normal
RNode UI without requiring a device restart.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
d75bd2d77f feat: display Columba logo on RNode OLED via external framebuffer
Add support for displaying the Columba constellation logo on RNode's
OLED display when connected. The logo is sent via KISS protocol using
the external framebuffer commands (CMD_FB_EXT, CMD_FB_WRITE).

Changes:
- Add conversion script to render icon to 64x64 monochrome bitmap
- Add columba_logo.py with 512-byte framebuffer data
- Add framebuffer methods to ColumbaRNodeInterface
- Auto-display logo after successful RNode connection
- Enable by default via enable_framebuffer config option

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
45f4d02482 feat: add automatic RNode reconnection after disconnect
When the RNode disconnects (power cycle, out of range, etc.), the
interface now automatically attempts to reconnect:

- Starts a background reconnection loop on disconnect detection
- Tries to reconnect every 10 seconds, up to 30 attempts (~5 minutes)
- Logs progress: "Reconnection attempt X/30 for RNode..."
- Stops reconnection loop when connection succeeds or interface is stopped

Also fixes CompanionDeviceManager-triggered reconnection:
- initialize_rnode_interface() now checks for existing offline interface
- Calls start() to reconnect instead of failing due to missing config
- Handles case where interface already exists but config was cleared

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
acb10d8e95 fix: improve RNode BLE connection stability and link establishment
- Set HW_MTU to 500 to prevent RNS from truncating packet data before
  link_id computation, which was causing link establishment failures
- Increase BLE stabilization delay from 0.5s to 1.5s to allow connection
  to fully establish before configuration
- Add retry logic to writes (3 attempts with 0.3s delays) to handle
  transient BLE connection issues
- Add diagnostic logging to writeSync() for easier debugging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
2b46964125 feat: add RNode LoRa interface support via Bluetooth
Implements RNode interface support for LoRa communication via paired
Bluetooth RNode devices. Uses a Kotlin Bridge architecture where Kotlin
handles Bluetooth I/O and Python handles the KISS protocol.

- **KotlinRNodeBridge**: Handles Bluetooth Classic (SPP/RFCOMM) and BLE
  (Nordic UART Service) connections to RNode hardware. Manages connection
  lifecycle, data buffering, and provides read/write APIs to Python.

- **ColumbaRNodeInterface**: Python interface implementing KISS protocol
  for RNode communication. Handles frame escaping, command parsing, radio
  configuration, and integrates with RNS Transport layer.

- **UI Components**: Added RNode configuration fields to InterfaceConfigDialog
  including device name selector, connection mode (Classic/BLE), frequency,
  bandwidth, spreading factor, coding rate, and TX power settings.

- Supports both Bluetooth Classic (UUID: 00001101-0000-1000-8000-00805F9B34FB)
  and BLE via Nordic UART Service (UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e)
- Thread-safe circular buffer for BLE packet reassembly
- Automatic device discovery from paired devices list
- Connection state management with callbacks

- Full KISS protocol implementation (FEND/FESC escape sequences)
- RNode detection and firmware version validation
- Radio parameter configuration (frequency, bandwidth, SF, CR, TX power)
- Airtime limiting support (short-term and long-term)
- Required RNS Transport interface attributes for compatibility

- set_rnode_bridge() to receive Kotlin bridge reference
- initialize_rnode_interface() called during bridge setup
- RNode interface registered with RNS.Transport.interfaces

1. **Chaquopy ByteArray conversion**: Raw bytes from Kotlin needed explicit
   `bytes()` conversion in Python due to Chaquopy's jarray handling.

2. **KISS frame format**: Initial detection commands were missing FEND
   delimiters, causing RNode to not respond to detection requests.

3. **RNS Transport compatibility**: Required iteratively adding interface
   attributes (bitrate, rxb, txb, mode, mtu, HW_MTU, FIXED_MTU,
   AUTOCONFIGURE_MTU, announce_rate_target, ifac_size, etc.) and methods
   (sent_announce(), received_announce(), process_held_announces(),
   should_ingress_limit()) to satisfy RNS Transport requirements.

4. **Owner inbound routing**: Changed from owner.inbound() to direct
   RNS.Transport.inbound() calls since owner was ReticulumWrapper, not
   Transport.

Successfully tested bidirectional communication:
- Announces sent and received between Columba and Sideband via LoRa
- Links established with ~1.8s RTT over LoRa
- Messages delivered from Columba to Sideband
- Messages received from Sideband (routing to correct identity required)

- python/rnode_interface.py (NEW): KISS protocol and RNode interface
- reticulum/rnode/KotlinRNodeBridge.kt (NEW): Bluetooth bridge
- python/reticulum_wrapper.py: RNode bridge integration
- ReticulumServiceBinder.kt: Bridge initialization in setupBridges()
- InterfaceConfigDialog.kt: RNode UI configuration fields
- InterfaceManagementViewModel.kt: RNode state management
- ReticulumConfig.kt: RNode data model with targetDeviceName, connectionMode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:47 -05:00