Skip to content

feat: add SOS Emergency feature with sender/receiver integration, audio recording, and boot resilience#713

Draft
MatthieuTexier wants to merge 26 commits into
torlando-tech:mainfrom
MatthieuTexier:feature/sos-emergency-v2
Draft

feat: add SOS Emergency feature with sender/receiver integration, audio recording, and boot resilience#713
MatthieuTexier wants to merge 26 commits into
torlando-tech:mainfrom
MatthieuTexier:feature/sos-emergency-v2

Conversation

@MatthieuTexier

@MatthieuTexier MatthieuTexier commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Complete SOS Emergency feature for Columba — emergency distress signals over the LXMF/Reticulum mesh network. Works across all transports: LoRa (RNode), BLE, TCP, AutoInterface.

Key capabilities:

  • Multi-mode SOS triggers (combinable): shake, tap pattern, power button (3 presses), floating button
  • GPS location + battery telemetry in Sideband-compatible FIELD_TELEMETRY (0x02) format
  • FIELD_COMMANDS (0x06) for reliable SOS message detection with text-based fallback for legacy/Sideband clients
  • Ambient audio recording via FIELD_AUDIO (0x07) (AAC 16kHz, 15-60s configurable)
  • Periodic location/battery updates while SOS is active
  • "SOS Cancelled — I am safe." message sent on deactivation
  • Foreground service (specialUse|microphone) + boot receiver for background detection and reboot resilience
  • PIN-protected deactivation, silent auto-answer for incoming calls
  • Draggable floating SOS button + "SOS ACTIVE" pill overlay with persisted positions
  • Receiver-side: urgent notifications, red chat bubbles, inline audio player, breadcrumb trail on map, SOS active indicator on conversations

Sender Side

State Machine

SosManager implements Idle → Countdown → Sending → Active → Idle with:

  • Re-entry guard in trigger() (prevents duplicate sends)
  • ensureActive() guards before state mutations (cancel race prevention)
  • forceDeactivate() that cancels triggerJob, countdownJob, periodicUpdateJob, audioRecordingJob
  • Cancellation message sent from both Active and Sending states
  • DataStore state persistence across app/phone restarts

Trigger Detection

SosTriggerDetector supports three independently combinable modes via SensorEventListener + BroadcastReceiver:

  • Shake: Sustained acceleration > sensitivity × 9.81 m/s² for 500ms within 1s window
  • Tap pattern: Spike-based state machine (rejects walking steps >100ms, debounce 150ms)
  • Power button: 3 rapid SCREEN_OFF/SCREEN_ON events within 2s window
  • Thread-safe: synchronizedList for timestamps, @Volatile scalar state

SOS Messages

  • Template text + GPS coordinates (Locale.US formatting) + battery level
  • FIELD_TELEMETRY (0x02): raw msgpack bytes with SID_LOCATION + SID_BATTERY
  • FIELD_COMMANDS (0x06): {"sos_state": "active"|"update"|"cancelled"} for reliable detection
  • FIELD_AUDIO (0x07): ["m4a", bytes] AAC audio recording

Background & Boot Resilience

  • SosTriggerService: foreground service (specialUse|microphone on API 34+) keeps process alive
  • BootReceiver: starts ReticulumService + SosTriggerService on BOOT_COMPLETED
  • State restored from DataStore on startup — active SOS survives reboots

Receiver Side

  • SOS detection: FIELD_COMMANDS (primary) with text fallback (startsWith("SOS"|"URGENCE"|"EMERGENCY"))
  • Notifications: urgent persistent notification with "Open Chat" + "View on Map" actions
  • Chat styling: red errorContainer bubbles with "SOS EMERGENCY" badge + "View on Map" button
  • GPS extraction: from FIELD_TELEMETRY (primary) or text regex (fallback, locale-independent)
  • Audio playback: inline player (play/pause + progress + share button, I/O on Dispatchers.IO)
  • Breadcrumb trail: red polyline + dot markers on MapLibre from stored received_locations (source=sos_trail)
  • SOS active indicator: red border on conversation card via SosActiveTracker (in-memory StateFlow)
  • Cancellation: dismisses notification + posts follow-up when "SOS Cancelled" received

Settings (SosEmergencyCard)

