Skip to content

Wire pump BLE heartbeat to CGM reading schedule + preserve manager state on missing plugin#2461

Closed
loopkitdev wants to merge 3 commits into
LoopKit:next-devfrom
loopkitdev:o5-integration
Closed

Wire pump BLE heartbeat to CGM reading schedule + preserve manager state on missing plugin#2461
loopkitdev wants to merge 3 commits into
LoopKit:next-devfrom
loopkitdev:o5-integration

Conversation

@loopkitdev

@loopkitdev loopkitdev commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the pump BLE heartbeat to the CGM reading schedule (uses the coupled LoopKit setBLEHeartbeatRequest API) and hardens device-manager restore:

  • DeviceDataManager.updatePumpManagerBLEHeartbeatPreference sends a PumpHeartbeatRequest (last CGM reading date + expected interval) when the pump must provide the heartbeat, or nil when the CGM wakes the app itself; refreshed after every CGM reading.
  • Preserve persisted pump/CGM state when a plugin can't be instantiated. Previously, launching a build missing a plugin assigned pumpManager/cgmManager = nil, whose didSet deleted the persisted state — permanently wiping the pod pairing (incl. session keys) or CGM config. Now it logs and leaves the saved state on disk for a later build that includes the plugin.

⚠️ Coupling — must merge together

Requires the LoopKit setBLEHeartbeatRequest PR to compile; pairs with the OmnipodKit PR. Merge together. (Cross-links below.)

🤖 Generated with Claude Code

https://claude.ai/code/session_017Tbv6GBAMmGATWp6zwBDy7

Coupled PRs — merge together (three-way dependency)

Loop and OmnipodKit will not compile without the LoopKit protocol change; land all three together and bump the LoopWorkspace next-dev submodule pins in one commit.

ps2 added 2 commits July 14, 2026 20:21
Send the pump a PumpHeartbeatRequest (last CGM reading date + expected reading
interval) when it must provide the BLE heartbeat, or nil when the CGM wakes the
app itself, via the new setBLEHeartbeatRequest API. The last-reading time is
refreshed after every CGM reading (processCGMReadingResult already calls
updatePumpManagerBLEHeartbeatPreference), so the pump's heartbeat cadence tracks
the actual reading schedule.
instantiateDeviceManagers restored a manager by assigning pumpManager/cgmManager
= the result of ...FromRawValue(...). When the plugin wasn't available in the
running build, that returned nil, and the nil assignment fired the didSet, which
wrote rawPumpManager/rawCGMManager = nil and DELETED the persisted state — so
launching a build missing a plugin permanently wiped the pod pairing (incl.
session keys) or CGM config.

Only assign when the manager instantiates; otherwise log and leave the saved
state on disk so a later build that includes the plugin can restore it.
@marionbarker

Copy link
Copy Markdown
Contributor

Test

I'm putting test notes in comments for this PR so they are in one place.

Summary from initial testing: intermittent success

  • When looping while locked, the intervals are sometimes 5 minutes and sometimes 10 minutes
  • But sometimes, signal is lost while locked and no reconnection happens until app is reopened

Configuration

I started with LoopWorkspace next-dev commit 14b70bd.
I downloaded the patch files from Loop #2461, LoopKit 596 and used git apply to modify those 2 submodules.
I pointed directly to the loopkitdev o5-integration branch of OmnipodKit, commit fdafb4a.

I am running 2 test phones:

  • SE 2nd gen, iOS 18
  • SE 3rd gen, iOS 26

Narrative

  • 2026-07-18, around 17:00 to both phones: The first time I built I did not do a clean build folder, so I think the update was not clean
    • SE 3rd gen was already connected to an rPi DASH, continued that connection
    • SE 2nd gen was not connected to a pump: Added Ominpod 5 (PH1K04182331) to phone connection
      • When locked, loop went red

2nd gen, Omnipod 5 narrative

2026-07-19 06:16 lock 2nd gen (o5) phone

3rd gen, rPi DASH narrative

  • 2026-07-18 20:00: realized this phone was not looping while Loop app in the background; was from the original build (without cleaning build folder), so built again
    • observe that rPi is continuing to interact with the phone (see it in terminal) while phone is locked
  • 2026-07-19 06:07:

2026-07-19 06:16 lock 3rd gen (rPi DASH) phone

getGlucoseSample() bridged async work to sync via
DispatchGroup.wait(timeout: .distantFuture). It runs from update(), which
executes on a Swift Concurrency cooperative-pool thread; blocking that thread
starves the (core-count-sized) pool and deadlocks the whole concurrency
runtime under repeated Live Activity updates (Loop stops looping). Observed on
device as all cooperative threads stuck in getGlucoseSample -> dispatch_group
wait, with the Nightscout upload queues blocked behind them.

Make getGlucoseSample async and await getGlucoseSamples directly instead of
blocking. Upstream fix (both sites) submitted as LoopKit#2465.
@marionbarker

Copy link
Copy Markdown
Contributor

Configuration

