Commit graph

83 commits

Author SHA1 Message Date
torlando-tech
3b490ca58d test: add coverage tests for ensure_advertising method
Add tests for the new ensure_advertising() method that covers all
code paths in android_ble_driver.py:
- No bridge returns False with warning log
- Bridge returns True when advertising active
- Bridge returns False triggers restart log
- Exception caught and logged, returns False

Tests import and exercise the real AndroidBLEDriver class to provide
actual codecov coverage on the source file.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 15:08:00 -05:00
torlando-tech
5baa34032e fix: add proactive BLE advertising refresh to prevent silent stops
Android silently stops BLE advertising when the app goes to background,
screen turns off, or device enters Doze mode - without calling any
callback. This leaves the app thinking it's advertising when it's not.

Add a proactive refresh mechanism that restarts advertising every 60
seconds to ensure the device remains discoverable. Also add bridge
methods to allow Python to check/ensure advertising is active.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:45:47 -05:00
torlando-tech
1f15d4742e refactor: use ble-reticulum as GitHub pip dependency
Replace submodule with pip install from GitHub. This simplifies
dependency management and avoids namespace collision with RNS.Interfaces.

Changes:
- Remove external/ble-reticulum submodule
- Update build.gradle.kts to install from GitHub branch
- Update reticulum_wrapper.py to use ble_reticulum package
- Remove duplicate BLE files from python/ble_modules/
- Fix KotlinBLEBridge address callback deduplication

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 23:50:10 -05:00
torlando-tech
ec49b2a155 test: add comprehensive telemetry format tests
Add 20 new tests for improved coverage:
- Edge cases: None input, empty bytes, short arrays, wrong types
- Sideband compatibility: exact format, extra sensors, missing time
- LXMF extraction: FIELD_TELEMETRY, FIELD_COLUMBA_META, legacy field 7
- Send format: telemetry fields, cease signals, expires metadata
- Timestamp handling: ms/s conversion, zero timestamp

Total: 56 tests (was 36)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 17:50:57 -05:00
torlando-tech
4c75f9d080 fix: remove redundant local imports causing telemetry callback failure
The `import json` statements inside _on_lxmf_delivery() caused Python
to treat `json` as a local variable throughout the function scope.
This resulted in "cannot access local variable 'json'" errors when
json.dumps() was called before those imports ran.

Since json and time are already imported at module level, the local
imports were unnecessary and harmful.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 17:36:43 -05:00
torlando-tech
b7d9cebc75 fix: use Sideband-compatible telemetry format for location sharing
Fixes #155 - Position sharing was being interpreted as "audio messages"
by Sideband because Columba was using the wrong LXMF field and encoding.

Changes:
- Switch from FIELD_AUDIO (0x07) to FIELD_TELEMETRY (0x02)
- Use msgpack-packed Telemeter binary format instead of JSON
- Add FIELD_COLUMBA_META (0x70) for Columba-specific signals (cease)
- Add backwards compatibility for receiving legacy field 7 messages
- Add u-msgpack-python dependency for binary serialization
- Add 36 unit tests for pack/unpack round-trip verification

The telemetry format now matches Sideband's sense.py Location sensor:
- Latitude/longitude as signed int microdegrees
- Altitude/speed/bearing/accuracy in centimeter units
- Unix timestamp in seconds

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 16:10:33 -05:00
torlando-tech
9cd180fd7d test: add comprehensive tests for msgpack-affected code paths
Add tests to verify msgpack 0.9.8 -> 0.9.10 upgrade doesn't break:

NodeTypeDetectorTest.kt (49 tests):
- Aspect-based node type detection (6 tests)
- appData string pattern matching (8 tests)
- msgpack format byte detection for propagation nodes (18 tests)
- LXMF display name format validation (6 tests)
- getNodeTypeDescription coverage (3 tests)
- Edge cases and boundary conditions (8 tests)

test_announce_handler.py additions (8 tests):
- Successful propagation node stamp cost extraction
- umsgpack.unpackb exception handling
- Index out of bounds scenarios (data[5], data[5][1/2])
- Type conversion error handling
- Large value boundary testing
- Empty app_data handling
- pn_announce_data_is_valid=False code path

Also fixes outdated tests that referenced deprecated _announce_handler_ref
attribute (now uses _announce_handlers dict).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:57:45 -05:00
torlando-tech
526685cc26 test: add coverage for heartbeat, exception handler, and persistence fields
- Add test_wrapper_heartbeat.py with 19 tests for Python heartbeat thread
  and global exception handler