Setting Default Description
Enable SOS off Master toggle
Message Template "SOS! I need help..." Customizable text
Countdown 5s Delay before sending (0 = instant)
Include Location on GPS in messages
Trigger Modes none Multi-select: shake, tap, power button
Audio Recording off Record ambient audio
Audio Duration 30s Recording length (15-60s)
Periodic Updates off Send location updates while active
Update Interval 120s Between periodic updates
Floating Button off Draggable SOS FAB
Silent Auto-Answer off Auto-answer calls during SOS
Deactivation PIN none Optional PIN to deactivate

Thread Safety & Reliability

  • synchronized lists for sensor timestamps, @Volatile scalar state in SosTriggerDetector
  • MutableStateFlow.update{} for atomic SosActiveTracker mutations
  • ensureActive() guards before state mutations in sendSosMessages() (cancel race prevention)
  • try/catch in settings observer to prevent monitoring death on transient errors
  • synchronized(lock) for SosAudioRecorder file access
  • try/finally with assigned flag for MediaPlayer/MediaRecorder leak prevention
  • Contacts fetch wrapped in try/catch in periodic update loop (prevents silent death on DB errors)
  • Re-read sosManager.state after forceDeactivate() in startObserving() (prevents stale service restart)

Tests (73 new)

Suite Count Coverage
SosManagerTest 34 State machine, telemetry, persistence, restore, cancel races
SosTriggerDetectorTest 28 Shake, tap spike detection, power button, multi-mode, cooldown
SosViewModelTest 7 State propagation, delegation
BootReceiverTest 4 Boot-time service startup

Test Plan

  • Scenario 1: Countdown + Cancel
  • Scenario 2: Instant Send (0s countdown)
  • Scenario 3: GPS Location + Battery Telemetry
  • Scenario 4: PIN Deactivation
  • Scenario 5: Auto-Answer
  • Scenario 6: Periodic Updates
  • Scenario 9: Floating Button
  • Scenario 10: Custom Template
  • Scenario 11: Resilience (App Kill / Phone Restart)
  • Scenario 11b: Full Reboot Resilience
  • Scenario 12: Repeated Cycle
  • Scenario 13: SOS Badge Visibility
  • Scenario 14: Receiver Chat Experience
  • Scenario 15: Shake Trigger
  • Scenario 16: Tap Pattern Trigger
  • Scenario 17: Trigger Mode Cooldown
  • Scenario 18: Power Button Trigger
  • Scenario 19: Multi-Mode Triggers
  • Scenario 20: Audio Recording
  • Scenario 21: Background Service Lifecycle

@sentry

sentry Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Codecov Report

❌ Patch coverage is 25.00000% with 72 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
python/reticulum_wrapper.py 25.00% 72 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a comprehensive SOS Emergency feature for the Columba LXMF/Reticulum messenger. It encompasses the full sender/receiver stack: multi-mode gesture detection (shake, tap, power button), LXMF field encoding (FIELD_TELEMETRY, FIELD_COMMANDS, FIELD_AUDIO), a foreground service + boot receiver for background resilience, and rich receiver-side UI (red chat bubbles, breadcrumb trails, inline audio player).

Key concerns from prior review rounds addressed in this iteration:

  • Concurrent-trigger race replaced Mutex with AtomicBoolean + triggerGeneration — re-triggers after cancel() no longer silently drop
  • cancel() and deactivate() both cancel triggerJob; _state.value is SosState.Active guards added before startPeriodicUpdates/startAudioRecording
  • SosAudioRecorder.stopRecorder() now uses separate try blocks for stop() and release()
  • parseSosLocation FIELD_TELEMETRY primary path fixed: unpack_location_telemetry now returns a dict; poll_received_messages serialises with json.dumps
  • FIELD_COMMANDS sos_state extraction added to poll_received_messages
  • SosActiveTracker trail rows deleted (deleteSosTrailForSender) on cancellation
  • Notification ID overflow fixed with xor
  • PIN minimum-length validation (>= 4 digits) now enforced in settings card

Outstanding items from earlier threads not yet resolved: missing FOREGROUND_SERVICE_MICROPHONE permission (causes silent SecurityException on Android 14+) and launcher icon used as notification small icon (R.mipmap.ic_launcher in SosTriggerService and NotificationHelper).

