Commit graph

121 commits

Author SHA1 Message Date
torlando-tech
aae4368e15 fix: always check created hash for existing links and clean up stale
Remove conditional that only checked recipient_dest.hash when hashes
didn't match - links are stored under recipient_dest.hash so this check
must always run. Also clean up stale links from both direct_links and
backchannel_links when found.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:58:18 -05:00
torlando-tech
9da7682048 fix: clean up stale links and prevent race in direct_links cleanup
- Clean up stale/closed links when discovered during existing link check
- Use identity comparison in finally block to only remove our own link,
  preventing concurrent calls from incorrectly removing each other's links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:55:08 -05:00
torlando-tech
bf146f1335 fix: add link cleanup in exception handler for establish_link
Clean up stored link and call teardown() when an exception occurs,
matching the cleanup done on timeout. Safely handles cases where
recipient_dest or link may not be defined yet.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:04:09 -05:00
torlando-tech
adb23e992a fix: only request path when none exists to reduce network traffic
Previously, request_path() was called unconditionally even when a path
already existed. This generated unnecessary network broadcasts. Now we
only request a path if we don't have one. If the existing path is stale,
link establishment will fail and can be retried.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 12:41:53 -05:00
torlando-tech
152ef248e2 fix: add link.teardown() on timeout to properly release resources
When link establishment times out, the link object was being removed
from direct_links but teardown() wasn't called. This left the underlying
RNS link in limbo, potentially consuming resources. Now properly closes
the link before removing from the dictionary.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 12:11:45 -05:00
torlando-tech
cd7b5dd7f6 test: add comprehensive tests for link management functions
Add 35 new tests for link management in reticulum_wrapper.py:
- TestEstablishLink: 12 tests for establish_link()
- TestCloseLink: 7 tests for close_link()
- TestGetLinkStatus: 6 tests for get_link_status()
- TestProbeLinkSpeed: 8 tests for probe_link_speed()
- TestNextHopBitrate: 2 tests for next_hop_bitrate functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 10:56:12 -05:00
torlando-tech
5fcc17b0bd fix: add LXMF mock to TestOnLXMFDelivery and fix hop count test
- Add LXMF mock in setUp/tearDown for TestOnLXMFDelivery tests
  (LXMF is None in test environment, causing AttributeError)
- Update test_get_hop_count_returns_none to properly mock RNS.Transport
  (function now implements hop count, test needed updating)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 09:26:36 -05:00
torlando-tech
6797a18aab fix: address Greptile code review issues
- Add missing delivery_method field to error returns in send_lxmf_message_with_method
- Use try/finally for temp image file cleanup to prevent leaks
- Add race condition guard in LinkSpeedProbe.probe() to skip if already probing
- Populate next_hop_bitrate_bps in probe_link_speed results from RNS interface
- Add try/finally bitmap recycling in ImageUtils to prevent memory leaks
- Fix None→0 conversion in RoutingManager helpers to omit field instead

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:55:24 -05:00
torlando-tech
6375054163 refactor: simplify probe_link_speed to use establish_link
Instead of sending empty LXMF messages to probe link speed, now uses
the reliable establish_link() function. Also checks backchannel_links
for existing incoming links from peer.

This removes ~80 lines of complex probe message handling code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
ae6b947e3a fix: check backchannel_links for bidirectional link status
- Always request fresh path even when one exists (avoids stale routes)
- Check backchannel_links in addition to direct_links for existing links
- Links are bidirectional, so incoming links from peer count as "active"

This fixes the issue where Device A shows "Online" but Device B shows
"last seen X minutes ago" even though they have an active link between them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
48912950c6 fix: manual link establishment now works correctly
Fixed 3 bugs in establish_link() that prevented manual link establishment
from working while LXMF DIRECT messages succeeded:

1. Missing identity recall fallbacks - now tries destination hash,
   identity hash, and local cache (matching send_lxmf_message pattern)

2. Reversed path request logic - now requests path when one doesn't
   exist instead of when one already exists

3. Wrong key for storing links - now uses recipient_dest.hash
   (created destination hash) instead of input hash

Also updated close_link() and get_link_status() with same hash
handling logic for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
301466e67c feat: add conversation link management for real-time online status
- Add ConversationLinkManager to track active links per conversation
- Open link when entering conversation, close after 5min inactivity
- Update online indicator to show "Online" when link is active
- Add establish_link(), close_link(), get_link_status() to Python wrapper
- Add AIDL methods and protocol support for link management
- LinkSpeedProbe now uses existing links from ConversationLinkManager

