- Announce lxst.telephony destination alongside LXMF announces so
remote peers discover both messaging and call paths together
- Add periodic re-announcing in CallManager (3-hour interval,
matching reference LXST Telephony implementation)
- Auto-request identity path from network when unknown, with 5s
retry loop (mirrors existing LXMF pattern in reticulum_wrapper)
- Move setupLxstCallManager() to first position in setupBridges()
so Telephone is available immediately after Python init (was last,
causing up to ~47s delay before calls could be placed)
- Remove stale "call.audio" announce handler that could never match
the actual "lxst.telephony" aspect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The call() method checked and set self.active_call without holding
_call_handler_lock. During the up-to-70s path discovery window, an
incoming call could set active_call via __caller_identified, which
call() would then overwrite — orphaning the incoming link.
Fix: set _busy=True under lock before path discovery so incoming
calls see the reservation and get rejected. Clear _busy in finally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents orphaned Reticulum links when call() is invoked while an
existing call is active or connecting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use local reference capture instead of locks for receive_audio_packet()
and receive_signal() to prevent race with hangup()/__link_closed()
nulling self.active_call. Lock-free approach avoids audio jitter from
contention on the 25-50Hz packet path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _call_handler_lock to answer(), hangup(), and __link_closed()
to prevent race conditions on self.active_call (Sentry HIGH)
- Remove invalid `=*` gitignore pattern (Greptile)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 40 new tests across 12 test classes covering previously untested
code paths: incoming/outgoing link handling, packet reception, signal
routing, audio forwarding, and Kotlin callback notification. Add
pragma: no cover to RNS fallback stub (dead code in test context).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
call_manager.py was rewritten during LXST-kt extraction as a thin
Reticulum transport layer — profile constants, telephone attribute,
audio bridge, and callback handlers all moved to Kotlin. Rewrite all
50 tests to match the new API surface: active_call (RNS.Link),
packet forwarding, signal routing, and bridge wiring.
Key fix: use RNS.Link.ACTIVE symbolically (not hardcoded int) since
conftest.py globally replaces sys.modules['RNS'] with MagicMock.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove vestigial Python LXST artifacts that are no longer needed
now that audio, codecs, and filters are handled by Kotlin LXST-kt:
- Remove numpy pip dependency (was for Python audio float32 conversion)
- Remove pycodec2 pip install and pre-built wheels (Kotlin JNI handles codecs)
- Delete chaquopy_audio_backend.py (617 lines, replaced by AudioDevice.kt)
- Delete lxst and lxst-filterlib wheels (never installed)
- Remove dead set_audio_bridge() plumbing from wrapper and manager
- Update call_manager.py to use renamed onInboundPacket/onInboundSignal
- Update LXST-kt submodule with transport-agnostic core/ renames
Remaining Python: call_manager.py (Reticulum bridge), rns, lxmf, u-msgpack.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python's _notify_kotlin callback (via Chaquopy function reference) silently
fails to reach Telephone.onIncomingCall(), leaving isIncomingCall=false so
answer() always rejected. Fix with three changes:
- Add Telephone.prepareForAnswer() for lightweight JIT state setup
- Binder answerCall() falls back to CallBridge identity when answer() fails
- VoiceCallScreen auto-answer no longer gates on callState, preventing
notification answer button from starting a new outgoing call
Also: answer() now returns Boolean, onIncomingCall() no longer sends
duplicate STATUS_RINGING (Python already sent it to remote).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add set_kotlin_audio_active() method to CallManager
- Delegates to chaquopy_audio_backend.set_kotlin_audio_active()
- Provides Kotlin-accessible method to control Python audio disable
- Logs state change for debugging
- Add global _kotlin_audio_active flag (default False)
- Add set_kotlin_audio_active() and is_kotlin_audio_active() functions
- ChaquopyRecorder._record_chunk() returns silence when flag True
- ChaquopyPlayer.play() drops frames when flag True
- Prevents dual audio pipeline conflict during Kotlin calls
- Add STATUS_* signalling constants (0x00-0x06) to CallManager
- Send INTEGER signals via send_signal() in all LXST callbacks:
- _handle_ringing() -> STATUS_RINGING (0x04)
- _handle_established() -> STATUS_CONNECTING (0x05) + STATUS_ESTABLISHED (0x06)
- _handle_ended() -> STATUS_AVAILABLE (0x03)
- _handle_busy() -> STATUS_BUSY (0x00)
- _handle_rejected() -> STATUS_REJECTED (0x01)
- Preserve existing _notify_kotlin() string events for UI
This fixes the signal type mismatch (Gap 1 from 11-VERIFICATION.md)
where Kotlin Telephone expected INTEGER signals but Python only sent
STRING events. Kotlin Telephone can now transition to ESTABLISHED state
and open audio pipelines.
- Add on_state_changed() for Kotlin state notifications
- Add on_profile_changed() for profile sync
- Add set_kotlin_telephone_callback() for callback registration
- Add _notify_kotlin() for Python->Kotlin event notifications
- Update receive_audio_packet() to forward to LXST Packetizer
- Update receive_signal() to forward to LXST signalling
- Wire existing handlers to notify Kotlin Telephone
The second create_identity() method at line 7376 shadows the first one at
line 2338. Making display_name optional allows callers that don't pass a
name to work correctly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update Reticulum fork from rebase-1.1.3 to fix/socket-leak-1.1.3 branch.
The fix closes sockets on connection failure in TCPInterface.connect() and
BackboneInterface.connect() to prevent resource leak during reconnection
attempts (~780 leaked sockets/hour, ~4.3 MB/hour native memory growth).
Addresses memory growth identified during Phase 9 profiling investigation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover start/stop, snapshots, periodic scheduling, and thread safety.
13 test cases for ~90% coverage of memory_profiler.py.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add threading.Lock to synchronize access to _profiling_active and
_snapshot_timer between stop_profiling() and timer callback.
Prevents orphaned timer continuing after stop_profiling() returns.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create memory_profiler.py with tracemalloc snapshot comparison
- Add enable_memory_profiling() method to ReticulumWrapper
- Schedule periodic snapshots using threading.Timer (Chaquopy-compatible)
- Filter frozen importlib and unknown traces to reduce noise
- Log top 10 growing allocations to Android logcat via logging_utils
- Provide get_memory_profile() for current stats query
- Zero overhead when disabled (lazy import pattern)
Add 27 tests covering:
- get_discovered_interfaces(): TCP interfaces, radio interfaces with LoRa
params, interfaces with location data, multiple interfaces sorted by
status and stamp value, bytes/string transport_id handling, None value
filtering, exception handling
- _get_discovery_status_name(): status code to name conversion
- _create_config_file() discovery options: discover_interfaces,
autoconnect_discovered_interfaces, interface_discovery_sources,
required_discovery_value, bootstrap_only for TCP clients
This improves patch coverage from ~56% to target higher coverage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add unit tests for the RNS 1.1.x discovery functionality:
Python tests:
- test_autoconnected_endpoints.py: 14 tests for get_autoconnected_interface_endpoints()
- test_discovery_enabled.py: 12 tests for is_discovery_enabled()
Kotlin tests:
- DiscoveredInterfacesViewModelTest.kt: 22 tests covering autoconnected endpoints
state, isAutoconnected() helper, and discovery settings loading
- DiscoveredInterfacesScreenTest.kt: 29 tests for UI helpers including
isYggdrasilAddress(), formatInterfaceType(), and icon/badge visibility
Also fixes:
- Fix detekt ComplexCondition issues in RNodeWizardScreen, MapScreen, MainActivity
- Fix detekt LongMethod in ColumbaApplication by extracting restorePeerIdentities()
- Fix detekt SwallowedException in InterfaceRepository with verbose logging
- Fix detekt MatchingDeclarationName by moving FocusInterfaceDetails to own file
- Fix detekt ReturnCount in isYggdrasilAddress and ViewModel helper functions
- Update StartupConfigLoaderTest for new discovery settings
- Update TcpClientWizardViewModelTest for allInterfaceEntities changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
- 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>
Add unit tests for ReceivingInterfaceInfo:
- 25 Kotlin tests covering TCP, Auto, BLE, RNode, Serial, and unknown interfaces
- Tests for friendly name extraction, interface type parsing, and edge cases
Refactor ReceivingInterfaceInfo to reduce cyclomatic complexity:
- Extract InterfaceCategory enum for display properties
- Extract categorizeInterface() and looksLikeAddress() helper functions
- Main function now delegates to smaller, focused functions
Add 2 additional Python tests for interface name formatting:
- Test interface.name same as class name returns class only
- Test interface.name being None returns class only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
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>
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>
- Harden 4 Python tests that were flaky due to mock object identity
comparisons by using explicit sentinel values for link status constants
- Add failure annotations to CI workflow so root cause job is clearly
marked when fail-fast cancels sibling jobs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover all branches: success with items, empty list, lowercase
normalization, empty string filtering, set storage, deduplication,
and list replacement.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add requester to allowed list in tests that expect requests to succeed
- Add test_blocks_request_from_non_allowed_requester to verify blocking
- Add test_blocks_request_when_allowed_list_empty to verify empty = block all
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
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>
Expand Python test coverage to include:
TestSendTelemetryRequestSuccess (6 tests):
- Successful send with immediate identity recall
- Successful send with cached identity
- Error when local_lxmf_destination is missing
- Error when identity cannot be resolved after path request
- jarray conversion handling
- Correct FIELD_COMMANDS structure building
TestOnLxmfDeliveryFieldCommands (4 tests):
- FIELD_COMMANDS handling when collector enabled
- Skipping FIELD_COMMANDS when collector disabled
- Identity retry mechanism in delivery handler
- Ignoring non-collector requests
Total Python tests: 45 (up from 35)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add 14 new tests covering:
- _send_telemetry_stream_response: filtering by timebase, LXMF message
creation, handling empty telemetry, cleanup of expired entries
- send_telemetry_request: error handling for uninitialized states
- FIELD_COMMANDS constants: verify all protocol constants exist
- Timebase filtering: verify received_at vs timestamp filtering
These tests improve coverage for the Python telemetry collector
implementation, specifically the FIELD_TELEMETRY_STREAM response
generation and FIELD_COMMANDS request handling paths.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 4 tests to SettingsRepositoryTest for host mode persistence
- Add 7 tests to TelemetryCollectorManagerTest for host mode management
- Add 6 tests to SettingsViewModelTest for host mode UI state
- Create test_telemetry_host_mode.py with 21 Python tests for:
- pack_telemetry_stream function
- set_telemetry_collector_enabled method
- _store_telemetry_for_collector method
- _cleanup_expired_telemetry method
- Integration tests for full workflow
- Fix detekt issues with appropriate suppressions
- Fix SwallowedException by including exception in log
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
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>