- Add DebugViewModelPersistenceTest.kt with tests for new persistence
  debug info fields (heartbeatAgeSeconds, healthCheckRunning, etc.)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 22:03:19 -05:00
torlando-tech
99b49a6dec feat: add process persistence improvements inspired by Sideband
Implements high and medium priority improvements to ensure the Reticulum
process stays running indefinitely, matching Sideband's robustness:

High Priority:
- Add Python heartbeat thread (1-second updates) with Kotlin health check
  monitoring (5-second polls, restart on 10-second stale threshold)
- Reduce lock renewal frequency from 9 hours to 5 minutes
- Add explicit service auto-restart in onDestroy()

Medium Priority:
- Add NetworkChangeManager to detect connectivity changes and reacquire
  locks, triggering LXMF announce on network transitions
- Add Python maintenance thread for interface auto-reinit (60-second retry)
- Add global exception handler via sys.excepthook for crash logging

New files:
- HealthCheckManager.kt - monitors Python heartbeat, triggers restart
- NetworkChangeManager.kt - detects network changes, reacquires locks
- HealthCheckManagerTest.kt - 16 unit tests
- NetworkChangeManagerTest.kt - 13 unit tests

Modified files:
- reticulum_wrapper.py - heartbeat, maintenance threads, exception handler
- MaintenanceManager.kt - 5-minute refresh interval (was 9 hours)
- ReticulumService.kt - auto-restart, stale heartbeat handling
- ServiceModule.kt - new manager instantiation
- ReticulumServiceBinder.kt - integrate new managers
- PythonWrapperManager.kt - getHeartbeat() method

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:31:17 -05:00
torlando-tech
3551e6bd79 fix: remove undefined announce_app_data attribute in shutdown
Fixes #40

The shutdown() method was trying to clear self.announce_app_data,
but this attribute was never initialized in __init__. This caused
an AttributeError during shutdown, preventing clean Python wrapper
cleanup.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 20:42:32 -05:00
torlando-tech
25e91930f9 fix: Add fields mock to LXMF delivery tests
The reaction detection code checks lxmf_message.fields, so tests
need to mock this attribute to avoid TypeError.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 17:34:10 -05:00
torlando-tech
98a7b1e3e9 auto-claude: subtask-4-3 - Add Python tests for reaction send/receive
Add comprehensive test suite for ReticulumWrapper reaction methods:

- TestSendReaction: Tests send_reaction method including success case,
  not initialized, no router, various emojis, identity not found,
  cached identity fallback, Reticulum unavailable, and callback registration

- TestReactionReceiveCallback: Tests _on_lxmf_delivery reaction detection,
  skipping regular message processing, missing callback handling,
  regular messages not treated as reactions, callback error handling,
  and various emoji types including ZWJ sequences

- TestReactionReceivePolling: Tests poll_received_messages marking
  reaction messages with is_reaction flag and reaction fields

- TestSetReactionReceivedCallback: Tests callback registration

- TestReactionField16Structure: Verifies Field 16 contains required
  keys (reaction_to, emoji, sender) with correct values

- TestReactionEdgeCases: Tests jarray conversion, empty emoji,
  missing sender, partial Field 16, and exception handling

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 20:46:18 -05:00
torlando-tech
618eea848a auto-claude: subtask-4-2 - Update message receive handling to parse incoming reactions
- Add kotlin_reaction_received_callback field for event-driven reaction notifications
- Add set_reaction_received_callback() method following location callback pattern
- Update _on_lxmf_delivery() to detect Field 16 reactions (reaction_to key)
  - Parse reaction data (reaction_to, emoji, sender, source_hash, timestamp)
  - Invoke Kotlin callback for instant notification
  - Skip regular message processing for reaction-only messages
- Update poll_received_messages() to flag reaction messages with:
  - is_reaction: true
  - reaction_to, reaction_emoji, reaction_sender fields

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 20:41:59 -05:00
torlando-tech
9dd85675b8 auto-claude: subtask-4-1 - Add send_reaction method to ReticulumWrapper class
Add send_reaction(dest_hash, target_message_id, emoji, source_identity_private_key)
method for sending emoji reactions via LXMF. The method:
- Creates lightweight LXMF messages with Field 16 containing reaction data
- Uses OPPORTUNISTIC delivery for fast transmission of small reaction messages
- Follows the pattern of send_lxmf_message_with_method for identity handling
- Stores reaction_to, emoji, and sender hash in app extensions dict

