I have a watchOS app that records workouts on a piece of Bluetooth LE fitness equipment.
It runs an HKWorkoutSession and, for the length of that session, stays connected to the
machine and subscribed to a characteristic that reports step data once a second. Each
notification is recorded into the workout's HKLiveWorkoutBuilder. The connection needs to
last as long as the workout — typically 20–60 minutes.
The watch app declares both:
WKBackgroundModes = workout-processing
UIBackgroundModes = bluetooth-central
This works indefinitely while the app is in the foreground. When the app leaves the
foreground — the user lowers their wrist or goes to the watch face — the peripheral
disconnects after roughly three minutes, and the workout loses the rest of its data.
What we have been able to establish from logs at the moment of the disconnect:
The app is not suspended. Notifications arrive at a steady 1 Hz with no gaps right up to
the disconnect (271 and 272 consecutive notifications in two separate cases), so the
workout session is keeping the app running as expected.
centralManager.state is .poweredOn.
centralManager(_:didDisconnectPeripheral:error:) is called with error == nil,
and the peripheral's state is .disconnected.
Nothing in our code calls cancelPeripheralConnection at that point; our own teardown
runs afterwards, in response to the disconnect.
The user is still using the machine, and the machine is still notifying.
Reproducible: two captures roughly three minutes and twenty seconds after the app
backgrounded.
I understand from Get timely alerts from Bluetooth devices on watchOS (WWDC22) that
background Bluetooth on watchOS is intended for timely alerts, with a limited number of
background runtime opportunities, and that periodic data collection should use Background
App Refresh instead. Our data arrives once a second for the whole workout, so I assume we
are well outside what that mode is budgeted for, and that the disconnect is the system
reclaiming the link.
My questions:
Does a running HKWorkoutSession grant any exemption from the background Bluetooth
limits? workout-processing clearly keeps the app executing — we receive
notifications for minutes after backgrounding — but the connection is still torn down.
Is Bluetooth's background budget independent of the workout runtime, and is that the
intended behaviour?
Is there a supported way to hold a BLE connection for the duration of a workout
session? Reading a fitness machine's telemetry for an entire workout seems like a
mainstream use of both frameworks together, but I cannot find a configuration that
survives the app leaving the foreground.
Would a longer connection interval change this? The session recommends at least
150 ms. If the disconnect is driven by connection-event volume rather than notification
count, a slower interval or a lower update rate may be the answer — and if so, is there
a documented threshold to aim for?
error == nil on disconnect. The documentation says a nil error means the
disconnect was requested locally via cancelPeripheralConnection, which we do not call
here. Should a system-initiated reclaim of the link be reported this way, or is there a
more specific error we should be looking for? We now log the error on
peripheral(_:didUpdateValueFor:error:) to catch
LeGattNearBackgroundNotificationLimit and LeGattExceededBackgroundNotificationLimit,
but have not yet observed either before a disconnect.
Is there any entitlement path for this? We are aware of
com.apple.developer.bluetooth-central-background and that it is limited to continuous
glucose monitoring. Is there an equivalent route for fitness equipment, or is
foreground-only the expected design for this class of app?
If continuous background Bluetooth is simply not available here, I would like to know
that clearly, so I can design around the disconnect — reconnecting mid-workout rather
than treating a dropped link as the end of the session.