Repository navigation
G7SensorKit on the watch, including but not limited to directAuth capability - #80
Conversation
- watchOS target: G7SensorKit-watchOS framework target and shared scheme, deployment target 10.6 to match LoopKit, and the iOS target's SDKROOT pinned to iphoneos so the watchOS target cannot satisfy an iOS dependency. next-dev's Share sources are members of the watch target too, because G7CGMManager uses them. - Watch acquisition, all under #if os(watchOS) in G7BluetoothManager: a daemon-held connect request re-lodged after each reading, with the process held across the sensor's tail; a bootstrap scan for first contact, a sensor change, three missed bursts, or a user Reconnect; radio census lines for diagnosis. Phone acquisition is unchanged. - Pairing code: entered on the phone once per sensor (a settings section shown only when a paired watch has the app) and synced to the watch in the CGM state. - The watch declares display type .watch. - An adopted sensor latches its activation date on the first reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- G7P256Field: fixed-width 4 x 64-bit Montgomery field with a 4-bit window scalar multiply, behind G7P256.add and G7P256.multiply. A scalar multiplication goes from 145 ms to 2.5 ms in an unoptimized Debug build. - The sensor drops a key exchange after about four seconds without a reply. exchangeKeys computes rounds 1 and 2 before the first request and round 3 before requesting the sensor's, logs the compute times, and checks the sensor's advisory proofs only after replying. - Watch discovery with no known peripheral identifier scans until the sensor is found instead of for one capped pass, and publishes G7WatchDirectRead.isSearching and searchingNote. Scan state resets when Bluetooth powers off so the next poweredOn restarts it. - G7CGMManager.defaultDisplayType (watch on watchOS) backs every initializer and G7Sensor, so a freshly created manager on the watch no longer authenticates in the phone's slot. - G7CGMManager.sensorSessionLengthIsKnown says whether sensorEndsAt comes from the sensor's reported session length.
- The adopted peripheral is reported by G7BluetoothManager, recorded in the sensor's credentials, and saved to G7CGMManagerState.peripheralIdentifier, which is already restored into the Bluetooth layer at launch. The separate rememberedPeripheralID in user defaults is gone; an existing value migrates into state once, and a freshly created manager discards it. - The arm's last-reading clock is in memory, seeded at launch from the state's latestReadingTimestamp. - The missing-code and searching signals are G7CGMManager.watchNeedsCodeFor and watchIsSearching, with G7CGMManager.watchStatusDidChange, instead of G7WatchDirectRead statics backed by user defaults. - The re-lodge arm is fixed at holdApp; the diagnostics toggle and its key are removed. - The legacy G7Lab.* keys are removed on first launch.
Configuration sharing (LoopKit's DeviceConfigurationSharing): - The export carries the sensor's identity, activation and pairing code; the key, peripheral handle, readings and Share account stay with the exporter. It changes only when the sensor or its code does, so a host can rebuild on change. - init(adopting:) replaces receivePairingCode(_:phoneSensorID:). A manager built from an export reads directly with the passed sensor and code, records configuredByAnotherController, and asks the sensor for its version so the session length is known before the first relaunch. - Watch build conditionals that were really about role are run-time checks on configuredByAnotherController: forcing direct mode at restore (now in the state's decode), latching the activation date from the first reading, and the code-replacement prompt when a sensor changes while eavesdropping (UI-controlled managers only). The pairing-code status and entry API is no longer phone-only. The display slot, the watch acquisition arm's API and the one-time defaults migration stay platform conditionals. - A passed manager keeps its sensor at the end of a suspected-session-end grace period, whether the period ends while the app runs or ended while it was not running: the exporter decides when the sensor ended, and its next export brings the replacement. Deleting it retracts its lifecycle alerts and lets go of the sensor without recording a sensor end or archiving it. - The unused reacquireForNewSensor path is removed. Cleanups: - Comments state intent in a line or two, without issue numbers or history dates, and the false ones are corrected (the pairing code reaches the watch in the export). G7Authenticator's advisory comment is back to stock; the budget-warning log lines lose their banners. - The grid-delay re-lodge arm has a neutral name, which is also its raw value; nothing stores it. - The watch simulator's x86_64 exclusion is dropped. It came in for a dependency that no longer exists. Tests: G7DeviceHandoffTests (11), and deleting a passed manager retracts every lifecycle alert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only earlier test builds wrote those keys; no upstream install has them. - Removes two watch conditionals in G7CGMManager and the helper behind them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop the duplicate-connect guard in handleDiscoveredPeripheral. It was written for the removed ride-only mode, whose connection events fired ~10/s; the arm's lodged flag already blocks a second connect, and the active peripheral manager is reused rather than re-created. - Drop the grid-delay re-lodge arm (Relodge, RelodgePlan, nextFire, gridDelay, fireOffset, lead) and the lodge's startDelay parameter. The re-lodge has been fixed to the 35 s hold; relodgeHold(sinceLinkUp:) keeps that decision unchanged. - The reading clock now serves only the 3-missed-bursts check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- isScanning and isConnected are stock again: a synchronous read on the manager's queue. The watch-only 50 ms bound existed for a watch screen that read them on the main thread; that screen now shows the status the CGM manager already publishes (searching, code needed, last reading) instead. - Drop the user's "Reconnect sensor" action (G7CGMManager.reconnectG7, G7Sensor.reconnect, G7BluetoothManager.reconnect). It was a manual way out of a stuck first connection; the scan after three missed readings covers the same case without a button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Role (configuredByAnotherController), carried in the sensor's credentials: - A manager passed its configuration keeps the peripheral handle it finds, including when CoreBluetooth swaps the peripheral object; handles are per device, so the export cannot carry one. A manager set up through its own UI keeps the identifier it paired with, as before. - Display slot: a manager set up through its own UI claims .phone; one passed its configuration claims the secondary (.watch) slot, so it never takes the slot of the controller that passed it. defaultDisplayType and its platform conditional are gone; the UI initializers default to .phone again. Reading mode: - Reading directly with no pairing code and no key connects to nothing, not even the sensor's own peripheral: such a link closes unauthenticated. This extends the existing no-code, no-handle check. Reaching the sensor that way sets the code-needed status. Every path that sets up direct reading through the UI stores a code, so a UI-configured manager never meets this. Plumbing, compiled everywhere: - The bluetooth manager's delegate callbacks (log line, can-authenticate, adopted peripheral, status changed) keep their empty defaults without a platform conditional. - The state-restoration relaunch line goes to the device log. - Neutral names: needsCodeForSensor, isSearchingForSensor and the G7CGMManager.statusDidChange notification replace the watch-prefixed ones. Tests: the display slot by role; only a passed sensor keeps the peripheral it finds; direct reading without a code or key cannot authenticate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- G7WatchAcquisition.swift, a member of G7SensorKit-watchOS only, holds the arm: its state, the hold-then-connect re-lodge, the restored-as-connecting cancel, the first-contact and three-missed-readings scan, the reading clock, and the glance notes. The constants and pure decisions stay on G7WatchAcquisition, so the watch tests reach them unchanged. - G7BluetoothManager reaches it through G7AcquisitionArm: scan, found, connected, disconnected, failed, radio down, restored, and the reading clock. The arm is created once in init behind a single `#if os(watchOS)`; the iOS build has no arm and runs the stock steps exactly as before. This is the kit's one platform conditional. - The reading-clock call sites in G7Sensor and G7CGMManager are plain calls that do nothing without an arm. - A connect the daemon refuses at once is retried after 30 s, every time. The stop-after-two rule guarded the grid-delay arm's fractional start delay, which is gone. - Members the arm uses lose `private`; nothing else about them changes. Tests: the iOS target runs the stock acquisition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The adopting init gains the localState parameter from LoopKit's contract and ignores it; G7 keeps no local state yet. Tests: G7SensorKit 176 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bench, 2026-10-04: discovery took 4 min 51 s, about one sensor broadcast cycle, and it then asks for the system Bluetooth pairing. Discovery happened with the screen off, so the note asks only that Loop stay open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watch arm's picture of its connect request (`lodged`) is updated only by Bluetooth callbacks. One that never comes leaves nothing lodged and nothing scheduled to notice: the missed-burst check lives in scan(), which runs at launch, poweredOn, a forget or after a bootstrap pass. 2026-10-05 08:08:55: an app update relaunched the watch with its own pending connect restored as .connecting. The arm cancelled it as foreign (the 09-16 stale-request rule); the cancel took effect in bluetoothd (a force-quit relaunch at 09:27 restored no request) but no callback or .disconnected state reached the app, so the 2-s follow-up stood down and no connect was requested for 79 minutes. The force-quit recovered it only because launch runs scan(): 16 bursts missed → bootstrap pass → link at the next broadcast. Earlier restores-as-connecting (09-17, 09-18) recovered because their cancels were answered at once. recheck() runs what scan() runs at launch on any wake the host reports: connected or a recent request standing → nothing; nothing standing → lodge; three bursts missed → a bootstrap pass. A pass in progress already owns acquisition. G7CGMManager.recheckAcquisition() reaches it; on the phone there is no arm and it does nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The system's Bluetooth pairing prompt appears only the first time a watch meets a sensor. Bench 2026-10-04: the new sensor at 23:41 drew a "Just Works" pairing request (sensor bond=2); the same sensor re-found at 00:06 after an install that wiped the app's data did not (bond=1, lePaired 1), because the system's pairing outlives the app. While searching the app cannot tell which case it is in, so the note says "if". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G7 reconnect fails with
|
Applies the review of LoopKit #626 more widely: drops isConfiguredByAnotherController and the unused sensorSessionLengthIsKnown, and makes members only the kit reads internal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From EJM0's and kristinkand's port of this branch to Trio, where each closed a gap of half an hour or more without readings: - Misses count from the manager's start at the earliest. A fresh manager counted from a long-stale saved reading and went straight to a scan pass instead of lodging by its known identifier. - A wake past the scan cap ends a pass the app was suspended in; its timer never ran, and every wake was turned away until the app opened. - A peripheral restored as connecting or disconnecting, with no request or close of ours, is cancelled and replaced by our own connect, with the process held for the looks. After a restored close the connect waits out the sensor's advertising tail like any other close. Our cancel of a restored connect had no link, so the new connect goes in at once rather than after a tail wait. Not taken: lodging when the app leaves the screen, and handing the wait to CBConnectPeripheralOptionStartDelayKey. Both connect into the tail, which the sensor drops and watchOS counts against the watch, and the start-delay option doesn't seem to work on the watch. The tail clearance now says so. The watch notes are localizable and no longer name the app. Co-Authored-By: elias maurer <eliasjosefmaurer@gmail.com> Co-Authored-By: kristinkand <163904051+kristinkand@users.noreply.github.com>
…tch setting - A reconnect the sensor answers authenticated but not bonded (05 01 02 after the system bond was lost) runs the certificate phase with the saved key and bonds again, without the code (EJM0's fix). If that fails, the key is dropped and the next connection pairs with the code, as LoopKit#98 does. - A pairing whose bond the sensor never confirmed, because the system pairing request couldn't show with the app in the background, used to surface later as a wrong pairing code. When that key is refused, the new Sensor Pairing Not Finished alert says to keep the app open and accept the request; it is retracted at the next reading (kristinkand's fix, worded for any app). - A Read on Apple Watch setting in the G7 settings, offered when a paired watch has the app installed or while it is on. The manager reports it as isConfigurationSharingEnabled, so the host passes the sensor to its watch only while it is on; the footer says the watch takes the sensor's watch slot. Eavesdropping, the code entry the watch needs sits under the setting. - The settings card counts down to the next reading in minutes and seconds, instead of counting up the minutes since the last; once a reading is a minute late it shows the minutes since, as before. - The setting, the new-sensor alert and the status text are localizable. Co-Authored-By: elias maurer <eliasjosefmaurer@gmail.com> Co-Authored-By: kristinkand <163904051+kristinkand@users.noreply.github.com>
…with the code From Pete Schwamb's LoopKit#80 (b97ad44, after LoopKit#98): a stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again (05 01 02 after the system bond was lost), throws storedKeyNotBonded. That counts as a stale key: it's dropped while a code is at hand, and the next connection pairs with it. Before, the first case was an unexpectedResponse that kept the key, and the second failed on every connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
… the screen Pete Schwamb's take in LoopKit#80 (8efdffc): lodging as the app leaves the screen connects into the sensor's advertising tail, which the sensor drops and watchOS counts against the watch, and the start delay doesn't seem to work on the watch. Our tests agree it costs readings: after a start-delay connect, 4 of 5 gaps on 23 October and 2 of 68 bursts on 24 October. Leaving the screen with a hold running now keeps the hold: it waits out the tail, and if watchOS ends it first, its end lodges at once. With no hold and no request standing, it still lodges a plain connect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
From Pete Schwamb's LoopKit#80 (b97ad44): until the next reading is due, the settings card counts down to it in minutes and seconds; once a reading is a minute late, it shows the minutes since the last, as before. Co-Authored-By: Pete Schwamb <pete@schwamb.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…t-dev; no start delay after a loop kristinkand/G7SensorKit untethered-fallback 5e32eb7 (test branch only): - a refused rebond drops the key, the next connection pairs with the code (#80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) - #98: calibration replies decoded (accepted high no longer shows as refused), a queued calibration older than 5 minutes is dropped; #83: the paired sensor's warm-up time on Sensor Paired; #93: why no scan, logged - the G7 settings count down to the next reading (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…G7SensorKit#80) kristinkand/G7SensorKit untethered-pr-plus 3f066c6, on top of untethered-plus 4259a99: - a refused rebond drops the key, the next connection pairs with the code (Pete Schwamb, #80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…t-dev; no start delay after a loop kristinkand/G7SensorKit untethered-fallback 5e32eb7 (test branch only): - a refused rebond drops the key, the next connection pairs with the code (#80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) - #98: calibration replies decoded (accepted high no longer shows as refused), a queued calibration older than 5 minutes is dropped; #83: the paired sensor's warm-up time on Sensor Paired; #93: why no scan, logged - the G7 settings count down to the next reading (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…G7SensorKit#80) kristinkand/G7SensorKit untethered-pr-plus 3f066c6, on top of untethered-plus 4259a99: - a refused rebond drops the key, the next connection pairs with the code (Pete Schwamb, #80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
… as the app leaves the screen kristinkand/G7SensorKit untethered-pr 01e84bb, on top of EJM0's 369f306: - a refused rebond drops the key, the next connection pairs with the code (Pete Schwamb, LoopKit/G7SensorKit#80, #98) - leaving the screen with a hold running keeps the hold past the sensor's tail (#80) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…t-dev; no start delay after a loop kristinkand/G7SensorKit untethered-fallback 5e32eb7 (test branch only): - a refused rebond drops the key, the next connection pairs with the code (#80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) - #98: calibration replies decoded (accepted high no longer shows as refused), a queued calibration older than 5 minutes is dropped; #83: the paired sensor's warm-up time on Sensor Paired; #93: why no scan, logged - the G7 settings count down to the next reading (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
… as the app leaves the screen kristinkand/G7SensorKit untethered-pr 01e84bb, on top of EJM0's 369f306: - a refused rebond drops the key, the next connection pairs with the code (Pete Schwamb, LoopKit/G7SensorKit#80, #98) - leaving the screen with a hold running keeps the hold past the sensor's tail (#80) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…G7SensorKit#80) kristinkand/G7SensorKit untethered-pr-plus 3f066c6, on top of untethered-plus 4259a99: - a refused rebond drops the key, the next connection pairs with the code (Pete Schwamb, #80, #98) - no start delay, no connect into the sensor's tail as the app leaves the screen (#80) The untethered loop no longer hands the sensor's wait to bluetoothd when it's done (Elias's 160bd1a): the sensor keeps its own hold, as that function's note foresaw if link-ups dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…e app leaves the screen The reader's own copy of the G7 code gets the two changes from Pete Schwamb's LoopKit/G7SensorKit#80 that the kit forks get too: - A stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again, throws storedKeyNotBonded: the key is dropped while a code is at hand, and the next connection pairs with it (before: an unexpectedResponse that kept the key). - Leaving the screen with a hold running keeps the hold, which waits out the sensor's tail; if watchOS ends it first, its end lodges at once. No connect into the tail, no start delay: after a start-delay connect, 4 of 5 reading gaps on 23 October and 2 of 68 bursts on 24 October went missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…with the code From Pete Schwamb's LoopKit#80 (b97ad44, after LoopKit#98): a stored-key reconnect the sensor doesn't answer as authenticated, or whose certificate phase fails while bonding again (05 01 02 after the system bond was lost), throws storedKeyNotBonded. That counts as a stale key: it's dropped while a code is at hand, and the next connection pairs with it. Before, the first case was an unexpectedResponse that kept the key, and the second failed on every connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ (cherry picked from commit 30ec7a1)
…s with the code A stored-key reconnect the sensor doesn't answer as authenticated, or whose bonding again with the saved key fails (05 01 02 after the system bond was lost), now counts as a stale key: it's dropped while a pairing code is at hand, and the next connection pairs with the code. Before, the first case kept the key and the second failed on every connection. From Pete Schwamb's LoopKit/G7SensorKit#80 (b97ad44), as kristinkand's untethered-pr-plus has it (30ec7a1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135v4JApHewuwGjWKiSPDNY
Points LoopKit, G7SensorKit and Loop at their next-dev merges of LoopKit/LoopKit#626, LoopKit/G7SensorKit#80 and LoopKit/Loop#2605. Loop also picks up LoopKit/Loop#2606.
From Pete Schwamb's LoopKit#80 (b97ad44): until the next reading is due, the settings card counts down to it in minutes and seconds; once a reading is a minute late, it shows the minutes since the last, as before. Co-Authored-By: Pete Schwamb <pete@schwamb.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ (cherry picked from commit 946c850)
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch's G7 reader copies his watch acquisition from G7SensorKit on the watch (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Named in the untethered docs' credits (with Elias Maurer and Pete Schwamb), the watch G7 README, the acknowledgements, and next to the code that follows his. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgnnHLJbhxmQaEjQNYR5VZ
…the watch After kristinkand's 56cb282 on feature/untethered-pr-plus. Untethered mode builds on his Sport Mode for Loop (LoopKit/Loop#2545), the watch acquisition in this branch's G7SensorKit follows his (LoopKit/G7SensorKit#80), and Extended Untethered Mode's minimal screen follows his Sport Mode glance. Here in the acknowledgements and next to the glance; the docs and the copied G7 reader that the original also credits him in aren't on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135v4JApHewuwGjWKiSPDNY
Lets the watch read the G7 directly. It replaces #65, rebased on current
next-dev, from my fork so maintainers can push edits. Two upstream commits are kept as they were.While it is possible for the Loop Watch App to read G7 BG analogously to how the phone does it, doing so under the conditions in which SportMode is used increases the probability of triggering a known Dexcom problem that results in losing BG for both Loop and Dexcom D2W on the watch. Therefore, this build adopts directAuth only on the watch, with a fix to avoid the Dexcom problem.
What's in it:
exportConfiguration()/init(adopting:localState:), from LoopKit's new protocols), replacingreceivePairingCode. Where a watch-only#ifwas really about "this manager was set up by another device", it's now a run-time check instead.Changes on the phone even without the Loop PR:
Needs LoopKit/LoopKit#626; merge that first. Builds against stock
next-devwith it, and the kit's 176 tests pass.Update: direct to watch lands first
Jeremy agreed that direct to watch (DTW) lands ahead of Sport Mode; the Sport Mode PRs get updated after. Two commits on top:
Watch acquisition: fixes from a week on a watch (5db6301, with @EJM0 and @kristinkand)
Rebond a reconnect, explain an unfinished pairing, a Read on Apple Watch setting (651c2a4)
isConfigurationSharingEnabled, SportMode - necessary LoopKit changes to allow a watch controller LoopKit#626). It replaces the "Watch Direct Read" section. Its footer notes that the watch slot conflicts with Dexcom's own watch app.The Loop side is LoopKit/Loop#2605. It runs the G7 manager on the stock Loop watch app, behind the toggle.
Tested on an Apple Watch Series 9 with a real G7 and the phone's Bluetooth off: the watch read every window in the background. 196 tests pass, and the watchOS target builds.