Skip to content

Fix pod pairing retries: double completion and a discovery Timer that never fires - #116

Merged
marionbarker merged 1 commit into
loopandlearn:devfrom
scrappy:pairing-fixes
Jul 27, 2026
Merged

Fix pod pairing retries: double completion and a discovery Timer that never fires#116
marionbarker merged 1 commit into
loopandlearn:devfrom
scrappy:pairing-fixes

Conversation

@scrappy

@scrappy scrappy commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #115.

Three small changes in the DASH/O5 pairing path, plus a format-specifier fix:

  • OmniPumpManager.pairAndPrime — on the connectToNewPod failure path, delete the extra completion(...) so completionFailure(...) is the only completion call, per your comment on the issue. This stops the second invocation from resetting the pairing UI's auto-retry latch and leaving a hidden retry running alongside the user's manual Retry.

  • BlePodComms.connectToNewPod — schedule the discovery polling Timer on the main run loop. The completion for discoverPods can run on a caller thread with no active run loop, where a timer scheduled on the current thread never fires and that discovery attempt hangs without ever timing out or calling endPodDiscovery(). Also return after each terminal branch so a single tick can't invoke the completion twice, and pass targetPod to the success completion instead of re-reading devices.first!.

  • PairPodViewModel — perform the pairing auto-retry via DispatchQueue.main.asyncAfter instead of Thread.sleep on a utility-QoS worker, so the retry attempt runs with a live run loop regardless of the fix above.

  • BluetoothManager — malformed %{public}%{public}@ in one os_log call.

Status: I'm running these fixes on my own phone now (cherry-picked onto the current iAPS submodule pin). The first two are straightforward to confirm from the code; my next pod change is the real test of the retry path — I'll follow up here with the result either way.

@marionbarker

Copy link
Copy Markdown
Contributor

Overview

In order to test this - we have 3 open branches in OmnipodKit to consider:

  • dev branch: old method of BLE connection to BLE pods
  • next-dev branch: proposed new method of BLE connection for BLE pods, tested with LoopWorkspace next-dev and in the process of being back-ported to the dev branch through PRs under test
  • ble-heartbeat is being tested with open PRs for eventual merge to dev branch

We need to confirm the changes in this PR apply to the ble-heartbeat and next-dev branches that have the associated changes in Loop and LoopKit.

Evaluation of other branches

Download the patch for PR 116 and use git apply to see if this applies cleanly for the ble-heartbeat and next-dev branches

The only conflict is the logging change in OmnipodKit/Bluetooth/BluetoothManager.swift that does not apply for those two branches. The patch was modified to remove that change and then it applies cleanly to those two branches.

Test Plan

  1. Use LoopWorkspace update_dev_to_3.14.4 as the test platform (old BLE method)

    • Point to OmnipodKit dev branch and apply the patch from this PR
    • Test using rPi DASH simulator
  2. Use LoopWorkspace update_dev_to_3.14.4 as the test platform (new BLE method)

    • point LoopKit to loopkitdev/LoopKit branch backport/pump-ble-heartbeat-api-dev, commit e32e2f
    • point Loop to loopkitdev/Loop branch backport/pump-ble-heartbeat-api-dev, commit 3d21cdc4
    • Point to OmnipodKit ble-heartbeat branch and apply the modified patch from this PR
    • Test using rPi DASH simulator
    • Test using an actual Omnipod 5 pod
  3. Use LoopWorkspace next-dev as the test platform (new BLE method)

    • Apply the modified patch from this PR to OmnipodKit, next-dev branch, commit
    • Test using rPi DASH simulator
    • Test using an actual Omnipod Atlas DASH pod

@marionbarker

Copy link
Copy Markdown
Contributor

Test 1 (from Test Plan)

✅ successful test

Configuration

LoopWorkspace branch update_dev_to_3.14.4, commit 5855ce5

  • OmnipodKit branch, dev, is 2 commits ahead - no code change, only update to CODEOWNERS
  • switch to dev branch and apply patch for PR 116

Build onto SE 2nd gen phone running iOS 18.

  • clean build
  • Eversense CGM connected, delete and switch to Nightscout as a CGM
  • no pump connected, add All Omnipod Types and select DASH
  • ✅ Nominal behavior for pair, prime and insert
  • Wait for green loop

@marionbarker

Copy link
Copy Markdown
Contributor

Test 2 (from Test Plan)

✅ successful test of new BLE method using LoopWorkspace 3.14.4 (not next-dev) with modification from this PR

Configuration

LoopWorkspace branch update_dev_to_3.14.4, commit 5855ce5

  • modify the 3 submodules: Loop, LoopKit and OmnipodKit to support the ble heartbeat method for OmnipodKit
    • Loop: loopkitdev/Loop branch backport/pump-ble-heartbeat-api-dev, commit 3d21cdc4
    • LoopKit: loopkitdev/LoopKit branch backport/pump-ble-heartbeat-api-dev, commit e32e2f
    • OmnipodKit: ble-heartbeat branch, commit c79c2d7
      • apply modified patch for PR 116 (no changes to OmnipodKit/Bluetooth/BluetoothManager.swift)
      • local name for branch is ble-heartbeat_plus_pr116
    • save this configuration in local clone with branch name backport/pump-ble-heartbeat_plus_pr116

Test with rPi DASH