Confidence Score: 4/5

Safe to merge after the two unresolved P1s from previous threads are addressed: missing FOREGROUND_SERVICE_MICROPHONE permission and launcher icon as notification small icon.

This iteration resolved the majority of previously identified concerns. Score held at 4 because two P1 issues from prior threads remain open: (1) missing android.permission.FOREGROUND_SERVICE_MICROPHONE causes a silent SecurityException on Android 14+, killing background detection; (2) R.mipmap.ic_launcher as notification small icon renders as a solid blob on API 21+.

app/src/main/AndroidManifest.xml (missing FOREGROUND_SERVICE_MICROPHONE permission), SosTriggerService.kt and NotificationHelper.kt (launcher icon as notification small icon)

Important Files Changed

Filename Overview
app/src/main/java/com/lxmf/messenger/service/SosManager.kt New SOS state machine (Idle→Countdown→Sending→Active→Idle). AtomicBoolean+triggerGeneration replaces previous Mutex. deactivate() cancels triggerJob and _state.value guards prevent post-deactivation job launches.
app/src/main/java/com/lxmf/messenger/service/SosTriggerDetector.kt New gesture/power-button detector. Thread-safe with synchronizedList and @volatile. startObserving() correctly re-reads state after forceDeactivate().
app/src/main/java/com/lxmf/messenger/service/SosAudioRecorder.kt AAC recorder with separate try/catch blocks for stop() and release(), fixing the previous MediaRecorder mic-lock issue.
python/reticulum_wrapper.py FIELD_TELEMETRY now unpacked via unpack_location_telemetry() in both callback and poll paths; poll path now serialises with json.dumps(). FIELD_COMMANDS sos_state extraction added to poll path.
app/src/main/java/com/lxmf/messenger/service/MessageCollector.kt SOS detection logic integrated: FIELD_COMMANDS primary + text fallback, SosActiveTracker updates, deleteSosTrailForSender on cancellation, trail location inserted for breadcrumb.
app/src/main/java/com/lxmf/messenger/ui/components/AudioMessagePlayer.kt New inline audio player composable. LaunchedEffect(audioBytes) now properly deletes previous tempFile before writing a new one. Share flow correctly wraps copy in try/finally.
app/src/main/java/com/lxmf/messenger/service/SosTriggerService.kt Lightweight foreground service. Still uses R.mipmap.ic_launcher as notification small icon and FOREGROUND_SERVICE_MICROPHONE permission is missing (noted in previous threads).
app/src/main/java/com/lxmf/messenger/notifications/NotificationHelper.kt SOS notification helpers added. parseSosLocation now works for the poll path. Notification IDs use xor (overflow-safe).
app/src/main/java/com/lxmf/messenger/service/SosActiveTracker.kt New in-memory StateFlow tracker. explicitlyRemoved set prevents stale restore after clear(). MutableStateFlow.update{} used for atomic mutations.
app/src/main/java/com/lxmf/messenger/receiver/BootReceiver.kt New boot receiver starts ReticulumService and SosTriggerService on BOOT_COMPLETED. Clean error handling and action guard.
data/src/main/java/com/lxmf/messenger/data/db/dao/ReceivedLocationDao.kt Adds getSosTrailForSender, deleteSosTrailForSender, getRecentSosTrailSenders. Queries scoped to source='sos_trail'.

Sequence Diagram

