Commit graph

131 commits

Author SHA1 Message Date
Torlando
c0529dc307
Merge pull request #284 from torlando-tech/feature/issue-216-rns-discovery
feat: add RNS 1.1.x bootstrap and interface discovery support
2026-01-22 13:48:06 -05:00
torlando-tech
53fa9f24e0 feat: add connected indicator and improved icons for discovered interfaces
- Add "Connected" badge showing which discovered interfaces are currently
  auto-connected by RNS discovery
- Add getAutoconnectedEndpoints() API to fetch active auto-connected interfaces
- Use type-specific icons: globe for TCP, antenna for radio, incognito for I2P,
  tree-pine for Yggdrasil (detected via 0200::/7 IPv6 range)
- Show I2P b32 addresses with .b32.i2p suffix
- Display human-readable interface type labels (e.g., "Backbone (TCP)")

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 21:12:05 -05:00
torlando-tech
8bc1d054d4 feat: add discovery toggle and fix discovered interfaces display
- Add toggle in Discovered Interfaces screen to enable/disable discovery
- Service auto-restarts when toggle is changed (no app restart needed)
- Show bootstrap-enabled interface names in settings card
- Add discovery settings to SettingsRepository and StartupConfigLoader
- Include discover_interfaces and autoconnect_discovered_interfaces in config
- Include bootstrap_only for TCPClient interfaces in buildConfigJson
- Fix Python wrapper bug: discovered_interfaces() returns list, not dict
- Add bottom padding to list to avoid nav bar overlap

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 19:49:16 -05:00
torlando-tech
66982b18de feat: Show user-configured interface name in Node Details
Update the "Received Via" card in Node Details to show the user-configured
interface name (e.g., "Sideband Server") instead of generic type names
(e.g., "TCP/IP").

Python changes:
- Build formatted interface string "ClassName[UserConfiguredName]" from
  the interface object's type and .name attribute
- This allows Kotlin to extract both the friendly name and interface type

Kotlin changes:
- Add extractFriendlyName() to parse user-configured name from brackets
- Add extractInterfaceType() to parse class name before brackets
- Display friendly name as main text, interface type as subtitle

The card now shows:
- Title: "Received Via"
- Content: "Sideband Server" (user-configured name)
- Subtitle: "TCPClientInterface" (interface type)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 19:02:43 -05:00
torlando-tech
93d794c1fd feat: extend DiscoveredInterface data model with full RNS 1.1.x fields
Add comprehensive discovery data to DiscoveredInterface including:
- Transport/network IDs and status info (statusCode, lastHeard, hops)
- TCP-specific fields (reachableOn, port)
- Radio-specific fields (frequency, bandwidth, SF, CR, modulation)
- Location fields (latitude, longitude, height)
- Helper properties for interface type detection

