Skip to content

Scheduled spot SpO2 (#103) + persist background-sync data in a cold process - #132

Closed
foureight84 wants to merge 2 commits into
mainfrom
feat/103-scheduled-spot-spo2
Closed

foureight84 wants to merge 2 commits into
mainfrom
feat/103-scheduled-spot-spo2

Conversation

@foureight84

Copy link
Copy Markdown
Owner

Refs #103. The Estimated label from #129 is confirmed on hardware. This adds the other half the reporter asked for: the app starts a real spot SpO2 on the SpO2 interval. It also fixes a background-sync bug found on the way.

1. Background sync data was dropped in a cold process (4df27b0)

RingSyncWorker publishes what it pulls onto PulseEventBus (replay = 0). The only collector that writes it to Room was started by the UI's startup (PulseLoopApp). In a process Android launched just for the worker, nothing was listening, so every event was dropped. YCBT never deletes ring history, so its data was likely re-fetched on the next open, but I haven't checked other families. EventPersistenceSubscriber is now one per process, started in PulseLoopApplication.onCreate, and AppServices uses the same instance.

2. App-started spot SpO2 (f4d930c)

  • Setting: Measurement Frequency → Measure SpO₂ from the app. Off by default, shown for YCBT rings with MANUAL_SPO2 while SpO₂ is on. Stored in SharedPreferences (ScheduledSpo2Prefs), so no schema bump that could clash with feat(sleep): extend a sleep record over time the ring missed (#106) #130.
  • Slots (ScheduledSpo2Slots): halfway between the ring's own readings, on a grid from local midnight in local wall-clock time: 60 min → :30, 30 → :15/:45. A run that starts within min(5 min, interval/4) of a ring reading, or would still be measuring at the next one, is skipped.
  • Scheduling (ScheduledSpo2Worker): one-time work that appends the next slot (APPEND_OR_REPLACE) as it finishes, and always returns success. App start and every RingSyncWorker run call ensureScheduled (KEEP) to revive a cancelled chain. Saving settings reschedules it (REPLACE). Best effort: Doze defers runs.
  • Measuring: the reporter showed the ring stops the measurement when the link drops, so the run holds the connection until 04 0e.
    • If the app has a live link (AppServices.peek()), it uses coordinator.measureSpO2Only(). It skips during a workout, which polls SpO2 itself.
    • Otherwise it uses a transient RingBLEClient with its own RingSyncCoordinator. It runs the normal startup sequence, waits for the history sync to drain (the same wait factoryResetRing uses), then measures. The reading settles and is stored exactly as the Vitals button stores it.
    • It yields to the app if the app opens before the measurement starts, but not during one.

Tests

./gradlew testDebugUnitTest: 1584 pass, including 9 new ScheduledSpo2SlotsTest cases: rollover, a 35-min grid restarting at midnight, a DST change, the late-run guard, and every slot passing its own guard.

Not checked on a ring. To check: the transient-client path end to end, how many overnight slots Doze skips, and that the stored reading appears as a spot reading, not a duplicate of the ring's own log.

🤖 Generated with Claude Code

…rted

RingSyncWorker publishes what it pulls from the ring onto PulseEventBus, which keeps
no backlog (replay = 0). The only collector that writes it to Room was built and
started by the UI's startup (AppServices / PulseLoopApp), so in a process Android
launched just for the worker, every event was published to no one and dropped.

The subscriber is now one per process and started from PulseLoopApplication.onCreate;
AppServices uses the same instance. Groundwork for #103's scheduled spot SpO2, which
runs from the background too.
On YCBT rings the hourly SpO2 is computed from heart rate. The ring's manual
measurement is real, so the app can now run it on the SpO2 interval:
Measurement Frequency -> "Measure SpO2 from the app", off by default, offered for
YCBT rings with manual SpO2.

- Slots sit halfway between the ring's own readings on a grid from local midnight
  (60 min -> :30, 30 -> :15/:45). A run WorkManager starts within 5 min of a ring
  reading, or that would still be measuring at the next one, is skipped.
- ScheduledSpo2Worker is one-time work that appends the next slot as it finishes;
  app start and RingSyncWorker revive the chain if a run was cancelled.
- The ring stops the measurement when the link drops, so the run holds the
  connection until 04 0e: through the app's link when it has one, otherwise a
  private client with its own RingSyncCoordinator, which runs the normal startup
  sync first and stores the reading exactly as the Vitals button does.
@foureight84

Copy link
Copy Markdown
Owner Author

Folded into #134 (which is retargeted to main and includes all commits from #132 alongside longer intervals, 120m HR, and RC4 fixes).

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.

1 participant