sequenceDiagram
    participant GD as Gesture/Boot
    participant STD as SosTriggerDetector
    participant SM as SosManager
    participant RP as ReticulumProtocol
    participant SAR as SosAudioRecorder
    participant NH as NotificationHelper
    participant MC as MessageCollector
    participant SAT as SosActiveTracker

    GD->>STD: shake / tap / power press
    STD->>SM: trigger()
    SM->>SM: AtomicBoolean CAS guard
    SM->>SM: startCountdown(n) → SosState.Countdown
    Note over SM: countdown delay (0-30s)
    SM->>SM: sendSosMessages() → SosState.Sending
    SM->>RP: sendLxmfMessage (FIELD_TELEMETRY + FIELD_COMMANDS sos_state=active)
    RP-->>SM: Result
    SM->>SM: SosState.Active → persistSosActiveState()
    SM->>NH: showSosActiveNotification()
    SM->>SM: startPeriodicUpdates() [if enabled]
    SM->>SAR: start() → record AAC audio
    SAR-->>SM: audioBytes
    SM->>RP: sendLxmfMessage (FIELD_AUDIO)

    Note over MC: Receiver side
    RP-->>MC: receivedMessage (FIELD_COMMANDS sos_state=active)
    MC->>SAT: addSender(sourceHash)
    MC->>NH: notifySosReceived(urgent)
    MC->>MC: insert ReceivedLocationEntity (sos_trail)

    Note over SM: User deactivates
    SM->>SM: deactivate() → cancel triggerJob / periodicJob / audioJob
    SM->>SM: SosState.Idle
    SM->>RP: sendLxmfMessage (FIELD_COMMANDS sos_state=cancelled)
    RP-->>MC: receivedMessage (cancelled)
    MC->>SAT: removeSender(sourceHash)
    MC->>MC: deleteSosTrailForSender(sourceHash)
    MC->>NH: cancelNotification + notifyMessageReceived
Loading

Reviews (28): Last reviewed commit: "fix: replace runBlocking PIN fallback wi..." | Re-trigger Greptile

Comment thread app/src/main/AndroidManifest.xml
Comment thread app/src/main/java/com/lxmf/messenger/service/SosManager.kt
@MatthieuTexier MatthieuTexier force-pushed the feature/sos-emergency-v2 branch 2 times, most recently from c950c56 to 30ebe2b Compare March 26, 2026 09:25
Comment thread app/src/main/java/com/lxmf/messenger/service/SosTriggerDetector.kt
@MatthieuTexier MatthieuTexier force-pushed the feature/sos-emergency-v2 branch from 30ebe2b to 4c7f35d Compare March 26, 2026 10:23
Comment thread app/src/main/java/com/lxmf/messenger/service/SosManager.kt
@MatthieuTexier MatthieuTexier force-pushed the feature/sos-emergency-v2 branch from 4c7f35d to cb13031 Compare March 26, 2026 11:40
Comment thread app/src/main/java/com/lxmf/messenger/service/SosManager.kt
…io recording, and boot resilience

Add a complete SOS Emergency feature to Columba, allowing users to send
emergency distress signals to pre-selected contacts over the LXMF mesh
network. Works across all transports: LoRa (RNode), BLE, TCP, AutoInterface.

Sender side:
- State machine: Idle -> Countdown -> Sending -> Active -> Idle
- Multi-mode triggers (combinable): shake, tap pattern, power button (3 presses)
- Floating draggable SOS button + draggable "SOS ACTIVE" pill overlay
- GPS location (Locale.US formatting) + battery level in messages
- FIELD_TELEMETRY (0x02) Sideband-compatible msgpack telemetry
- FIELD_COMMANDS (0x06) for reliable SOS message detection (text fallback for legacy)
- FIELD_AUDIO (0x07) ambient audio recording (AAC 16kHz, 15-60s configurable)
- Periodic location/battery updates while active
- PIN-protected deactivation, silent auto-answer for incoming calls
- "SOS Cancelled - I am safe." message sent on deactivation
- Foreground service (specialUse|microphone) for background detection
- Boot receiver for automatic restart after reboot
- DataStore state persistence across app/phone restarts

Receiver side:
- SOS detection via FIELD_COMMANDS (primary) with text-based fallback
- Urgent persistent notifications with "Open Chat" and "View on Map" actions
- Red emergency styling in chat bubbles with "SOS EMERGENCY" badge
- "View on Map" extracts GPS from FIELD_TELEMETRY (primary) or text (fallback)
- Inline audio player (play/pause + progress bar + share button)
- Red border on conversation card when contact has active SOS
- Breadcrumb trail on map (red polyline + dot markers) from stored positions
- Cancellation notification when SOS is resolved
- Clear trail button on map

Settings:
- Enable SOS, message template, countdown (0-30s), include location
- Trigger modes (multi-select checkboxes), shake sensitivity, tap count
- Audio recording toggle + duration (15-60s)
- Periodic updates toggle + interval (30-600s)
- Floating button, silent auto-answer, deactivation PIN
- Draggable element positions persisted in DataStore

