Skip to content

Smartwatch manager for loop - #2646

Open
dabear wants to merge 13 commits into
LoopKit:next-devfrom
bjorninge-no-personal:smartwatch-manager-review
Open

dabear wants to merge 13 commits into
LoopKit:next-devfrom
bjorninge-no-personal:smartwatch-manager-review

Conversation

@dabear

@dabear dabear commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Loop: host SmartwatchManager plugins

Hosts the new watch plugin type from LoopKit LoopKit/LoopKit#684. Full description, screenshots and reviewer questions: LoopKit/LoopWorkspace#569, which also adds the first plugin (Pebble).

Part of three PRs: LoopKit LoopKit/LoopKit#684, Loop (this), and LoopWorkspace LoopKit/LoopWorkspace#569.

Commits

Up to date with Loop next-dev (f161c29e, with the G7 fix, #2652), merged in 917e0d27; LoopWorkspace next-dev now pins that Loop too. The watch host imports LoopCore, where #2645 moves LoopConstants.

Commit What
768106b3 bluetooth-peripheral background mode through Info.plist preprocessing (LOOP_BLUETOOTH_PERIPHERAL, on by default; cleared in LoopConfigOverride.xcconfig to leave it out).
48bc6d4e Host SmartwatchManager plugins: one watch under Settings → Devices → Add Watch, saved and restored like the pump and CGM; SmartwatchManagerHost snapshots; glucose alerts through a new AlertForwarder hook on AlertManager, following Loop's mute; the plugin's log lines and device alerts; unhandled URLs offered to the plugin.
5adc79cc Remote actions from watch plugins: each behind its own Watch Permissions toggle, off by default, checked with LoopKit's rules. The screen shows only when a plugin declares a remote action. Revert it together with 7c2999c5 (its strings), 021f6e40, 73e89c6b, be083470, e539d380, 28928727 and 16eabfc0 to leave remote actions out.
265952cc The host's new strings in Loop/Localizable.xcstrings (the Watch row and picker, preset names sent to a watch; the device alert's OK button uses the existing OK string), as Xcode extracts them, so they reach translation.
7c2999c5 The remote actions' strings in the string catalog: Watch Permissions, its toggles and their footers.
f532d3d2 Settings: make clear that Add Watch is for watches other than Apple Watch. The Devices row says so, the watch picker says that Loop's Apple Watch app is installed from the Watch app, and the picker's fallback icon is a generic watch.
021f6e40 Loop's bolus recommendation for carbs a watch is about to add (LoopKit's recommendBolusFor): recommendManualBolus with the carbs as the entry they would become, like the Apple Watch's, rounded as the bolus check rounds the recommendation it caps at. Only while Allow Boluses from Watch is on, for the current watch, with glucose from the last 15 minutes and a valid carb amount; logged.
73e89c6b Carbs from the watch carry their absorption (fast, medium, slow) as Loop's carb-entry absorption times (30 min, 3 h, 5 h), medium when the watch doesn't say; Allow Carbs and Allow Boluses from Watch say at most one every 5 minutes, LoopKit's new default.
be083470 A mute request of 0 from the watch ends Loop's mute of alerts (AlertMuter.unmuteAlerts()), after the same checks as a mute; logged.
e539d380 Settings: the Watch Permissions row is under a Third-Party Smartwatch header; its screen starts with What Non-Apple Watches Can Do and puts the toggles under Third-Party Smartwatch Permissions; the mute footer says the watch can also end the mute. The string catalog follows.
28928727 Watch Permissions: shorter texts. The intro and the mute footer say only what the user needs; the bolus footer lists every limit (the recommendation and maximum bolus, one every 5 minutes, at most 3 times the maximum bolus in 24 hours, glucose from the last 15 minutes) and no longer mentions forced requests, which no watch app offers. Only the three strings and their catalog keys change (none had translations).
917e0d27 Merge of Loop next-dev (f161c29e: #2645, #2649, #2659 and #2652). No conflicts.
16eabfc0 Watch Permissions: the bolus footer says the daily limit is 3 times the maximum bolus in 24 hours; "3 maximum boluses a day" read as three boluses.

Testing

  • LoopTests pass at the head after merging next-dev (917e0d27): 435. Before that, in both versions: 431 without remote actions, 432 with (three upstream string tests need a US region: -testRegion US). 28928727 changes only three strings: built and run on a phone, LoopTests not run again.
  • Used daily on an iPhone with a Pebble Time 2: setup, reconnects, unit changes, alerts.

Bjørn Inge added 6 commits November 5, 2026 09:13
Watch plugins that publish a Bluetooth service, such as a Pebble plugin, need
the bluetooth-peripheral background mode to keep the watch updated while Loop
is in the background, and a plugin bundle can't declare background modes
itself. Info.plist lists it under #if LOOP_BLUETOOTH_PERIPHERAL, defined in
Loop.xcconfig; clearing INFOPLIST_PREPROCESSOR_DEFINITIONS (for example in
LoopConfigOverride.xcconfig) leaves it out. The built Info.plist is otherwise
unchanged.
One watch plugin, under Settings → Devices → Add Watch, saved and restored like the pump and CGM.

- `SmartwatchManagerHost` sends snapshots when a reading is stored and when the loop's output changes, with only what the plugin declares and requests: glucose, history, prediction, loop status and therapy context.
- Glucose alerts reach the watch through a new `AlertForwarder` hook on `AlertManager`, following Loop's mute. Test alerts show on the watch and clear after 30 s; alarms that are off in the active profile, or all while the CGM raises its own, are cleared on the watch.
- The plugin's log lines go to the device log, and its device alerts become Loop alerts, at most time-sensitive.
- URLs Loop doesn't handle are offered to the plugin.
Bolus, carbs, alert mute and override presets requested from the watch, each behind its own Watch Permissions toggle, off by default, and checked with LoopKit's `SmartwatchRemoteActionPolicy`. The screen shows only when the plugin declares a remote action; the Pebble plugin declares none. Revert this commit to leave remote actions out.

- Bolus: through the remote bolus path (maximum bolus, notifications, upload), with the bolus context in the snapshot, recent glucose required, and the plugin's `remoteActionLimits`.
- Carbs: through the remote carbs path, dated when the user confirmed them on the watch.
- Mute: Loop's alert mute, capped at its own durations.
- Presets: Loop's override presets in the snapshot; start one or end the running one through the remote override path. Temp targets are refused (LoopKit's default).
- Deleting or replacing the watch turns every permission off.
The Watch row, the watch picker, the device alert's OK button and the preset names sent to a watch, added to Loop/Localizable.xcstrings as Xcode extracts them, so they reach translation. Custom Preset is in use again, with its translations.
Watch Permissions, its toggles and their footers, added to Loop/Localizable.xcstrings as Xcode extracts them. Revert this commit together with "Remote actions from watch plugins" to leave remote actions out.
…atch