Field 16 format: {"reaction_to": "msg_id", "emoji": "👍", "sender": "hash"}

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 20:38:34 -05:00
torlando-tech
04d7acf622 feat: add reply-to-message feature (WIP)
- Add LXMF Field 16 support for extensible app extensions
- Add replyToMessageId column to MessageEntity with migration 23→24
- Add swipe-to-reply gesture component (toward center)
- Add ReplyPreviewBubble and ReplyInputBar UI components
- Add async reply preview loading in MessagingViewModel
- Update MessagingScreen with reply UI integration
- Add 17 tests for reply parsing in MessageMapper
- Add 11 tests for reply UI components

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 14:17:06 -05:00
torlando-tech
7f0e517ec0 feat: implement location sharing with cease message support
Add bidirectional location sharing between contacts:
- LocationSharingManager handles outgoing sharing sessions
- LocationTelemetry model with cease flag for stop notifications
- ReceivedLocationDao/Entity for storing incoming locations
- ContactLocationBottomSheet for viewing contact details on map
- MapViewModel combines contacts + announces for display names
- Python wrapper filters location-only messages from chat view

When a user stops sharing, a cease message is sent to recipients
causing immediate marker removal instead of waiting for expiry.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 19:02:52 -05:00
torlando-tech
292c0aaf54 test: add coverage tests for file attachments and FileUtils
- Add 9 pytest tests for file attachment handling in reticulum_wrapper
- Add 14 MockK tests for FileUtils.readFileFromUri and getFilename
- Tests cover list/tuple/dict formats, binary conversion, error handling

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 18:32:54 -05:00
torlando-tech
39487bf64c feat: add file transfer support via LXMF Field 5
Implement sending and receiving file attachments of any type using
LXMF Field 5 (FILE_ATTACHMENTS). Key features:

- Send any file type via file picker (multiple files supported)
- 512KB combined size limit (same as images)
- File display card with type icon, filename, and size
- Size indicator with visual feedback when approaching limits
- Tap received files to save them

Technical changes:
- Add FileAttachment data class and FileUtils utilities
- Add FileAttachmentCard and FileAttachmentPreviewRow UI components
- Extend MessagingViewModel with file attachment state management
- Update Python wrapper to handle Field 5 send/receive
- Add Field 5 parsing to MessageMapper
- Update protocol layer to pass file attachments through
- Handle Java ArrayList to Python list conversion in wrapper

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 17:29:22 -05:00
torlando-tech
ee6fabc58b refactor: remove message polling entirely, use startup drain
Replace continuous 2-30s fallback polling with a one-time startup drain.
The pending_inbound queue already handles messages that arrive before
callback registration, so we only need to drain it once at startup.

Changes:
- Remove startMessagesPolling() loop entirely from PollingManager
- Remove messagesPoller SmartPoller (no longer needed)
- Add drainPendingMessages() for one-time startup drain
- Update ReticulumServiceBinder to call drain instead of startPolling
- Remove messagePollingJob from ServiceState
- Update Python docstrings to reflect new architecture

Architecture is now:
- Startup: drain any queued messages
- Runtime: 100% event-driven via callbacks
- No continuous polling for messages

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 21:55:03 -05:00
torlando-tech
9e119fc890 refactor: remove 1s message polling, use event-driven callbacks
Message delivery is now primarily event-driven via Python callbacks.
The aggressive 1s conversationPoller is removed since the event-driven
callback infrastructure is already in place and working:

- ReticulumServiceBinder registers kotlin_message_received_callback
- Python _on_lxmf_delivery() invokes callback for immediate delivery
- PollingManager.handleMessageReceivedEvent() processes the events

Changes:
- Remove conversationPoller (1s fixed interval) from PollingManager
- Keep messagesPoller (2-30s adaptive) as fallback safety net
- Simplify setConversationActive() to just track state
- Reduce verbose debug logging in poll_received_messages()
- Remove PATH TABLE DIAGNOSTIC spam from Python polling