Thread safety & reliability:
- synchronized lists for sensor timestamps, @volatile scalar state
- MutableStateFlow.update{} for atomic SosActiveTracker mutations
- ensureActive() guards before state mutations (cancel race prevention)
- try/catch in settings observer to prevent monitoring death on transient errors
- Synchronized audio file access in SosAudioRecorder
- MediaPlayer/MediaRecorder leak prevention with try/finally patterns
- Cancellation message sent from both Active and Sending states

Tests: 73 new unit tests
- SosManagerTest (34): state machine, telemetry, persistence, restore
- SosTriggerDetectorTest (28): shake, tap spike, power button, multi-mode
- SosViewModelTest (7): state propagation, delegation
- BootReceiverTest (4): boot-time service startup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@MatthieuTexier MatthieuTexier force-pushed the feature/sos-emergency-v2 branch from cb13031 to 2041a98 Compare March 26, 2026 11:55
@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

This unique feature is ready for merging.
It fullfills my needs, feel free to request some changes or discuss the choices I made.

MatthieuTexier and others added 3 commits March 29, 2026 09:29
Accept upstream's added IdentityKeyProvider import in IdentityRepositoryDatabaseTest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MapViewModel constructor gained an identityRepository parameter
but the test file was not updated, causing compilation failure in
CI shard 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread app/src/main/java/com/lxmf/messenger/service/SosManager.kt
deactivate() did not cancel triggerJob, so if sendSosMessages() was
suspended (e.g. on persistSosActiveState), deactivation would race:
periodicUpdateJob and audioRecordingJob cancels were no-ops (not yet
assigned), then triggerJob resumed and spawned uncancellable jobs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread app/src/main/java/com/lxmf/messenger/ColumbaApplication.kt
MatthieuTexier and others added 3 commits March 29, 2026 10:51
When an SOS cancellation message was received, trail rows were kept
in the database. On app restart within 24h, getRecentSosTrailSenders()
would re-add the cancelled sender to SosActiveTracker, falsely showing
active SOS indicators.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. getLastKnownLocation() and loadIdentity() swallowed
   CancellationException, preventing deactivate()/forceDeactivate()
   from stopping coroutines suspended in location or identity calls.

2. sendSosMessages() could launch startPeriodicUpdates() and
   startAudioRecording() on scope (not children of triggerJob) after
   deactivate() cancelled triggerJob — orphaned jobs sending SOS
   messages indefinitely. Added ensureActive() checks before each.

3. SosAudioRecorder assigned recorder field before prepare()/start()
   succeeded. A concurrent cancel()/stopRecorder() could call stop()
   on an unprepared MediaRecorder causing a native crash. Moved
   assignment to after start() succeeds.

4. readAndDeleteOutputFile() leaked temp file when readBytes() threw.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SosManager:
- Mark job fields as @volatile for cross-thread visibility

SosTriggerDetector:
- Wrap tap/power timestamp compound operations in synchronized blocks
  to prevent ConcurrentModificationException
- Add @volatile isSensorListening guard in onSensorChanged to reject
  in-flight sensor events after stop()

SosActiveTracker:
- Track explicit removals to prevent stale restore on app restart
  (race between async DB restore and incoming cancel messages)
- Add clear() for identity switch cleanup

MessageCollector:
- Clear SosActiveTracker on stopCollecting() to prevent SOS state
  leaking across identity switches

SettingsRepository:
- Remove legacy SOS_TRIGGER_MODE key when writing new trigger modes

Python wrapper:
- Preserve raw FIELD_COMMANDS in fields_serialized alongside extracted
  SOS state (non-SOS commands were silently dropped)
- Cap audio file read to 5 MB to prevent OOM
- Validate and clamp battery values in pack_location_telemetry
- Coerce telemetry JSON fields to correct types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread app/src/main/java/com/lxmf/messenger/service/SosManager.kt Outdated
MatthieuTexier and others added 2 commits March 29, 2026 17:31
The coroutine Mutex was released asynchronously in the finally block,
meaning cancel()/deactivate() could return while the mutex was still
held. A re-trigger arriving before the finally dispatch would be
silently dropped — dangerous for a safety feature.