Build onto SE 2nd gen phone running iOS 18.

  • clean build
  • Nightscout as a CGM
  • All Omnipod Types with pod type DASH
  • ✅ Nominal behavior when building over the existing rPi DASH connection on this phone
  • Green loop continues
  • Deactivate and pair a new rPi DASH pod
  • ✅ Nominal behavior for pair, prime and insert
  • Wait for green loop

Pair with actual Omnipod 5

  • deactivate rPi DASH
  • switch to Omnipod 5 Pod Type
  • ✅ nominal pair, prime and insert of Omnipod 5 POD, Lot PH1K04182331
  • lock phone
  • leave phone locked for > 15 minutes - unlock and observe Loop was looping with the Nightscout CGM that does not have a heartbeat

Xcode Debug log

This log contains the rPi DASH plus Omnipod 5 pairing with the new code from PR 116 using the new BLE heartbeat

Green Loop with Locked Phone

✅ Unlock the phone and observe that app is looping while locked with a CGM that does not have a heartbeat.

Leave the Omnipod 5 connected to this test phone with this configuration to get longer term log files.

@marionbarker

Copy link
Copy Markdown
Contributor

Test 3 (from Test Plan)

✅ successful test of next-dev (v3.15.1) with modification from this PR

Configuration

LoopWorkspace branch next-dev, commit 47fb757

  • confirm OmnipodKit branch, next-dev is at commit 475e031
    • apply modified patch for PR 116 (no changes to OmnipodKit/Bluetooth/BluetoothManager.swift)

Build onto SE 3rd gen phone running iOS 26.5.2.

  • clean build at 2026-07-26 12:51 PDT
  • Nightscout as a CGM (no heartbeat)
  • All Omnipod Types connected but with no active Pod
    • Last Pod was an Omnipod 5 that expired at 80 hours last night
    • Switch Pod Type to DASH
    • Select an Atlas DASH pod, lot PD1U08252521
  • ✅ Nominal behavior for pair, prime
  • had an issue during insert - but I've been having trouble with the connection between this test phone and Xcode
    • Xcode disconnected and app hung - save the available log
    • check later and no ips file found
  • rebooted phone, changed cables, quit and restarted Xcode and built again
  • ✅ nominal insert
    • save the available log
  • Wait for green loop

Xcode debug log

The debug logs from before the app hung just before insertion and after phone reboot, rebuild and successful insertion are in this zip file.

Green Loop with Locked Phone

✅ Unlock the phone and observe that app is looping while locked with a CGM that does not have a heartbeat.

Leave the Omnipod Atlas DASH connected to this test phone with this configuration to get longer term log files with the phone locked.

Testing Mistake

It does not affect what we are testing, but I did forget to the check the state of Pod Keep Alive.
The last time I used this test phone, I had that enabled and set to Silent Tune.

Since this is an SE phone that does not need the PKA running, disable PKA at 13:15 PDT.

Network issues

The phone was also not picking up the Nightscout site. Not getting CGM values. And I was seeing a lot of "network error" in the xcode log with upload failures.

  • I deleted Nightscout as a CGM and added it back
  • Now getting data flowing to and from Nightscout. (As of 13:20, all is ok).

Lock the phone and leave it alone for longer term testing.

marionbarker
marionbarker previously approved these changes Jul 26, 2026

@marionbarker marionbarker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approve by test.

Applied this modification to 3 different versions of OmnipodKit with successful tests of each of the following:

  1. original BLE method tested with LoopWorkspace "dev" branch, using rPi simulator
  2. backport modified BLE-heartbeat method to LoopWorkspace "dev" like branch
    • pair actual Omnipod 5 Pod
  3. next-dev modified BLE-heartbeat tested with LoopWorkspace next-dev branch
    • pair actual Omnipod Atlas DASH Pod

Comment thread OmnipodKit/PumpManager/OmniPumpManager.swift Outdated
Three related fixes in the DASH/O5 pairing path:

- pairAndPrime: a failed connectToNewPod invoked the caller completion
  twice (completion + completionFailure). The first call schedules the
  pairing UI's silent auto-retry; the second immediately resets the
  auto-retry latch and shows the error, leaving a hidden retry running
  concurrently with the user's manual Retry. OmniBLE calls the
  completion once here; the second call crept in during the OmnipodKit
  rewrite.

- connectToNewPod: the discovery polling Timer was scheduled on the
  calling thread. On a thread with no running run loop (the pairing
  auto-retry path) the timer never fires, so that discovery attempt
  never completes, never times out, and never calls endPodDiscovery,
  leaving discovery mode enabled and the scan running. Schedule the
  timer on the main run loop, and return after each terminal condition
  so a single tick cannot invoke the completion twice.

- PairPodViewModel: perform the pairing auto-retry via
  DispatchQueue.main.asyncAfter instead of Thread.sleep on a utility
  QoS thread, so the retry attempt runs with a live run loop.

Also fixes a malformed %{public} os_log format specifier in
BluetoothManager.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@marionbarker marionbarker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approve again. Thanks for removing the comments in OmnipodKit/PumpManager/OmniPumpManager.swift.

@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.

Looks good! Thanks to @scrappy for tracking this stuff down and to @marionbarker for all her fabulous testing & git management work.

Comment thread OmnipodKit/PumpManager/OmniPumpManager.swift Outdated

@ps2 ps2 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
marionbarker merged commit 09d82e2 into loopandlearn:dev Jul 27, 2026
@marionbarker

Copy link
Copy Markdown
Contributor

Thanks for your input @scrappy. These changes were merged into dev, ble-heartbeat and next-dev.

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