Note: Manual link establishment times out (investigating), but detecting
existing links from LXMF DIRECT message exchange works correctly.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
71eb8e6954 fix: handle large images by writing to temp file for Binder IPC
Large images (>500KB) exceed Android Binder IPC transaction limits (~1MB).
Now large images are written to temp files and the path is passed through
AIDL instead of the raw bytes.

Changes:
- Add imageDataPath parameter to AIDL sendLxmfMessageWithMethod
- ServiceReticulumProtocol writes large images to temp files
- Python reads from file path and deletes temp after reading
- Cap loadBitmap to MAX_PREVIEW_DIMENSION regardless of preset
  (prevents Canvas crash for ORIGINAL preset with huge images)

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
386d2e9847 fix: link speed probe now correctly finds and uses existing links
- Fix direct_links key lookup (LXMF uses hex strings, not bytes)
- Add find_direct_link() helper to check both key formats
- Fix float-to-long conversion for link rates from Python
- Reset probe state before starting new probe
- Check for late link establishment after timeout

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
f412365fff feat: improve link speed probe with LXMF message-based probing
- Send empty LXMF message to establish link instead of raw Link
- Add delivery method parameter to probe (direct vs propagated)
- Filter empty probe messages on receive side to prevent rendering
- Fix UnboundLocalError for has_text_content in message filtering
- Add next_hop_bitrate_bps field for heuristic-based recommendations
- Monitor message state for SENT/DELIVERED detection

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
5febe58167 feat: add link speed probe infrastructure for adaptive compression
Add ability to probe actual link speed to a destination by establishing
a Link and measuring the establishment rate. This provides end-to-end
path speed measurement that accounts for all intermediate hops.

Python layer:
- probe_link_speed() establishes link, measures rate, tears down
- Reuses existing active links if available
- Returns establishment_rate_bps, expected_rate_bps, rtt_seconds, hops

Kotlin layer:
- LinkSpeedProbeResult data class with helper methods
- Full AIDL/Service/Protocol stack integration
- Mock implementation for testing

Next: UI integration for quality selection dialog

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
90b3c26833 test: improve coverage for messaging, utilities, and UI components
Add 110+ new tests to improve patch coverage:
- Python: 29 tests for propagation functions (set_incoming_message_size_limit,
  _extract_file_summary, _fail_message_permanently, on_alternative_relay_received,
  _send_pending_file_notification)
- MessagingViewModel: 50 tests for image/file state, reactions, location sharing,
  sending state, retry, sync delegation
- ImageUtils: 17 tests for isAnimatedGif, CompressedImage.isAnimated
- FileUtils: 25 tests for formatFileSize edge cases, constants, MIME types
- MessageMapperTest: 40 tests for MessageUi computed properties, reactions
- SyncStatusBottomSheet: 28 UI tests for all sync states and progress display

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:22:08 -05:00
torlando-tech
26c2ccb9cb fix: properly scope LXMF mock and update tests for new fallback behavior
1. PropagationTestBase: Use setUp/tearDown to properly scope the LXMF
   mock instead of setting it at module level (which leaked between tests)

2. test_propagated_falls_back_to_direct_without_propagation_node: Updated
   test to match new behavior - PROPAGATED now falls back to DIRECT when
   no propagation node is set (instead of failing)

3. test_on_message_failed_retries_via_propagation: Set mock_message.fields
   to empty dict so "5 in fields" check works correctly

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 00:25:44 -05:00
torlando-tech
ae77042a46 fix: properly mock LXMF.LXMRouter constants in propagation tests
The get_propagation_state() method uses LXMF.LXMRouter.PR_* constants to
map state integers to human-readable names. When LXMF was mocked as a
plain MagicMock(), these constants became MagicMock objects instead of
integers, causing the state lookup to fail.