This significantly reduces battery drain when conversations are active
by eliminating the Python/Kotlin boundary crossing every second.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 21:40:43 -05:00
torlando-tech
2a1de3311b test: add Python tests for stamp generator callback
Added 5 Python tests for set_stamp_generator_callback:
- Callback storage in instance variable
- Registration with LXMF LXStamper
- Graceful handling of import errors
- Graceful handling of registration errors
- Setting callback to None (clearing)

Also fixed detekt EqualsNullCall violation in StampGeneratorTest.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 18:52:21 -05:00
torlando-tech
5a2356bbfc feat: Add native Kotlin stamp generator for Android
Replace Python multiprocessing-based stamp generation with native Kotlin
implementation. Python multiprocessing fails on Android due to lack of
sem_open support and aggressive process killing.

Changes:
- Add StampGenerator.kt with HKDF, SHA256, and parallel stamp search
- Add StampGeneratorTest.kt with Python-generated test vectors
- Add callback in PythonWrapperManager to bridge Python to Kotlin
- Register stamp generator in ReticulumServiceBinder
- Update requirements.txt to use LXMF fork with external generator support
- Add msgpack-core dependency for MessagePack encoding

Performance: ~9300 rounds/sec (vs ~1400 with broken Python multiprocessing)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 18:29:59 -05:00
torlando-tech
6506042600 test: Improve Python test coverage for LXStamper patch and SENT state check
Added comprehensive integration tests that actually execute the production code:

LXStamper Threading Patch Tests:
- test_initialize_patches_lxstamper_job_android: Calls initialize() and then
  exercises the patched job_android function with low difficulty
- test_initialize_lxstamper_cancellation_path: Tests cancellation via active_jobs
- Fixed module mocking to properly set mock_lxmf.LXStamper attribute

SENT State Check Tests:
- test_send_does_not_call_on_message_sent_for_outbound_state
- test_send_handles_missing_state_attribute_gracefully
- test_send_handles_state_check_exception_gracefully
- test_send_with_propagated_state_does_not_trigger_sent_callback

Coverage increased from 81% to 83%.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 14:17:23 -05:00
torlando-tech
d9c1a194a0 test: Fix test_send_with_immediate_sent_state_check mock setup 2025-12-16 14:17:12 -05:00
torlando-tech
a89cb1eaee test: Add unit tests for LXStamper threading patch and state check
Add test coverage for the threading-based stamp generation and
immediate SENT state check to improve patch coverage.

Tests added:
- test_lxstamper_patched_to_use_threading: Verify LXStamper.job_android is patched
- test_lxstamper_patch_graceful_failure: Verify graceful failure handling
- TestLXStamperThreading: Test threading-based stamp generation behavior
- test_send_with_immediate_sent_state_check: Test SENT state detection

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 14:17:06 -05:00
torlando-tech
7db2aa714b fix: Update message status immediately for propagated messages
When sending messages via propagation (directly to relay), the message
status was stuck at "pending" because send_lxmf_message_with_method()
didn't check for immediate SENT state transition like the older
send_lxmf_message() method does.

For PROPAGATED messages, the relay accepts the message immediately and
LXMF transitions to SENT state. However, the end recipient never sends
a delivery confirmation (they pull from the relay), so we must check
the state immediately after handle_outbound() and invoke the status
callback.

This fix adds the same state check that exists in send_lxmf_message()
to send_lxmf_message_with_method(), ensuring propagated messages show
"sent" status (single checkmark) instead of staying "pending".

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 14:11:23 -05:00
torlando-tech
c3eb6aac00 test: Add unit tests to improve patch coverage
- Add ContactToggleResult emission tests to MessagingViewModelTest
- Add public key extraction tests to test_wrapper_messaging.py
- Create MessageCollectorTest.kt for public key handling

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 18:53:42 -05:00
torlando-tech
a83ac4f0e0 fix: Show accurate toast feedback when saving contacts
Previously, MessagingScreen showed "Saved to Contacts" immediately
without waiting for the async operation result. If the peer's public
key wasn't available (e.g., they never announced), the save would
silently fail despite showing success.

Changes:
- Add ContactToggleResult sealed class to emit success/error from toggleContact()
- Update MessagingScreen to collect results and show accurate toasts
- Extract sender's public key from RNS identity cache in poll_received_messages()
- Add publicKey field to ReceivedMessage and pass through the pipeline
- Store public key from messages in peer_identities table