AtomicBoolean.set(false) is synchronous, so cancel/deactivate reset
it immediately, allowing trigger() to work right away.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. parseSosLocation now checks FIELD_TELEMETRY before falling back to
   text regex. Extracted as shared top-level function used by both
   MessageCollector (trail + notifications) and MessagingScreen (UI).
   Fixes missing breadcrumb trail for Sideband-compatible clients.

2. SosActiveTracker is restored from DB when MessageCollector starts
   (not just in Application.onCreate), so service disconnect/reconnect
   within a session no longer loses SOS indicators.

3. audioRecorder.start() runs on Dispatchers.IO instead of Main to
   avoid ANR from MediaRecorder.prepare() blocking the main thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both _on_lxmf_delivery and poll_received_messages serialized
FIELD_TELEMETRY (raw msgpack bytes) as str(value) or value.hex(),
making parseSosLocation's primary FIELD_TELEMETRY path dead code.

Now calls unpack_location_telemetry() to produce a dict with lat/lng
keys, enabling Kotlin-side GPS extraction from Sideband clients that
embed location only in FIELD_TELEMETRY (not in message text).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread python/reticulum_wrapper.py
Extract NaN check to local variable to stay under ComplexCondition
threshold of 4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MatthieuTexier and others added 5 commits March 30, 2026 18:18
- audioRecorder.start() back on Dispatchers.Main (was IO) so all
  MediaRecorder lifecycle runs on the same thread, avoiding
  IllegalStateException on some OEM/Android 12 devices.

- Poll path (poll_received_messages) now json.dumps fields_serialized
  like the callback path, so extractSosState and parseSosLocation
  FIELD_TELEMETRY primary path work for polled messages.

- Re-verify _state is Active after recording delay before stopping
  and sending audio, in case SOS was deactivated during recording.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap isSosMessageByField and parseSosLocation in remember() keyed
  on message.id/fieldsJson/content to avoid JSON parsing and regex
  on every recomposition.

- Move audioRecorder start/stop/cancel to Dispatchers.IO — prepare()
  does file I/O and shouldn't block Main. synchronized(lock) provides
  mutual exclusion; MediaRecorder has no thread affinity requirement.

- restoreIfActive() now uses compareAndSet on isTriggerRunning to
  atomically claim the slot, preventing concurrent trigger() from
  racing. Released in finally block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MapViewModel: keep both imports (first from SOS, map from upstream)
- DatabaseModule: combine both MIGRATION_43_44 statements (add source
  column + create interface_first_seen table)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test_poll_icon_appearance_also_in_fields_dict expected a raw dict
but fields is now a JSON string (consistent with callback path).
Parse the JSON string before asserting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@serialrf433

Copy link
Copy Markdown
Contributor

Would this also get solved by this PR? #689

MatthieuTexier and others added 3 commits March 31, 2026 08:27
- deactivate() falls back to synchronous DataStore read if cached PIN
  is null (brief window at startup before collector emits).

- Add composite index (source, senderHash, timestamp) on
  received_locations to avoid full table scan in SOS trail queries.

- Extract FIELD_COMMANDS sos_state in poll_received_messages path
  (was only done in callback path, leaving polled SOS messages
  without sos_state for Sideband clients).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace List<Any> + @Suppress("UNCHECKED_CAST") with
  SosSettingsSnapshot data class for combine/distinctUntilChanged.
  sensitivity and tapCount are change-triggers only (re-read in start).

- Add _state.value !is Active early return in startPeriodicUpdates()
  to close the TOCTOU window between the caller's state check and
  the job launch (deactivate() sets Idle synchronously).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
runBlocking violates the threading audit. Instead, make the scope
non-lazy so PIN/autoAnswer collectors start at construction time
(Hilt @singleton injection), before any deactivate() call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

Would this also get solved by this PR? #689
No, this PR adds boot start for the Reticulum service, but location sharing sessions are currently stored in memory only — they're lost on restart. So yes, you still need to open Columba for location sharing to resume.

@MatthieuTexier

MatthieuTexier commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Would this also get solved by this PR? #689
No, this PR adds boot start for the Reticulum service, but location sharing sessions are currently stored in memory only — they're lost on restart. So yes, you still need to open Columba for location sharing to resume.