Loop's Apple Watch app needs no setup in Settings → Devices, so people looking for it there would find only other watches. The Devices row now reads "Tap here to set up a watch other than Apple Watch", and the watch picker says that Apple Watch isn't added there and where Loop's Apple Watch app comes from. The picker's fallback icon for a watch plugin is a generic watch instead of the Apple Watch symbol. The string catalog follows.
@dabear
dabear force-pushed the smartwatch-manager-review branch from 6c4ee8f to f532d3d Compare November 5, 2026 08:42
@dabear

dabear commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Force-pushed 6c4ee8ff → f532d3d2 to put the branch on 29928c1c, the Loop that LoopWorkspace next-dev pins. The previous head merged Loop next-dev including #2645, after which a clean LoopWorkspace build fails in the Watch app (G7SensorKit's framework reference; fix in #2652).

Same six commits and features, with two small changes so the branch still merges cleanly into today's next-dev:

LoopTests pass: 431 without remote actions, 432 with.

Bjørn Inge Berg added 7 commits November 5, 2026 13:33
Answers a watch's recommendation request like the Apple Watch's: recommendManualBolus with the carbs as the entry the watch's carbs become (medium absorption, dated now), rounded as the bolus check rounds the recommendation it caps at, plus the maximum bolus. Only while Allow Boluses from Watch is on, for the current watch with remoteBolus, with glucose from the last 15 minutes and a valid carb amount; every request and answer goes to the device log.
…ests

The watch's fast, medium or slow carbs become carb entries (and recommendations) with Loop's carb-entry absorption times (30 min, 3 h, 5 h), medium when it doesn't say. Allow Carbs and Allow Boluses from Watch say at most one every 5 minutes, LoopKit's new interval.
…ions

The watch permissions are under a Third-Party Smartwatch header in Settings.
Their screen starts with what a watch other than Apple Watch can do, and the
toggles are under Third-Party Smartwatch Permissions. The mute footer says
that the watch can also end the mute. The string catalog follows.
The intro and the mute footer say only what the user needs. The bolus
footer lists every limit (the recommendation and maximum bolus, one every
5 minutes, 3 maximum boluses a day, glucose from the last 15 minutes) and
no longer mentions forced requests, which no watch app offers.
… in 24 hours

'3 maximum boluses a day' read as three boluses. The limit is a total:
watch boluses in any 24 hours add up to at most 3 times the maximum bolus.
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.

1 participant