Update Python bridge to return full discovery data from RNS 1.1.x API.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 18:11:18 -05:00
torlando-tech
e0dae212dc feat: add RNS 1.1.x bootstrap and interface discovery support (#216)
Add support for RNS 1.1.0+ interface discovery and bootstrap features,
enabling new users to connect without relying on deprecated public
infrastructure.

Changes:
- Add discovery settings to ReticulumConfig (discoverInterfaces,
  autoconnectDiscoveredInterfaces, interfaceDiscoverySources,
  requiredDiscoveryValue)
- Add bootstrapOnly option to TCPClient interface config
- Update Python config generation for discovery and bootstrap options
- Add get_discovered_interfaces() and is_discovery_enabled() API methods
- Extend AIDL interface with discovery methods
- Mark 3 reliable community servers as bootstrap candidates (Beleth,
  Quad4, FireZen)
- Add bootstrap toggle to TCP Client wizard UI

Note: The RNS fork needs to be rebased on 1.1.2 to enable the actual
discovery features. The code is ready but awaiting the dependency update.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:45:38 -05:00
Torlando
4af51e7c8a
Merge pull request #311 from torlando-tech/feature/issue-215-telemetry-collector-api
Add telemetry collector API with group tracking features
2026-01-21 13:49:52 -05:00
torlando-tech
2a79ed2613 Fix empty telemetry stream responses appearing in chat
Empty telemetry stream responses (0 entries from collector) were
appearing as blank messages in chat because is_location_only was
only set when the stream had entries. Now telemetry stream messages
are always marked as location-only regardless of whether they
contain data.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 11:28:37 -05:00
torlando-tech
b060512aee Add allowed telemetry requesters feature for host mode
Implements the ability to control which contacts can request telemetry
when acting as a collector (host mode). Users can select specific
contacts who are allowed to request their group's location data.
Requests from non-allowed contacts are silently blocked.

Changes:
- Add telemetry_allowed_requesters state and filtering in Python layer
- Add AIDL interface and bridge for setTelemetryAllowedRequesters
- Add SettingsRepository storage with DataStore persistence
- Add TelemetryCollectorManager state flow and Python sync
- Add SettingsViewModel state management with ContactRepository
- Add UI components: AllowedRequestersSection and AllowedRequestersDialog
- Fix ArrayList to Python list conversion in ReticulumServiceBinder
- Fix state preservation in SettingsViewModel.loadSettings()
- Add CLAUDE.md with Chaquopy ArrayList pitfall documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 10:39:49 -05:00
Torlando
3fe17c2ff9
Merge pull request #243 from torlando-tech/feature/issue-215-telemetry-collector-api
Add Telemetry Collector API support
2026-01-21 00:28:51 -05:00
Torlando
3657c288fe
Merge pull request #280 from torlando-tech/feature/usb-rnode-support
feat: USB RNode support (Issue #173)
2026-01-21 00:24:41 -05:00
torlando-tech
ba2cf44dea Add telemetry collector (host mode) for Group Tracker
Enable Columba to act as a telemetry collector compatible with Sideband's
protocol. When Host Group mode is enabled, Columba will:

- Store incoming FIELD_TELEMETRY (0x02) location data from peers
- Handle FIELD_COMMANDS (0x09) telemetry requests
- Respond with FIELD_TELEMETRY_STREAM (0x03) containing all stored entries

Implementation details:
- In-memory storage with 24-hour TTL, keeping latest per source
- Uses received_at timestamp for timebase filtering (handles clock skew)
- Follows Sideband's identity recall + path request pattern
- UI toggle in Location Sharing settings card

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 22:04:13 -05:00
torlando-tech
2a19d58f1d Add telemetry request functionality to Group Tracker
Enable requesting location data from group host (telemetry collector):

- Add send_telemetry_request() to Python wrapper using FIELD_COMMANDS
- Add path request retry logic when identity not immediately known
- Add request toggle and interval settings to UI
- TelemetryCollectorManager handles periodic location requests
- New AIDL/protocol methods for sendTelemetryRequest

UI renamed from "Telemetry Collector" to "Group Tracker" with
user-friendly labels (Group Host, Share with group, etc.)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 20:22:54 -05:00
torlando-tech
049424f75f Add FIELD_COMMANDS constant for LXMF telemetry requests
Previously Columba was using FIELD_IMAGE (0x06) for telemetry
commands, causing Sideband to interpret requests as image
attachments. Add the correct FIELD_COMMANDS constant (0x09)
per the LXMF specification.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 20:22:13 -05:00
torlando-tech
a4959d4b4a Address PR review comments for telemetry collector
- Add hex character validation for collector address (prevents crash)
- Fix periodic send race condition by awaiting in-progress sends
- Fix auto-confirm timing using LaunchedEffect for state sync
- Optimize SQL queries from O(n²) to O(n) using JOIN
- Add future timestamp rejection (>1hr) to prevent UI issues
- Add icon name validation (alphanumeric/underscore, max 50 chars)
- Add comprehensive tests for Python validation logic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 18:40:47 -05:00
torlando-tech
c1090423d6 Address remaining Greptile review comments
- Use location.time instead of System.currentTimeMillis() for accurate
  location capture timestamp in TelemetryCollectorManager
- Add timestamp validation in Python to prevent issues with None/0 values

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 18:40:47 -05:00
torlando-tech
a4db8fc4fb Add Telemetry Collector API support (#215)
Implements support for sending location telemetry to a collector and
receiving bulk telemetry streams from multiple sources.

Features:
- TelemetryCollectorManager for scheduled/manual telemetry sends
- TelemetryCollectorCard UI for configuration in Settings
- FIELD_TELEMETRY_STREAM (0x03) handling in Python layer
- Database migration for appearanceJson field on received locations
- SettingsRepository preferences for collector address, intervals, etc.

Testing:
- TelemetryCollectorManagerTest with mockk and Turbine
- TelemetryCollectorCardTest with Compose testing
- SettingsViewModelTest telemetry method tests
- SettingsRepositoryTest telemetry preference tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 18:40:47 -05:00
torlando-tech
f7307377d1 fix: auto-generate Bluetooth RNode interface names from device name
- Fix CompanionDeviceManager association path (Android 12+) not updating
  interface name when a device is selected
- Extract identifier from Bluetooth device name (e.g., "RNode 958F" -> "958F")
- Add BLE/BT suffix based on device type (e.g., "RNode 958F BLE")
- Remove unused device hash query code from KotlinUSBBridge, usb_bridge.py,
  and reticulum_wrapper.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:48:06 -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
54c6dca087 feat: add connecting spinner to interface stats and fix service binding race
Interface Stats Screen:
- Add "CONNECTING" status badge with spinner when interface is connecting
- Show spinner for up to 15 seconds on screen load if interface is offline
- Use tertiary color for connecting state to distinguish from online/offline
- Reset connecting state when interface goes offline (for replug detection)

Service Binding Fix:
- Wait up to 10 seconds for service to bind before attempting RNode reconnect
- Previously, reconnectRNodeInterface() silently did nothing if service wasn't
  bound yet, causing cold start reconnects to fail
- Add proper logging for service binding wait and timeout cases

Also adds connecting field to Python interface stats for future use.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 18:13:31 -05:00
torlando-tech
7907d07fd1 feat: add USB intent handling and interface stats screen
When a USB device is attached:
- Check if it's already configured as an RNode interface
- If configured: navigate directly to the new Interface Stats screen
- If not configured: navigate to RNode wizard with USB pre-selected

New Interface Stats screen displays:
- Online/offline status with enable/disable toggle
- Connection type and details (TCP/USB/BLE/Bluetooth)
- RNode radio settings (frequency, bandwidth, SF, TX power)
- Live traffic statistics (RX/TX bytes, RSSI, SNR)
- Edit button to modify interface configuration

Also:
- Make interface cards clickable in management screen
- Add singleTask launchMode to prevent duplicate Activity instances
- Add get_interface_stats() to Python wrapper for live stats

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 12:53:29 -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
cd345baba9 feat: add RSSI and SNR signal metrics to messages
Add signal quality metrics (RSSI and SNR) to received messages, displayed
on the message detail screen. RNode-delivered messages show both metrics;
BLE shows RSSI only (Android limitation).

Changes:
- Python layer: New signal_quality.py module for extracting metrics from
  Reticulum interfaces at message delivery time
- Database: Add receivedRssi (INTEGER) and receivedSnr (REAL) columns with
  migration 30->31
- Protocol: Parse rssi/snr from JSON in ServiceReticulumProtocol and
  EventHandler
- UI: New SignalQualityInfo utility with color-coded signal strength/quality
  display in MessageDetailScreen

Signal thresholds:
- RSSI: Excellent (>-50), Good (-50 to -70), Fair (-70 to -85), Weak (-85 to -100), Very Weak (<-100) dBm
- SNR: Excellent (>10), Good (5-10), Fair (0-5), Poor (-5-0), Very Poor (<-5) dB

Closes #209

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 21:23:46 -05:00
torlando-tech
9f9eef4c52 fix: revert to Python 3.11 and fix delivery status UI refresh
Python 3.13's socket.if_nametoindex() doesn't work on Android/Chaquopy,
causing AutoInterface to crash. Python 3.11 works correctly.

Changes:
- Revert Chaquopy from Python 3.13 to 3.11 (both app and reticulum modules)
- Use pycodec2 wheel with pure Python ctypes wrapper instead of Cython
  extension to avoid Android linker namespace symbol resolution issues
- Remove socket.if_nametoindex patch (not needed on Python 3.11)
- Add refresh trigger for delivery status updates to fix UI not showing
  double checkmarks until next message sent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
6a1f07a901 fix: prevent AutoInterface crash on Android by cleaning up adopted_interfaces
On Android, socket.if_nametoindex() is not available in Chaquopy and
the fallback via netinfo.interface_names_to_indexes() returns None.
The /sys/class/net/ifindex fallback is blocked by SELinux.

AutoInterface adds interfaces to adopted_interfaces before calling
interface_name_to_index(). When the exception was caught, the interface
remained in adopted_interfaces, causing final_init() to crash later.

The fix cleans up adopted_interfaces before raising the exception so
final_init() has nothing to iterate over for interfaces that can't
get their index. AutoInterface now gracefully degrades with a warning.

Also temporarily disabled x86_64 ABI due to wheel resolution issues
for audioop-lts and pycodec2 (only arm64 needed for real devices).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
0c91aeb442 feat: implement LXST voice calls with multi-process IPC
Complete voice call implementation for Android with cross-process support:

- Add call AIDL interface (IReticulumService/IReticulumServiceCallback)
- Implement ServiceReticulumProtocol for call IPC to :reticulum process
- Add ReticulumServiceBinder call methods bridging to Python CallManager
- Wire KotlinAudioBridge to Chaquopy audio backend for audio streaming
- Add incoming call notifications with IPC broadcast to UI process
- Fix microphone recording by adding FOREGROUND_SERVICE_MICROPHONE
- Support mute/unmute via software mute in audio bridge
- Add libopus native dependency for Opus codec support

Audio routing now works correctly between devices with earpiece default.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:06:23 -05:00
torlando-tech
fa59db20b8 feat: add LXST voice call support
Implement real-time voice calling over Reticulum using LXST Telephony:

- KotlinAudioBridge: Android AudioTrack/AudioRecord wrapper for Python
- chaquopy_audio_backend.py: Chaquopy-compatible replacement for Pyjnius soundcard
- CallBridge: Bidirectional state management between Python LXST and Kotlin UI
- call_manager.py: LXST Telephone wrapper with Kotlin callbacks

- VoiceCallScreen: Active call UI with mute/speaker controls
- IncomingCallScreen: Incoming call UI with pulsing animation
- CallViewModel: Call state management with Hilt DI
- Call button added to MessagingScreen TopAppBar

- CallNotificationHelper: Incoming/ongoing call notifications with full-screen intent
- CallActionReceiver: Notification action handling (answer/decline/end)
- AudioCoordinator: Audio focus, wake locks, proximity sensor

- RECORD_AUDIO for microphone access
- MODIFY_AUDIO_SETTINGS for audio routing
- USE_FULL_SCREEN_INTENT for incoming call overlay

Supports all LXST quality profiles (Opus/Codec2) for bandwidth-adaptive calls.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:04:40 -05:00
Torlando
abf18472fd
Merge pull request #274 from torlando-tech/feature/settings-about-section
feat: add comprehensive About section to Settings (#262)
2026-01-16 20:20:04 -05:00
torlando-tech
3e3e7041d6 feat: add comprehensive About section to Settings
Implements issue #262 - adds a detailed About section at the bottom of
the Settings screen displaying app information, device details, protocol
versions, identity info, links, and build metadata.

Features:
- App information (version, build code, git commit, build date)
- Device information (Android version, API level, model, manufacturer)
- Protocol versions (Reticulum, LXMF, BLE-Reticulum)
- Identity hash display
- Links to GitHub repository, issue tracker, and Reticulum website
- Legal information and dependency attribution
- Copy to clipboard functionality for system info

Technical changes:
- Add BuildConfig fields for git commit hash and build timestamp
- Create DeviceInfoUtil for collecting and formatting system info
- Add Python methods to retrieve protocol versions
- Add AIDL and Kotlin wrappers for protocol version retrieval
- Update SettingsViewModel with retry logic for fetching versions
- Create AboutCard composable with comprehensive UI
- Add 23 unit tests (8 for DeviceInfoUtil, 15 for AboutCard)

All quality checks passing (detekt, ktlint, cpd, unit tests).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 19:48:40 -05:00
torlando-tech
431d139e0b fix: don't pass None to LXMF set_outbound_propagation_node()
LXMF's set_outbound_propagation_node() expects a valid bytes hash and
calls len() on it, which fails with TypeError when passed None.

When clearing the propagation node, just clear our internal tracking
variable instead of calling the LXMF method. LXMF will handle the case
where no valid propagation node is set.

Fixes error: "object of type 'NoneType' has no len()"

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 17:36:27 -05:00
torlando-tech
0d452252a1 fix(#257): add Python tracking + Kotlin guard for propagated message status
Two-layer defense against spurious failure callbacks for propagated messages:

Python layer:
- Track successfully propagated messages in _successfully_propagated dict
- Guard in _on_message_failed to ignore failures for tracked messages
- Cleanup stale tracking entries (24h TTL)
- Immediate state check after propagation retry

Kotlin layer:
- Add isTerminalSuccessStatus() helper
- Guard in handleDeliveryStatusUpdate to block status degradation
  from terminal success states (sent/propagated/delivered) to failed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:55:42 -05:00
torlando-tech
d8a924862a fix: emit 'propagated' status for PROPAGATED method messages
When messages are sent via propagation node, the status should update
to "propagated" (not "sent") when the relay confirms receipt.

Changes:
- Modify _on_message_sent() to check desired_method and emit
  'propagated' status for PROPAGATED method messages
- Add state check after propagation retry in _on_message_failed()
  to detect immediate propagation success
- Handle pending file notifications when propagation is confirmed

This fixes the issue where Message Details showed "Sent" for messages
that were successfully propagated to a relay node.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:55:42 -05:00
torlando-tech
b6e5f331a6 fix: consistent filtering of deprecated propagation nodes in all queries
Problem: Auto-select relay was enabled but no relay was being selected.
Root cause: Different queries had inconsistent filtering:
- getAllAnnouncesPaged() had NO filter - showed deprecated nodes
- getAnnouncesByTypes() filtered deprecated nodes (stampCostFlexibility IS NULL)
- This caused relays to appear in announce stream but not be available for auto-selection

Changes:
- Make all announce queries consistently filter deprecated propagation nodes
- Add logging when pn_announce_data_is_valid() returns False (Python)
- Add debug logging to PropagationNodeManager for auto-selection diagnosis

Deprecated nodes are propagation nodes running old LXMF versions that
don't include stamp cost data in their announces. These cause sync failures
so they are intentionally filtered from relay selection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:42:59 -05:00
torlando-tech
de43ce9c92 Fix receiving interface capture for AutoInterface messages
AutoInterfacePeer uses 'ifname' attribute (network interface name like
'wlan0') instead of 'name'. For the first opportunistic message before
path_table is populated, the interface was not being captured.

Changes:
- Use class name (type().__name__) to identify interface type reliably
- Extract shared interface display logic to InterfaceUtils.kt
- Both MessageDetailScreen and AnnounceDetailScreen use same utility
- AutoInterface class names map to "Local Network" with Wifi icon

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:50:36 -05:00
Tyler Orlando
6d511bd0db Fix Python tests by adding type checking for Mock objects
- Add module-level LXMF field constants (FIELD_FILE_ATTACHMENTS,
  FIELD_IMAGE, FIELD_AUDIO) so code works without LXMF imported
- Use local booleans to track captured hops/interface instead of
  hasattr() which always returns True for Mock objects
- Add isinstance() checks for hop count (int), interface name (str),
  and public key (bytes) before using them
- This ensures JSON serialization doesn't fail when tests use
  MagicMock objects that auto-create attributes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-13 09:56:21 -05:00
torlando-tech
1c1bb78a32 Capture receiving interface and hops for opportunistic messages
For opportunistic (single-packet) messages that arrive before the path_table
is populated, we now capture the receiving interface and hop count directly
from the LXMF message object. This is enabled by a patch to our LXMF fork
that passes packet.receiving_interface and packet.hops to the delivery callback.

Changes:
- Update LXMF dependency to feature/receiving-interface-capture branch
- Check for receiving_interface and receiving_hops attributes on LXMessage first
- Fall back to path_table lookup for link-based messages (unchanged behavior)

This fixes the issue where messages received before an announce wouldn't have
hop count or interface info in the Message Details screen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:59:47 -05:00
torlando-tech
82f3f7aef7 Fix race condition: register message callback before Python init
Move message received callback registration from setupBridges() to
setupPreInitializationBridges() so it's registered BEFORE Python's
LXMF router starts. This ensures messages that arrive immediately
after initialization use the event-driven path with hop count and
interface data, rather than falling back to polling.

Also clarify in comments that hop/interface capture is only available
when a path exists in path_table (opportunistic messages without an
established path won't have this info).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 23:24:16 -05:00
torlando-tech
21695ebdbb Make message receiving truly event-driven (no polling)
Python callback now sends full message data including content, fields,
public key, hop count, and receiving interface. Kotlin processes the
message directly from the callback JSON instead of polling back to
Python.

This eliminates the round-trip poll and makes message delivery truly
event-driven.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:09:04 -05:00
torlando-tech
9ae5e052f5 Fix interface capture when interface.name is None
The interface object may have a name attribute that is None, resulting
in "None" being captured as the interface name. Now checks that
interface.name is truthy before using it, and skips storing if the
result is "None".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:04:31 -05:00
torlando-tech
59b2c78c42 Include hop count and interface in event-driven callback
The kotlin_message_received_callback was missing hops and receiving_interface
fields. While the current flow triggers a poll that also includes this data,
including it in the callback makes the data available for a future true
event-driven implementation without polling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:55:47 -05:00
torlando-tech
012154bd6d Fix race condition in path_table access using .get()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
90f61ffc23 Capture receiving interface for all messages, not just direct
The path table stores the interface through which we received the
message, regardless of hop count. For multi-hop messages, this is
the interface of the last relay that delivered to us.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
53d72b3ecd Capture hop count at delivery time instead of poll time
RNS.Transport.hops_to() returns current routing info which may change
after message reception. Now capture hop count and interface immediately
in _on_lxmf_delivery() and store on message object for later retrieval.

This ensures hop count reflects the actual path taken at reception time,
not a potentially different path discovered later.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
f4f3fb6e9f Only capture receiving interface for direct messages (hops=0)
Path table stores outbound routing info, not reception history.
For multi-hop messages, path_entry[5] is the next-hop interface for
sending TO the sender, not the interface that received the message.
Interface info is only accurate when hops=0 (direct delivery).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
1b871804b5 Validate hop count before storing (skip None or negative values)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
ea73038939 Use interface .name attribute in announce handlers too
Apply same fix to _announce_handler and poll_received_announces
for consistency across all interface name extraction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:51 -05:00
torlando-tech
3813ad1936 Use interface .name attribute for cleaner interface names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:50 -05:00
torlando-tech
5db8606bf0 Add hop count and receiving interface to received message info
Capture network metadata when messages are received and display it on the
message info screen:

- Add receivedHopCount and receivedInterface columns to MessageEntity
- Create database migration 28→29
- Capture hop count via RNS.Transport.hops_to() at message reception
- Capture receiving interface from RNS path table at message reception
- Pass new fields through EventHandler and ServicePersistenceManager
- Update Message, MessageUi models and mappers
- Display hop count (Direct/N hops) and interface (WiFi/BLE/LoRa/TCP)
  on received message info screen with appropriate icons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:49:50 -05:00
torlando-tech
75f0d6addc Fix race condition in path_table and announce_table access
Use .get() instead of checking membership then accessing, which
eliminates the race condition where another thread could remove
the entry between the two operations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 22:15:03 -05:00
torlando-tech
8c53d12d21 Add RMSP tile download via AIDL with multi-geohash support
Implements RMSP (Reticulum Map Service Protocol) tile fetching through
the cross-process AIDL interface, enabling offline map downloads over
mesh networks.

Key changes:
- Add fetchRmspTiles() and getRmspServers() to IReticulumService.aidl
- Implement RMSP methods in ReticulumServiceBinder with Python interop
- Add ServiceReticulumProtocol.fetchRmspTiles() for suspend function access
- Update ReticulumModule to expose ServiceReticulumProtocol for injection
- Add TileSource.Rmsp sealed class for RMSP tile source abstraction
- Implement multi-geohash coverage calculation for complete region downloads
  - decodeGeohashBounds() to get bounding box from geohash
  - geohashesForBounds() to find all cells covering a region
- Update downloadRegionRmsp() to iterate over all geohash cells
- Add RMSP server discovery UI in OfflineMapDownloadScreen
- Update rmsp_client.py with announce waiting and path resolution
- Add public key parameter for server identity reconstruction

The RMSP download now fetches tiles for all geohash cells covering the
requested region, matching HTTP download coverage behavior.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:09:31 -05:00