Now users see "Identity not available - peer hasn't announced" when
save fails, and contacts can be saved from message senders even if
they never announced.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 18:01:50 -05:00
Torlando
47b1955d74
Merge pull request #95 from torlando-tech/feature/bulk-peer-restore
perf: Optimize peer identity restoration from minutes to milliseconds
2025-12-15 10:40:03 -05:00
torlando-tech
aa0f757953 fix: Enable async TCP startup to prevent initialization timeout
Configure TCPClientInterface.SYNCHRONOUS_START = False during RNS import
to run TCP connections in background threads instead of blocking.

Problem: Reticulum initialization was taking 15.7s, exceeding the 15s
ANR timeout. The main cause was synchronous TCP connection attempts to
unreachable hosts, each blocking for up to 5 seconds.

Solution: Set SYNCHRONOUS_START = False after importing RNS but before
calling RNS.Reticulum(). This makes TCP connections non-blocking while
preserving all reconnection logic (initial_connect() already handles
success/failure paths and spawns reconnect threads as needed).

Result: Initialization time reduced from 15.7s to 11.9s, well under
the 15s timeout threshold.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 10:08:37 -05:00
torlando-tech
d71de9f86f perf: Optimize peer identity restoration from minutes to milliseconds
"Apply Changes" was taking 4.5+ minutes with ~4000 peer identities because
the previous implementation created full RNS objects for each peer:
- Created RNS.Identity from public key
- Created RNS.Destination for LXMF delivery
- Registered with RNS.Transport
- Called RNS.Identity.recall() to verify

But RNS's Identity.known_destinations is just a dict that remember() populates
directly. This change bypasses the expensive object creation and directly
populates the dict.

## Changes

### Python (`reticulum_wrapper.py`)
- Add `bulk_restore_announce_identities()`: Direct dict population for announces
  where destination_hash is already available (no computation needed)
- Add `bulk_restore_peer_identities()`: Lightweight hash computation for peer
  identities - computes LXMF delivery destination hash from public key without
  creating full RNS objects

### Kotlin
- Add `restoreAnnounceIdentities()` to AIDL interface, binder, and protocol
- Update `MessagingManager` to call new bulk Python methods
- Update `InterfaceConfigManager` to use appropriate bulk restore for each type

### Tests
- Add 25 new Python tests for bulk restore functions covering:
  - Success cases with valid data
  - Invalid inputs (missing fields, invalid hex, invalid base64)
  - Empty lists
  - Large batch performance
  - Equivalence with Identity.remember() dict format

## Performance Results (tested with ~4000 identities)

| Operation | Before | After |
|-----------|--------|-------|
| 1832 peer identities | ~4.5 min | 88ms |
| 2080 announce identities | (included above) | 142ms |
| **Total** | ~4.5 min | **~230ms** |

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 22:13:35 -05:00
torlando-tech
7091fbe67f test: Add 30 tests to improve TCP RNode patch coverage to 80%+
Add comprehensive test coverage for TCP RNode functionality:

RNodeWizardViewModelTest.kt (+16 tests):
- getPopularPresetsForRegion tests for all region types (US, AU, EU sub-bands)
- Tests for regions with no presets (Brazil, Russia, Japan)
- Asia-Pacific region filtering tests
- Frequency band exclusion tests

ServiceReticulumProtocolTest.kt (+2 tests):
- buildConfigJson includes TCP host/port for RNode
- buildConfigJson omits tcp_host when null (Bluetooth mode)

InterfaceRepositoryTest.kt (+1 test):
- Validates TCP RNode with invalid hostname format is skipped

test_wrapper_config.py (+6 tests):
- TCP RNode config generation with all LoRa parameters
- Airtime limits (st_alock, lt_alock) handling
- Interface mode handling (gateway, boundary, full)

test_wrapper_ble.py (+5 tests):
- get_paired_rnodes success/error/exception handling
- Empty device list and null response handling

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 01:58:19 -05:00
torlando-tech
907c28bb27 test: Improve patch coverage from 20.94% to 80%+ with 61 new tests
Add comprehensive test coverage for TCP RNode functionality, focusing on uncovered branches and edge cases:

- RNodeWizardViewModel (13 tests): TCP validation, RSSI polling, pairing retry, CDM association, manual device name validation
- DeviceDiscoveryStep (18 tests): TCP mode UI, manual entry forms, device card interactions, edit mode
- ReviewConfigStep (8 tests): New test file for config review UI, region cards, duty cycle warnings, advanced settings
- reticulum_wrapper.py (12 tests): Error handling, callback registration, BLE path cleanup, state transitions
- Stub modules (5 tests): New test file for usb4a, jnius, usbserial4a import validation
- InterfaceRepository (5 tests): UDP/AutoInterface/AndroidBLE validation edge cases

