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>
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>
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>
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>
- 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>
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>