- 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>
Previously Columba was using FIELD_IMAGE (0x06) for telemetry
commands, causing Sideband to interpret requests as image
attachments. Add the correct FIELD_COMMANDS constant (0x09)
per the LXMF specification.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Prevents race condition where two threads compete for USB reads
if the previous thread doesn't terminate within timeout.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set readTimeout=100ms to reduce CPU usage from continuous polling
- Escalate thread leak warning to ERROR level for visibility
- Capture deviceId early in detach handler for stability across Android versions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add hex character validation for collector address (prevents crash)
- Fix periodic send race condition by awaiting in-progress sends
- Fix auto-confirm timing using LaunchedEffect for state sync
- Optimize SQL queries from O(n²) to O(n) using JOIN
- Add future timestamp rejection (>1hr) to prevent UI issues
- Add icon name validation (alphanumeric/underscore, max 50 chars)
- Add comprehensive tests for Python validation logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use location.time instead of System.currentTimeMillis() for accurate
location capture timestamp in TelemetryCollectorManager
- Add timestamp validation in Python to prevent issues with None/0 values
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements support for sending location telemetry to a collector and
receiving bulk telemetry streams from multiple sources.
Features:
- TelemetryCollectorManager for scheduled/manual telemetry sends
- TelemetryCollectorCard UI for configuration in Settings
- FIELD_TELEMETRY_STREAM (0x03) handling in Python layer
- Database migration for appearanceJson field on received locations
- SettingsRepository preferences for collector address, intervals, etc.
Testing:
- TelemetryCollectorManagerTest with mockk and Turbine
- TelemetryCollectorCardTest with Compose testing
- SettingsViewModelTest telemetry method tests
- SettingsRepositoryTest telemetry preference tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracted business logic from ViewModels and UI components into testable utility classes (InterfaceFormattingUtils, DeviceNameValidator, RssiThrottler, DeviceClassifier) to enable comprehensive unit testing without platform dependencies. Added 116 new tests covering formatting, validation, throttling, and device classification logic, increasing patch coverage from 23.5% to approximately 35-40%.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When pairing a USB-connected RNode via Bluetooth, the wizard now:
- Adds "Pair via USB" option to the Bluetooth tab
- Receives PIN from RNode over USB connection
- Scans for unbonded RNodes and shows device selection list
- Auto-selects if only one unbonded RNode is found
- Uses BroadcastReceiver to properly detect bond state changes
This solves the issue where multiple RNodes in range would cause
pairing failures because the PIN belongs to the USB-connected device,
not necessarily the first discovered one.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix SwallowedException in KotlinUSBBridge by adding verbose logging
- Fix ImplicitDefaultLocale by using Locale.US for PIN formatting
- Add @Suppress for NestedBlockDepth in KISS protocol parser
- Remove unused generateDefaultInterfaceName and waitForBondState functions
- Add @Suppress for loadExistingConfig complexity (inherent to config mapping)
- Update DebugViewModelEventDrivenTest with interfaceRepository parameter
- Update ReviewConfigStepTest with isUsbMode and getConnectionTypeString mocks
- Fix test_usb_bridge.py mock to use Chaquopy getter methods
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>