Fix: Define all PR_* constants in the mock setup and set
reticulum_wrapper.LXMF to the mock (since the module-level variable
is only populated during initialize() which tests skip).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:42:16 -05:00
torlando-tech
2cc5cdc9a1 fix: prevent stamp generation deadlock and improve early startup
- Move native stamp generator registration to pre-init callback
  (prevents Python's multiprocessing.Manager() from hanging on Android)
- Start PropagationNodeManager early after service binding
- Add retry logic for relay sync when Python not yet initialized
- Fall back to DIRECT when PROPAGATED requested but no relay set

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:16:14 -05:00
torlando-tech
321838491f style: fix ktlint and detekt issues
- Run ktlintFormat to fix formatting issues
- Refactor supersedePendingFileNotifications to use functional approach
  instead of loop with multiple continues
- Extract tryParseNotificationMatch helper to reduce return statements
- Use PropagationState constants from ReticulumProtocol instead of
  duplicating them in ServiceNotificationManager
- Simplify complex condition in MessageMapper with helper variables

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 01:29:38 -05:00
torlando-tech
30075665b8 feat: improve file transfer UX with deferred notifications and manual sync toast
- Defer pending file notification until propagation is confirmed on relay
- Track hasFileAttachments before extraction to fix supersede matching
- Add service-side supersede logic for pending file notifications
- Only show sync complete toast for manual syncs, not automatic/periodic
- Add _isManualSync flag to PropagationNodeManager for toast control
- Add real-time progress tracking with 1% threshold for callbacks

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:52 -05:00
torlando-tech
a67dd91d51 feat: fix propagation sync completion timing with real-time progress UI
Previously, manual sync would immediately show "Sync complete" even when
large files were still downloading in the background. This fix implements
event-driven sync completion that waits for LXMF's PR_COMPLETE state.

Changes:
- Python: Add propagation state callback with 100ms polling during sync
- AIDL: Add onPropagationStateChanged callback
- ServiceReticulumProtocol: Add propagationStateFlow for state observation
- PropagationNodeManager: Wait for PR_COMPLETE before emitting success
- UI: Sync button shows spinner during sync, tappable for status details
- New SyncStatusBottomSheet component shows real-time sync progress
- Tests: Update PropagationNodeManagerTest for new async completion flow

The sync now properly tracks LXMF propagation states:
- PR_PATH_REQUESTED → PR_LINK_ESTABLISHING → PR_LINK_ESTABLISHED
- → PR_REQUEST_SENT → PR_RECEIVING → PR_COMPLETE

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:52 -05:00
torlando-tech
16e758045d test: add comprehensive tests for incoming message size limit feature
Add test coverage for:
- SettingsRepository: incoming message size limit save/load with clamping
- SettingsViewModel: runtime limit updates and protocol integration
- FileUtils: temp attachment write, cleanup, and size limit functions
- Python reticulum_wrapper: initialize with limit and runtime adjustment

This improves code coverage for the incoming message size limit feature
which allows users to configure the maximum size of incoming messages.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:51 -05:00
torlando-tech
7f3fb7e889 fix: set FIELD_IMAGE constant in mock to fix test
The test_send_with_both_image_and_file_attachments test was failing
because the mocked LXMF module returned a MagicMock for FIELD_IMAGE
instead of the expected integer 6. Set the constant explicitly on the
mock to ensure the fields dict uses the correct key.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:51 -05:00
torlando-tech
86fc7ba801 feat: add configurable incoming message size limit setting
Add user-configurable setting to control maximum incoming LXMF message size:
- Default 1MB (1024KB), range 512KB to 128MB
- UI with preset chips (1MB, 5MB, 10MB, 25MB, Unlimited) plus custom dialog
- Runtime update via LXMRouter.delivery_per_transfer_limit
- Persisted in DataStore preferences

Implementation:
- SettingsRepository: new preference with flow and save method
- AIDL: setIncomingMessageSizeLimit(int limitKb) method
- Python: set_incoming_message_size_limit() and delivery_limit in init
- SettingsViewModel: state field and update method
- MessageDeliveryRetrievalCard: size limit UI section

Also fixes test issues:
- MessagingViewModelTest: update mock parameter count (10 params)
- ConversationRepositoryTest: add missing attachmentStorage mock
- MessageDeliveryRetrievalCardTest: handle duplicate "Custom" chips

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:51 -05:00
torlando-tech
84cccade18 feat: support large file attachments with file-based transfer
Remove 512KB file size limit and implement file-based transfer for large
attachments to bypass Android Binder IPC (~1MB) and SQLite CursorWindow
(~2MB) limits.

Changes:
- Add temp file transfer for attachments >500KB via AIDL paths
- Extract large attachments to disk before database storage
- Move hex encoding to background thread with efficient lookup table
- Add compression warning dialog with hint to use file attachments
- Python reads large files from disk paths and cleans up temp files

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:51 -05:00
torlando-tech
e4c0bec1dd fix: profile icon interoperability with Sideband and map display
- Send icon appearance when sending messages to enable Sideband/MeshChat
  users to see Columba profile icons (was not being passed before)
- Add profile icon fields to ContactMarker for map rendering
- Create MarkerBitmapFactory.createProfileIconMarker() for MDI icon markers
- Use ProfileIcon in ContactLocationBottomSheet instead of Identicon
- Update MapScreen to show profile icons on map markers when available
- Add comprehensive unit tests for new functionality

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 12:26:36 -05:00
torlando-tech
0e58d3462b refactor: remove local BLEInterface.py copy
BLEInterface.py is provided by ble-reticulum pip dependency.
No need to maintain a duplicate local copy.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 20:03:45 -05:00
torlando-tech
4083eb2063 build: update ble-reticulum to main branch
The fix/ble-peer-interface-cleanup branch has been merged to main.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:59:06 -05:00
torlando-tech
43aad807fd style: fix ktlint formatting issues for CI
- Add ktlint_standard_function-naming = disabled for Compose functions
- Move inline comments to separate lines per ktlint rules
- Fix import ordering and multiline expression formatting
- Fix line length exceeding 160 characters
- Auto-format trailing commas and expression body placement

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:30:33 -05:00
torlando-tech
218fdee976 fix(ble): prevent interface/fragmenter loss during MAC rotation
- Rewrite _validate_spawned_interfaces() with 3-pass approach:
  - Pass 1: Collect orphaned addresses
  - Pass 2: Clean up address mappings, track interfaces to detach
  - Pass 3: Only detach interfaces with zero connected addresses
  - Fragmenters only cleaned up when interface fully detached

- Enhance _spawn_peer_interface() reuse logic:
  - Update address_to_identity and identity_to_address when reusing
  - Cancel pending detach for the identity
  - Mark interface as online

- Fix disconnect callbacks to preserve fragmenters:
  - _device_disconnected_callback: defer fragmenter cleanup to grace period
  - handle_central_disconnected: same fragmenter preservation
  - _process_pending_detaches: clean up fragmenters on actual detach

- Rename _cleanup_stale_interface() to _cleanup_stale_address():
  - No longer detaches interface during MAC rotation
  - Only cleans up stale address-specific mappings
  - Interface preserved for reuse with new address

Fixes orphaned peer interfaces and "No fragmenter for peer" warnings
during BLE MAC address rotation.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 13:31:42 -05:00
torlando-tech
ace9c86034 fix(ble): preserve peer interface across MAC rotations with grace period
When an address disconnects, check if other addresses still have the
same identity. Only detach the peer interface when ALL addresses for
that identity are disconnected.

Additionally, use a 2-second grace period before detaching to allow
new connections to establish during MAC rotation. If a new address
with the same identity connects within the grace period, cancel the
pending detachment.

This fixes the issue where peer interfaces were being rapidly created
and destroyed during MAC rotation, causing connection instability.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 01:44:38 -05:00
torlando-tech
94c3716757 fix(ble): identity timeout cleanup and UI improvements
BLE Connection Cleanup:
- Add pending identity timeout (30s) to disconnect non-Reticulum devices
  (e.g., AirTags, BLE scanners) that connect but never complete handshake
- Fix cancelConnection not triggering cleanup callback - manually clean up
  state and fire onCentralDisconnected after cancel
- Notify Python immediately for all peripheral connections (with or without
  identity) so it can track pending connections

UI Improvements:
- Add live-updating connection duration timer in BLE connections screen
  (updates every second using LaunchedEffect)

Build Changes:
- Point to ble-reticulum fix branch with identity timeout changes
- Load BLEInterface from local ble_modules for testing

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:07:31 -05:00
torlando-tech
a827273923 feat(python): add LXMF icon appearance field support
Implement FIELD_ICON_APPEARANCE (0x04) for Sideband/MeshChat interop:
- Parse icon appearance from incoming LXMF messages
- Include icon in outgoing messages when identity has icon set
- Format: [icon_name, foreground_rgb, background_rgb]
- Convert RGB bytes to/from hex strings for Kotlin bridge

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 22:38:08 -05:00
torlando-tech
b2f9478a13 test: add coverage tests for request_identity_resync method
Add tests for the new request_identity_resync() method that covers all
code paths in android_ble_driver.py:
- No bridge returns False with warning log
- Identity found returns True
- Identity not found returns False
- 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 16:11:42 -05:00
torlando-tech
d6515f8ef8 fix: add identity resync for BLE reconnection recovery
When Python's disconnect callback fires but the driver layer maintains
the GATT connection, data was being dropped because address_to_identity
was cleared.

Changes:
- Point ble-reticulum dependency to fix/identity-cache-on-disconnect branch
- Add requestIdentityResync() to KotlinBLEBridge for identity recovery
- Add request_identity_resync() to android_ble_driver.py as bridge method

The ble-reticulum fix adds identity caching (60s TTL) and cache lookup
in _handle_ble_data. When cache is also empty, it calls the driver's
request_identity_resync() method to recover identity from Kotlin.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:09:55 -05:00
torlando-tech
24f347b2fc fix: patch RNS.log in module namespace for ensure_advertising tests
The previous approach relied on mock_rns global which wasn't properly
connected to the reimported module. Now we:
1. Store reference to the actual module (abd_module)
2. Patch RNS.log in the module's namespace in setUp
3. Restore it in tearDown
4. Capture log calls directly instead of using mock assertions

This ensures the log capture works regardless of import order or
coverage instrumentation.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 15:13:54 -05:00
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