All 61 tests pass successfully. Coverage improvements target TCP validation error paths, UI state management, Python error handling, and configuration validation.

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

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2025-12-14 01:30:09 -05:00
torlando-tech
271a9e3afb fix: Correct TCP RNode config format and add stub modules
## Config Format Fix

The Android RNodeInterface expects `tcp_host = hostname` as a config
field, not `port = tcp://hostname`. This is because the Android
interface's __init__() reads `tcp_host` to set `use_tcp = True`,
which then bypasses USB/Bluetooth code paths entirely.

Changed reticulum_wrapper.py to output:
```
tcp_host = 10.0.0.9
```
Instead of:
```
port = tcp://10.0.0.9
```

## Stub Modules for Android RNodeInterface

Added minimal stub modules required for Android RNodeInterface
initialization on Chaquopy:

- python/usbserial4a/__init__.py - Stub serial4a class
- python/jnius/__init__.py - Stub autoclass() function
- python/usb4a/__init__.py - Stub usb class with get_usb_device()

These stubs satisfy unconditional import checks in Android
RNodeInterface.__init__() that happen BEFORE TCP mode is evaluated.
When use_tcp=True, the actual USB/Bluetooth code paths are never
executed, so the stubs don't need full implementations.

## Additional Fixes

- ServiceReticulumProtocol.kt: Add missing tcp_host/tcp_port serialization
- ReviewConfigStep.kt: Show WiFi icon and "WiFi / TCP" for TCP mode
- RNodeWizardViewModel.kt: Add helper methods for TCP mode detection
- DeviceDiscoveryStep.kt: Remove port field (hardcoded in RNS)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 00:10:49 -05:00
torlando-tech
599e0753ea feat: Add TCP/WiFi connectivity for RNode devices
Add support for connecting to WiFi-enabled RNode devices via TCP as an
alternative to Bluetooth. This enables RNodes with WiFi chips to be
accessed over the network using the standard Reticulum RNodeInterface.

## Data Model Changes

- Add `tcpHost` and `tcpPort` fields to `InterfaceConfig.RNode`
- Update `connectionMode` to support "tcp" in addition to "classic"/"ble"
- Extend serialization/deserialization in InterfaceRepository with validation

## UI Changes (DeviceDiscoveryStep)

- Add connection type selector with "Bluetooth" and "WiFi / TCP" chips
- Add TcpConnectionForm composable with:
  - IP address or hostname input field
  - Port input field (default: 7633)
  - "Test Connection" button with validation feedback
- Extract BluetoothDeviceDiscovery as separate composable
- Only start Bluetooth scanning when in Bluetooth mode

## ViewModel Changes (RNodeWizardViewModel)

- Add RNodeConnectionType enum (BLUETOOTH, TCP_WIFI)
- Add TCP state fields: tcpHost, tcpPort, validation state
- Add methods: setConnectionType(), updateTcpHost(), updateTcpPort(),
  validateTcpConnection()
- Update canProceed() to handle TCP mode
- Update saveConfiguration() to create TCP config
- Update loadExistingConfig() to restore TCP settings when editing

## Python Config Generation (reticulum_wrapper.py)

- For TCP mode: write to config file using standard RNodeInterface
  format with `port = tcp://host:port`
- For Bluetooth mode: continue using ColumbaRNodeInterface (unchanged)

## Technical Notes

TCP connections can use Reticulum's standard RNodeInterface since TCP
sockets are standard Python (no jnius/Android-specific code needed).
The custom ColumbaRNodeInterface remains necessary only for Bluetooth
connections due to Android Bluetooth API requirements.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:57:59 -05:00
torlando-tech
0f67b57202 test: Improve transport node toggle code coverage
Add additional unit tests for the transport node feature:

InterfaceConfigManager (3 tests):
- Verify getTransportNodeEnabled() is called during config apply
- Verify enableTransport=true is passed to config when enabled
- Verify enableTransport=false is passed to config when disabled

ServiceReticulumProtocol (3 tests):
- Verify enableTransport defaults to true in ReticulumConfig
- Verify enableTransport can be set to false
- Verify enableTransport can be set to true explicitly

