Skip to content

Backport the Omnipod BLE Heartbeat API from next-dev - #118

Open
marionbarker wants to merge 26 commits into
devfrom
ble-heartbeat
Open

Backport the Omnipod BLE Heartbeat API from next-dev#118
marionbarker wants to merge 26 commits into
devfrom
ble-heartbeat

Conversation

@marionbarker

Copy link
Copy Markdown
Contributor

Purpose

Backport to the dev branch the new BLE management scheme developed under the loopkitdev GitHub user name for next-dev branch.

This requires updates to LoopKit, which are merged to LoopKit/LoopKit dev and works best with updates to Loop, which are merged to LoopKit/Loop dev.

This was extensively tested by a few users and several test configurations.

Tests

There will be additional tests while this is a PR.

Prior History

There were extensive tests found in the following PR.

initial next-dev code changes and testing are found in these PR

code changes and testing of the backport to dev from next-dev are found in these PR

Commit history

There were several hot fixes that were applied to multiple open testing branches. The commit history is rather tortuous, but the code changes are clean.

marionbarker and others added 25 commits June 6, 2026 13:24
…ests

UnfinalizedDose's bolus initializer gained a required decisionId: UUID?
parameter; the source call sites were updated but
testBolusFinishedEarlyOnPodIsMarkedNonMutable was missed, so the
OmniTests target failed to compile ("missing argument for parameter
'decisionId'"). Passes decisionId: nil, matching the other test/source
call sites.
CryptoSwift 1.9.0 removed its `Data.bytes -> [UInt8]` extension (renamed
to `byteArray`). On the Xcode 26 SDK, `data.bytes` then resolves to
Foundation's new native `Data.bytes`, which returns a `RawSpan` -
breaking every crypto call site with errors like:

  Cannot convert value of type 'RawSpan' to expected argument type 'Array<UInt8>'
  Value of type 'RawSpan' has no member 'toHexString'

Builds pinned to CryptoSwift <= 1.8.5 are unaffected, so this only bites
users whose package resolution floats up to 1.9.0/1.10.0 (e.g. after
'Update to Latest Package Versions').

Replace `<data>.bytes` with `Array(<data>)`, which yields the same
[UInt8] and compiles against every CryptoSwift version (and doesn't
depend on CryptoSwift at all). Verified building OmnipodKit against
CryptoSwift 1.10.0 on Xcode 26.5.
CryptoSwift 1.9.0 removed its `Data.bytes -> [UInt8]` extension (renamed
to `byteArray`). On the Xcode 26 SDK, `data.bytes` then resolves to
Foundation's new native `Data.bytes`, which returns a `RawSpan` -
breaking every crypto call site with errors like:

  Cannot convert value of type 'RawSpan' to expected argument type 'Array<UInt8>'
  Value of type 'RawSpan' has no member 'toHexString'

Builds pinned to CryptoSwift <= 1.8.5 are unaffected, so this only bites
users whose package resolution floats up to 1.9.0/1.10.0 (e.g. after
'Update to Latest Package Versions').

Replace `<data>.bytes` with `Array(<data>)`, which yields the same
[UInt8] and compiles against every CryptoSwift version (and doesn't
depend on CryptoSwift at all). Verified building OmnipodKit against
CryptoSwift 1.10.0 on Xcode 26.5.
Use Array(data) instead of Data.bytes for CryptoSwift 1.9+ compatibility
Fix OmniTests: pass decisionId to UnfinalizedDose
hotfix loop-next-dev: Disable TBR enforcement as a workaround with mismatched basal limits
Update loop-next-dev to support Omnipod 5 Pods with token
fix for loop-next-dev: Properly handle and display times for DASH reset type pod faults
Commit f6d56f9 made DetailedStatus return nil (not 0.0) when the fault time is 0x0000, since that
is a DASH reset-fault sentinel rather than a valid time. Update the two tests that still asserted
0.0 (testPodInfoNoFaultAlerts, testPodInfoDuringPriming) to XCTAssertNil, matching the intended
behavior. Fixes the OmniTests failures blocking LoopWorkspace next-dev CI.
f6d56f9 made DetailedStatus return nil (not 0.0) when the fault time is 0x0000, a DASH reset-fault
sentinel rather than a valid time. Update the two tests still asserting 0.0 (testPodInfoNoFaultAlerts,
testPodInfoDuringPriming) to XCTAssertNil, matching the intended behavior.
OmniTests: faultEventTimeSinceActivation is nil for 0x0000 fault time
…-RileyLink

Fix for loop-next-dev branch: Remove unneeded reference to release RLs when switching pump types
When no host refreshes the reading schedule — an app that only calls the legacy
setMustProvideBLEHeartbeat sets the heartbeat target once and never updates it —
the fixed target goes chronically overdue, so every StartDelay probe pins to
heartbeatMinDelaySeconds (the 60s floor). That ~60s background wake cadence is ~5x
the intended ~interval cadence, burns the iOS background execution budget, and
gets the app suspended for long stretches (Trio field report on LoopKit/LoopKit#599:
99% of heartbeat intervals were 61s, with six 16–128 min suspension gaps).

Track the reading interval and when the target was last set. When the target
hasn't been refreshed within ~1.5 reading intervals, advance it by whole intervals
in issueDelayedConnectProbe so we hold the expected ~interval cadence. A host that
refreshes the schedule every reading (Loop) keeps the set-at timestamp fresh, so
its floor-based late-reading retry is unaffected.

Scheduler simulation: set-once host 60s -> 300s; per-reading-refresh host 300s
unchanged; a single late reading still retries at the floor.
Heartbeat: advance a stale target instead of collapsing to the 60s floor

@itsmojo itsmojo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@marionbarker

Copy link
Copy Markdown
Contributor Author

Test

✅ successful continuation of Loop dev with BLE backport on SE 2nd gen phone

Use LoopWorkspace branch update_dev_to_3.14.4, commit 2bf6a99.

  • this branch was updated today to include the backported code changes in Loop, LoopKit and OmnipodKit

Build to the SE 2nd gen phone which is connected to an Omnipod 5 pod and is using Nightscout as a CGM.

✅ It continues to loop while locked.

  • let this run for the rest of the day
  • each time I unlocked this phone it was connected to the Omnipod 5 pod
  • last report for this comment was at 20:40.

But- had issues on the SE 3rd gen phone - write those up as another comment.

@marionbarker

Copy link
Copy Markdown
Contributor Author

Test with Trio and Loop next-dev on SE 3rd gen phone

❌ there were frequent times when Trio stopped looping while locked - we need to look into those. Is there another modification needed for Trio?
❌ there were a few instances when the next-dev Loop app on the same test phone stopped looping while locked.
✅ The fault alert worked for Omnipod 5 reaching 80 hours

Trio timeline

I don't have the timeline down 100%. But I was running a backport version of Trio, test case 3 from this comment: #117 (comment)

Around 12:30, I happened to notice a Trio not looping alert and unlocked the phone.
There was a critical comms loss during an insulin delivery event.
I used every trick to try and restore comms with the Omnipod 5 Pod.

  • cycle Bluetooth
  • quit/restarted app
  • rebooted phone
  • built a fresh version of the app making 100% sure I had the correct SHA for the submodules
  • at the same time, I had been trying the rPi that doesn't quite work properly as an Omnipod 5 simulator and that may or may not have caused an issue - I quit the rPi, but I was messing around with a test on a different phone at the same time.
  • I was about ready to discard the pod when it came back
  • In the csv file attached below, you can see many attempts to reach the pod:
    • starting at UTC: 2026-07-28 19:32:19 (right after a cancel temp basal)
    • ending at UTC: 2026-07-28 19:40:07 when an 0x1d finally came back from the pod
    • the reboot must have happened around 2026-07-28 19:34:35, when there was a 3-minute gap in attempts to reach the pod

Several other times during the afternoon I noticed Trio not looping message on the lock screen.

  • when I unlocked the phone, I could see the pod reconnect, but it was clearly not looping while locked

I captured a few notes with time stamps after this - transcribing here:

Note at 17:40
It has my oldest Omnipod 5 on it - manuf April 2023. It could be the pod. I'm not sure how to tell.

I just confirmed I have all the right branches and built again to the phone:

SE 3rd gen running iOS 26, Omnipod 5 Pod, Nightscout as a CGM
The looping stats say 166 loops and 210 CGM readings - so definitely down from yesterday.

At 17:35, disabled auto-lock and leave phone unlocked with Trio in foreground for at least half and hour

Note at 18:15
18:12 - Trio phone kept looping while in foreground and phone open.
18:13 - lock the phone and come back later to check.

Note at 20:10
Both Loop and Trio showed no signal with I unlocked at 20:08.
They both connected right away/

I'm looking to see if there are app limits anywhere on the phone - not finding them.

Locked phone again to wait for the 80 hour fault to hit.
The new fault detection should wake up the phone for an fault.

Note at 20:44
Alarm at 20:32.

  • ✅ I can see Pod Expired. Insulin Delivery Stopped alert showing on the lock screen before unlocking.
    Unlocked and opened app and deactivated the pod.

Trio log

20260728_2106_Trio_ble-heartbeat_o5_log.txt.zip

Trio csv file

podState_Marion_backport-ble_20260728_1.csv

Loop next-dev testing

A few times when I unlocked the phone to check on the Trio app, I noticed the Loop next-dev app was also showing not looping.

I noticed this a few times and it can be seen in the csv file. There were 3 instances of > 1000 sec today and there was one yesterday too. The recovery times are:

  • 2026-07-28 21:32:45
  • 2026-07-29 02:08:34
  • 2026-07-29 02:41:44
  • 2026-07-29 03:07:49

Loop Report

csv file

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.

4 participants