Heartbeat: pull must-provide from the delegate, not just the app's push - #121
Heartbeat: pull must-provide from the delegate, not just the app's push#121loopkitdev wants to merge 2 commits into
Conversation
OmnipodKit only ever received the heartbeat preference (setMustProvideBLEHeartbeat
/ setBLEHeartbeatRequest) pushed in by the host; it never queried the host's
pumpManagerMustProvideBLEHeartbeat delegate. So a host that relies on the classic
LoopKit pull instead of pushing (e.g. Trio) never turned the heartbeat on: the
StartDelay probe was never armed on background, and with a network CGM nothing
woke the app — an 8-hour outage in a Trio field log. MinimedKit, the reference
RileyLink pump, drives its heartbeat entirely from this delegate; OmnipodKit was
the outlier.
Make the delegate the single source of truth for enable/disable:
- updateBLEHeartbeatPreference() resolves must-provide via
pumpDelegate.call { $0?.pumpManagerMustProvideBLEHeartbeat(self) == true }
(mirroring MinimedKit) and drives the BLE heartbeat / RileyLink timer.
- setMustProvideBLEHeartbeat and setBLEHeartbeatRequest no longer set the enable
bit themselves — they call updateBLEHeartbeatPreference(). setBLEHeartbeatRequest
additionally records the CGM reading SCHEDULE (via setHeartbeatSchedule) purely
for cadence alignment. So the push and the pull can never hold conflicting enable
state — the push no longer decides enable, only when the wake lands.
- Also called on pod connect, so the heartbeat engages even for a host that never
pushes.
The BLE-side setHeartbeatRequest is split into setHeartbeatEnabled (enable, from the
delegate) and setHeartbeatSchedule (cadence, from the push). Heartbeat still only
runs when the host says the CGM can't self-wake (network CGM); a local CGM resolves
to false and gets no probe.
OmniTests: 189 tests, 0 failures.
Test ConfigurationSet up a few configurations. Record them as they are created. Later come back for data harvesting. Edited to put separate tests configurations in individual comments. This comment has configuration for Trio. Trio testThis test used a Trio instance already under test that was not looping without a modification in either Trio or OmnipodKit.
|
Issue with this PR❌ the build hangs for all versions other than Trio (and yes I confirmed PR 121 does work with Trio both with and without the timing modification mentioned in PR 118.)
NarrativeWe have a test phone running the ble-heartbeat method with iAPS with an Omnipod 5 Pod, so I thought I would try it. I ran into a hang using loopkitdev:heartbeat-delegate-pull with iAPS. Then I tried LoopWorkspace Finally I repeated an attempt with LoopWorkspace iAPS hang screenshotuse iAPS dev branch, point to LoopKit/LoopKit:dev and loopdevkit/OmnipodKit:heartbeat-delegate-pull
Loop dev backport screenshotuse LoopWorkspace
Loop next-dev screenshotuse LoopWorkspace
|
updateBLEHeartbeatPreference resolved the must-provide answer with a synchronous pumpDelegate.call, which does _queue.sync on the delegate's queue. The push entry points (setMustProvideBLEHeartbeat / setBLEHeartbeatRequest) are invoked by the host on that same delegate queue (Loop, iAPS), so the sync deadlocked and hung the app. Trio was unaffected only because it doesn't push — it engages the heartbeat purely via the on-connect pull, which runs on the BLE queue. Hop the delegate query onto a private serial queue before calling pumpDelegate.call, so it can never run on the delegate queue regardless of the caller. state is Locked, so reading it off-queue is safe. Field report: loopandlearn#121 — hang on Loop / iAPS / Loop next-dev, fine on Trio.
Test Build✅ Using the latest version of PR 121, all three builds that were building onto the phone and then hanging yesterday, are successful - no hang. But none of these were tested with a pod.
✅ Confirm the Trio build still works. (This is the Trio build with the 2 commits @dnzxy added yesterday)
Build the same Trio version (with both commits from PR 121) onto SE 2nd gen, iOS 18 phone without a pod connected and test with rPi DASH.
Narrative: Signal Loss for Trio testTest Phone: SE 3rd gen running iOS 26, Omnipod 5 pod, Nightscout as a CGM There was a signal loss of the pod that occurred around the time of the rebuild for the Trio phone.
Try to restore communications:
ips file, SE 3rd gen Triothere were 2 ips file, post the most recent: Back up the OmnipodKit by checking out the previous commit, (changed from 02ff824, to 43cf8f0)
Trio log file, SE 3rd gen Trio |
Test Build and rPi for Loop dev backportThe issue reported for the Trio build and the rPi DASH in previous comment, is repeated in the Loop dev test below. Start with update_dev_to_3.14.4, commit 8af5ca0
Build onto SE 2nd gen running iOS 18
|



Problem
A Trio field log showed an ~8-hour locked-looping outage on an O5 pod with a network CGM. On every background, OmnipodKit disconnected the pod but never armed the StartDelay heartbeat probe, so nothing woke the app. Root cause:
heartbeatEnabledwas never set — OmnipodKit only ever received the preference the host pushes (setMustProvideBLEHeartbeat/setBLEHeartbeatRequest), and Trio doesn't push it. OmnipodKit never queried the host'spumpManagerMustProvideBLEHeartbeatdelegate.That delegate is the classic LoopKit pull contract. MinimedKit (the reference RileyLink pump) drives its heartbeat entirely from it (
delegate?.pumpManagerMustProvideBLEHeartbeat(self) == true). OmnipodKit was the outlier — pull-blind — so any host that relies on the pull instead of pushing got no pump heartbeat at all.Fix — the delegate is the single source of truth
updateBLEHeartbeatPreference()resolves must-provide viapumpDelegate.call { $0?.pumpManagerMustProvideBLEHeartbeat(self) == true }(mirroring MinimedKit) and drives the BLE heartbeat / RileyLink timer. It is the only place the enable bit is set.setMustProvideBLEHeartbeatandsetBLEHeartbeatRequestno longer set enable themselves — they callupdateBLEHeartbeatPreference().setBLEHeartbeatRequestadditionally records the CGM reading schedule (newsetHeartbeatSchedule) purely for cadence alignment. So push and pull can never disagree — the push no longer decides enable, only when the wake lands.setHeartbeatRequestis split intosetHeartbeatEnabled(enable, from the delegate) andsetHeartbeatSchedule(cadence, from the push).Heartbeat still runs only when the host says the CGM can't self-wake (network CGM); a local CGM resolves to
falseand gets no probe.Testing
Built + OmniTests on the dev line (dev LoopKit + ble-heartbeat): 189 tests, 0 failures. A
[heartbeat] … mustProvideBLEHeartbeat(delegate)=<bool>line is logged on each evaluation, so a Trio run will confirm the delegate returnstruefor its network CGM.Builds on #117 (merged).