Python get_debug_info (3 tests):
- Verify transport_enabled returns true from RNS when enabled
- Verify transport_enabled returns false from RNS when disabled
- Verify transport_enabled returns false when not initialized

Related to #49

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 21:48:55 -05:00
torlando-tech
e6b4d670e3 feat: Add transport node toggle in settings
Add a settings toggle to enable/disable Reticulum transport node mode.
When enabled (default), the device forwards traffic for the mesh network.
When disabled, only handles its own traffic without relaying for peers.

Changes:
- Add transportNodeEnabled preference in SettingsRepository
- Add enableTransport field to ReticulumConfig model
- Add state and toggle method in SettingsViewModel
- Pass setting through InterfaceConfigManager and ColumbaApplication
- Update Python wrapper to use config value instead of hardcoded yes
- Fix get_debug_info() to read actual RNS transport status
- Add toggle UI in NetworkCard with Hub icon and description
- Wire up toggle in SettingsScreen with service restart on change

Tests:
- Add 6 SettingsViewModel tests for transport node toggle
- Add 11 Python tests for transport node config generation
- Add 5 ReticulumConfig model tests for enableTransport field

Closes #49

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 21:48:55 -05:00
torlando-tech
82a395f563 fix: Distinguish propagated vs delivered status for relay messages
When messages are sent via propagation (relay), LXMF sets state=SENT
when the relay accepts the message, not state=DELIVERED. Previously,
our callback ignored this distinction and always reported "delivered",
misleading users into thinking the recipient had received the message.

Changes:
- Check lxmf_message.state in _on_message_delivered() callback
- Report "propagated" status when state=SENT (relay accepted)
- Report "delivered" status only when state=DELIVERED (recipient confirmed)
- UI shows single checkmark for propagated (same as sent)
- Added unit tests for both direct and propagated delivery scenarios

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 00:50:04 -05:00
torlando-tech
2c56e98ba5 feat: Add automatic relay fallback and manual retry for failed messages
When a selected relay is offline and message propagation fails, the system
now automatically tries alternative relays before marking the message as
permanently failed. Also adds a manual "Retry" option in the message context
menu for failed messages.

Key changes:
- Python: Modified _on_message_failed() to request alternative relays from
  Kotlin when propagation fails, with tracking to prevent infinite loops
- Kotlin: Added getAlternativeRelay() to PropagationNodeManager to find the
  nearest available relay excluding previously tried ones
- IPC: Added onAlternativeRelayRequested callback and provideAlternativeRelay
  method for Python-Kotlin communication
- UI: Added "Retry" menu item for failed messages and retryFailedMessage()
  in MessagingViewModel

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 00:01:13 -05:00
torlando-tech
73d35b0754 test: Add path request retry logic tests
Add 3 tests for the identity lookup retry mechanism in send_lxmf_message_with_method():
- test_requests_path_when_identity_not_found
- test_path_request_timeout_returns_error
- test_path_request_exception_handled

Uses time.sleep mocking to avoid actual 5-second waits.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:53:10 -05:00
torlando-tech
c61d58d31a test: Add Phase 2 tests for reticulum_wrapper.py coverage
Add 84 new tests to improve coverage of remaining uncovered code paths:

New files:
- test_wrapper_config.py: Tests for _remove_autointerface_from_config(), _setup_interface()
- test_wrapper_announce_polling.py: Tests for poll_received_announces()

Updated files:
- test_announce_handler.py: Add _announce_handler() internal logic tests
  (table extraction, LXMF parsing, stamp costs, Kotlin bridge notification)
- test_opportunistic_timeout.py: Add threading tests for timer lifecycle
- test_wrapper_peer_identity.py: Add error handling path tests

Total tests: 431 → 515

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:39:49 -05:00
torlando-tech
28cf53db53 test: Add comprehensive unit tests for reticulum_wrapper.py
Add 299 new tests across 10 test files to improve Python code coverage:

- conftest.py: Shared pytest fixtures for RNS/LXMF mocking
- test_wrapper_initialization.py: Constructor, bridges, config, init/shutdown
- test_wrapper_messaging.py: Message send, delivery callbacks, polling
- test_wrapper_identity.py: Identity CRUD, import/export, recovery
- test_wrapper_destination.py: Destination creation, announces
- test_wrapper_peer_identity.py: Peer identity recall/store/restore
- test_wrapper_propagation.py: Propagation node management
- test_wrapper_ble.py: BLE and RNode interface initialization
- test_wrapper_path.py: Path table, has_path, request_path
- test_wrapper_utilities.py: Debug info, echo, utility methods