I was behind on configuration. Updated both Loop and OmnipodKit this morning.

  • Workspace branch: next-dev
  • Workspace SHA: 14b70bd
  • Submodule name: branch, SHA
  • Loop: o5-integration, 162cd93
  • LoopKit: o5-integration, 5a3d5d8
  • OmnipodKit: o5-integration, fd38fca

Rebuilt onto the SE 2nd gen phone connected to an Omnipod 5 pod and then locked phone.

  • buildDateString: Mon Jul 20 10:15:52 PDT 2026
  • keeps looping while locked

Will report back with longer term results.

@marionbarker

Copy link
Copy Markdown
Contributor

Test

✅ successful test

future tests:

  • Test with Pod Keep Alive enabled for a DASH pod
  • Test using Omnipod 5 on personal phone

Configuration

The basic configuration uses:

  • LoopWorkspace next-dev, commit 14b70bd
    • in local clone, I created a new branch: pod-connection-mgmt and committed the following Loop, LoopKit and OmnipodKit SHA
    • Loop, commit 162cd93
    • LoopKit, commit 5a3d5d8
    • OmnipodKit, commit fd38fca

Customizations:

  • for the 2 test phones, I extended the alarm muting feature to include 12 hours in the picker so I can mute the phones at night
  • for my personal phone, I apply my standard customizations which are independent of the pod connection management changes

Test Narrative

I now have 3 phones using the latest version.

  • personal phone, iPhone 15 pro, G7 CGM (cgm has a heartbeat) , Atlas DASH pod
  • test phone SE 2nd gen, iOS 187.8, Nightscout CGM (no heartbeat), Omnipod 5 pod
  • test phone SE 3rd gen, iOS 26.5.2, Nightscout CGM (no heartbeat), rPi DASH simulator

Log Files

In each log file, the Build Details: buildDateString was used as a time flag to trim the contents in the Device Communication Log section. The older builds are known to have the problem mentioned in the link below that sometimes caused looping to stop when app was not in the foreground, so those messages were discarded as not useful:

Statistics from Log Files

Table with statistics from the 3 test cases, where the csv files are filtered for deltaSec > 180, then statistics for that quantity reported. (This is a measure of the time between communications sessions between the phone and the pod.)

deltaSec statistic : CGM/Pod G7
DASH
Nightscout
Omnipod 5
Nightscout
rPi DASH
hours in log 18 9 9
median (sec) 303 593 299
min (sec) 183 255 182
max (sec) 1260 650 653
average (sec) 414 471 391

Personal Log

The truncated log file (18 hours of data):

The csv file with the parsed Phone-Pod messages:

The longest gap, 1260 sec, was UTC timestamp 2026-07-21 10:53:44
there was time on 2026-07-20 in the afternoon (PDT) when the G7 was spotty and dropped out for a bit
I tried to keep the phone close, but there may be times when it is not in range

Test Phone, Omnipod 5 Log

Note that without this modification, an Omnipod 5 Pod cannot be run with a locked phone without a CGM with a heartbeat. This is a significant advance.

The truncated log file (9 hours of data):

The csv file with the parsed Phone-Pod messages:

Test Phone, rPi DASH Log

The truncated log file (9 hours of data):

The csv file with the parsed Phone-Pod messages:

@marionbarker

Copy link
Copy Markdown
Contributor

Test Fault

✅ successfully triggered a fault in an actual Omnipod 5 Pod and fault was reported

Configuration

SE 2nd gen running the pod-connection-mgmt branch from my local clone with Omnipod 5 pod paired.
Build and leave Xcode connected to the phone to capture any debug log files except it took too long to force the occlusion and Xcode got interrupted - so no debug log available.

Narrative

Test afternoon of 2026-07-21

Pod was within an hour of expiration

  • Save 45 g of carbs without bolusing.
  • Lock phone.
  • Inject olive oil into the Pod in an attempt to force a fault while bolusing
    • do this over a tray because the water in the pod is displaced by the olive oil

Observe looping on the Nightscout URL

  • 16:15 Autobolus of 1.8 U
  • drop is a little oily

I already broke the sound connection in case this pod faulted in the middle of the night. So I won't hear a fault.

  • 16:19 Manual bolus of 8 U
    • lock phone - listen to the clicks while bolusing

Increase therapy settings:

  • basal rate 6 U/hr
  • max bolus 20 U

More boluses

  • 16:33 Manual bolus 20 U
    • use a tool to clamp the cannula (the olive oil is not doing the job)
  • 17:04 Manual bolus 20 U
  • 17:19 Finally get an occlusion - interrupted Manual bolus 20 U at 15.05 U
    • I could hear the clicking halting intermittently during the second half of this bolus and then it stopped altogether

Log file

This is the entire log of the pod life including times before the current version of code was installed

  • buildDateString: Mon Jul 20 21:43:32 PDT 2026
  • obtained from an earlier report - this one has a later date because I tried to capture an Xcode debug log

Loop Report test-o5-full-life-fault 2026-07-21 173319-0700.md

Parsed messages from the full Omnipod 5 Pod life:
podState_Marion_newBLE-o5_20260721_1733_1.csv

@loopkitdev

Copy link
Copy Markdown
Contributor Author

Fixed in #2469

@loopkitdev loopkitdev closed this Jul 22, 2026
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