I just did a PR for this on my repo but it needs this one to be merged first so I won't submit it yet.

@serialrf433

Copy link
Copy Markdown
Contributor

I just did a PR for this on my repo but it needs this one to be merged first so I won't submit it yet.

And i forwarded the information to the person who asked about this in here: #689 (comment)

@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

I just did a PR for this on my repo but it needs this one to be merged first so I won't submit it yet.

And i forwarded the information to the person who asked about this in here: #689 (comment)

I may submit the location sharing persistence PR first, as it's a simpler standalone feature — though it'll require some refactoring to decouple it from this one.
I haven't received any feedback here yet — no rush, I'll move forward with the location sharing PR in the meantime.

@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

Putting this PR in draft — some changes here (BootReceiver, DB migration for source column, ReceivedLocationEntity changes) now overlap with #744 (location sharing persistence + Doze resistance), which was extracted as a standalone feature.
Once #744 is merged, I'll rebase this branch to remove the duplicated code and resolve any conflicts. The SOS-specific changes (SosManager, SosTriggerDetector, SosAudioRecorder, SOS notifications, SOS UI) are unaffected and will remain as-is.

MatthieuTexier added a commit to MatthieuTexier/columba that referenced this pull request Apr 1, 2026
… column

LocationServiceCoordinator:
- Add resetFailedState() method
- acquire() checks permission when serviceFailed is true and resets
  automatically if re-granted (no process restart needed)