All tests use pre-import mocking pattern to avoid RNS/LXMF dependencies.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:22:10 -05:00
Torlando
c84d0e9f2b
Merge pull request #79 from torlando-tech/feature/opportunistic-timeout
Feature/opportunistic timeout
2025-12-12 19:36:03 -05:00
torlando-tech
fa40217e60 feat: Auto-request path for unknown destinations
When sending to a destination whose identity isn't cached (e.g., peer hasn't
announced since app start), automatically call RNS.Transport.request_path()
and wait up to 5 seconds for the network to respond with the path.

This enables sending messages to known peers without waiting for their
periodic announces, improving UX for users resuming conversations.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 19:18:48 -05:00
torlando-tech
1629c34107 test: Add coverage tests for sync and delivery callbacks
Add 14 new tests to improve code coverage:

PropagationNodeManager (5 tests):
- syncWithPropagationNode skips when no relay configured
- syncWithPropagationNode skips when already syncing
- syncWithPropagationNode updates lastSyncTimestamp on success
- syncWithPropagationNode handles protocol failure gracefully
- triggerSync emits NoRelay when no relay configured

Python delivery callbacks (6 tests):
- _on_message_delivered calls Kotlin callback with correct JSON
- _on_message_delivered handles missing callback gracefully
- _on_message_failed calls Kotlin callback with failed status
- _on_message_failed handles missing callback gracefully
- _on_message_sent calls Kotlin callback with sent status
- _on_message_sent handles missing callback gracefully

Python propagation node setting (3 tests):
- set_outbound_propagation_node stores node hash
- set_outbound_propagation_node clears with None
- get_outbound_propagation_node returns hex string

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 19:18:10 -05:00
Torlando
21ec3284fb
Merge pull request #78 from torlando-tech/feature/opportunistic-timeout
Feature/opportunistic timeout
2025-12-12 18:41:00 -05:00
torlando-tech
c6d543cecb fix: Add propagation stamp generation for retry fallback
When retrying an opportunistic message via propagation, the message was
missing the required propagation stamp (proof-of-work), causing the
propagation node to reject it.

Added:
- Clear propagation_packed and propagation_stamp before retry
- Set defer_propagation_stamp=True to trigger stamp generation
- Message now goes through pending_deferred_stamps → stamp generation →
  pending_outbound → delivery

Also added unit tests for:
- Stamp generation flag configuration on retry
- Fresh messages without prior propagation data
- No retry when propagation node not configured

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 18:36:36 -05:00
torlando-tech
44f49c58a5 fix: Update announce handler tests to match implementation
Tests were expecting `_announce_handler_ref` (single handler) but
implementation uses `_announce_handlers` dict with aspect-specific
handlers. Tests were never run in CI until recent coverage changes.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 18:25:13 -05:00
torlando-tech
4225f9e3e3 feat: Add timeout fallback for opportunistic messages to propagation
When sending opportunistic messages to offline recipients, the message
would get stuck in "Sent" state forever waiting for a delivery receipt
that never arrives. This adds a 30-second timeout mechanism that
triggers propagation fallback for undelivered opportunistic messages.

Changes:
- Add tracking dict for opportunistic messages with timestamps
- Add background timer thread checking every 10s for timeouts
- After 30s without delivery, trigger _on_message_failed() to retry
  via propagation node (using existing fallback logic)
- Update deliveryMethod to "propagated" when retrying via propagation
  so UI correctly shows "Delivered to propagation network"

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 18:16:46 -05:00
torlando-tech
afc27d6cdb refactor: Fix detekt/ktlint issues and remove runBlocking antipattern
- Fix TooManyFunctions in PropagationNodeManager by removing restartPeriodicSync
- Fix TooManyFunctions in MessagingViewModel by extracting helpers to top-level
- Fix CyclomaticComplexity in sendMessage by refactoring into smaller methods
- Fix DestructuringDeclarationWithTooManyEntries in MessageDetailScreen
- Remove runBlocking antipattern from PropagationNodeManager StateFlow init
- Apply ktlint formatting fixes across codebase
- Update tests for refactored code

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 16:58:06 -05:00