ReceivedLocationEntity:
- Document that source column is scaffolding for future SOS trail
  entries (PR torlando-tech#713)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MatthieuTexier and others added 2 commits April 14, 2026 13:17
…dips

The shake accumulator credited the full inter-sample gap whenever a
sample was above threshold, even when the previous sample was below.
Because the below-threshold branch only reset state after gaps >200ms,
bursty motion (e.g. several ~30-50ms spikes spaced ~150ms apart while
running or bumping the phone in a bag) was accumulated as if the phone
had been continuously above threshold — triggering SOS well before
500ms of actual sustained shaking, and neutralising the sensitivity
slider at 4.0x and above.

Fix: only credit dt when the previous sample was also above threshold.
A dip below threshold immediately breaks the continuous chain; a gap
longer than SHAKE_GAP_RESET_MS (200ms) resets the accumulator. Adds a
regression test exercising 5 bursty spikes at 4.0x sensitivity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous linear mapping `threshold = sensitivity * GRAVITY_EARTH`
made the upper half of the 1.0-5.0 slider unreachable by human shaking:
4.0x required ~4g net sustained for 500ms, 5.0x required ~5g. Only the
previous accumulator bug (which credited sub-threshold dip time as
"shaking") made those settings trigger at all — and it did so falsely.

Remap to `threshold = (0.5 + sensitivity * 0.5) * GRAVITY_EARTH` so
  1.0x → ~1.0g  (easy, catches vigorous shaking)
  2.5x → ~1.75g (moderate)
  5.0x → ~3.0g  (hard, needs deliberate strong shake)

All settings remain achievable by a human hand. Tests updated via a
`shakeThreshold(sensitivity)` helper that mirrors the new formula.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@brothercorvo

Copy link
Copy Markdown
Contributor

I love this and I will implement the same function in the https://github.com/FreeTAKTeam/reticulum_mobile_emergency_management
the only aspect to keep in mind would be to maintain some level of compatibility with the TAK standard.

emergency for creation, with values such as 911 Alert, Ring The Bell, Geo-fence Breached, In Contact
emergency cancel="true" for cancellation

@brothercorvo

Copy link
Copy Markdown
Contributor

Putting this PR in draft — some changes here (BootReceiver, DB migration for source column, ReceivedLocationEntity changes) now overlap with #744 (location sharing persistence + Doze resistance), which was extracted as a standalone feature. Once #744 is merged, I'll rebase this branch to remove the duplicated code and resolve any conflicts. The SOS-specific changes (SosManager, SosTriggerDetector, SosAudioRecorder, SOS notifications, SOS UI) are unaffected and will remain as-is.

@MatthieuTexier 0x06 MUST NOT used for SOS commands because LXMF constants identify it as image data.
Instead, use Sideband/LXMF-compatible FIELD_TELEMETRY = 0x02 and FIELD_COMMANDS = 0x09

@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

I love this and I will implement the same function in the https://github.com/FreeTAKTeam/reticulum_mobile_emergency_management the only aspect to keep in mind would be to maintain some level of compatibility with the TAK standard.

emergency for creation, with values such as 911 Alert, Ring The Bell, Geo-fence Breached, In Contact emergency cancel="true" for cancellation

Hi @brothercorvo, I looked for a standard but found nothing relevant, we might converge to a common protocol if it fits both needs.

@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

Putting this PR in draft — some changes here (BootReceiver, DB migration for source column, ReceivedLocationEntity changes) now overlap with #744 (location sharing persistence + Doze resistance), which was extracted as a standalone feature. Once #744 is merged, I'll rebase this branch to remove the duplicated code and resolve any conflicts. The SOS-specific changes (SosManager, SosTriggerDetector, SosAudioRecorder, SOS notifications, SOS UI) are unaffected and will remain as-is.

@MatthieuTexier 0x06 MUST NOT used for SOS commands because LXMF constants identify it as image data. Instead, use Sideband/LXMF-compatible FIELD_TELEMETRY = 0x02 and FIELD_COMMANDS = 0x09

Thank you @brothercorvo.
Humm I was pretty sure I used the commands field, might not be up to date... I will check that ASAP.
Regards

@brothercorvo

Copy link
Copy Markdown
Contributor

I love this and I will implement the same function in the https://github.com/FreeTAKTeam/reticulum_mobile_emergency_management the only aspect to keep in mind would be to maintain some level of compatibility with the TAK standard.
emergency for creation, with values such as 911 Alert, Ring The Bell, Geo-fence Breached, In Contact emergency cancel="true" for cancellation

Hi @brothercorvo, I looked for a standard but found nothing relevant, we might converge to a common protocol if it fits both needs.

I have implemented the Emergency function and created a pre-release. you can take a look here:
https://github.com/FreeTAKTeam/reticulum_mobile_emergency_management/releases

to understand the LXMF fields and create the RUST implementation of Reticulum I used deepwiki
image

@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

@brothercorvo Thanks for the careful review — I checked the code and it already follows your recommendation. SOS state is sent via FIELD_COMMANDS = 0x09 with sub-command COMMAND_SOS_STATE = 0x02 (see reticulum_wrapper.py:4684 sender, :3380 and :6300 receivers), and location telemetry uses FIELD_TELEMETRY = 0x02 (msgpack, Sideband-compatible). FIELD_IMAGE = 0x06 is only used for image attachments and never for SOS.

The greptile bot comment from March 29 that likely prompted your concern was about the poll-receive path falling through to a generic list handler structured like FIELD_IMAGE/FIELD_AUDIO — not about 0x06 being wired to SOS. That fallback was fixed on March 31 in 4ffa4dc.

MatthieuTexier added a commit to MatthieuTexier/columba that referenced this pull request Apr 24, 2026
… column

LocationServiceCoordinator:
- Add resetFailedState() method
- acquire() checks permission when serviceFailed is true and resets
  automatically if re-granted (no process restart needed)

ReceivedLocationEntity:
- Document that source column is scaffolding for future SOS trail
  entries (PR torlando-tech#713)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MatthieuTexier added a commit to MatthieuTexier/columba that referenced this pull request Apr 24, 2026
… column

LocationServiceCoordinator:
- Add resetFailedState() method
- acquire() checks permission when serviceFailed is true and resets
  automatically if re-granted (no process restart needed)

ReceivedLocationEntity:
- Document that source column is scaffolding for future SOS trail
  entries (PR torlando-tech#713)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@MatthieuTexier

Copy link
Copy Markdown
Contributor Author

Location sharing is not working anymore in v1, I don't have time for troubleshooting, I will resume the devs once the issue is solved.

MatthieuTexier added a commit to MatthieuTexier/columba that referenced this pull request May 10, 2026
… column

LocationServiceCoordinator:
- Add resetFailedState() method
- acquire() checks permission when serviceFailed is true and resets
  automatically if re-granted (no process restart needed)

ReceivedLocationEntity:
- Document that source column is scaffolding for future SOS trail
  entries (PR torlando-tech#713)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants