- In Xcode, go to File > Add Package Dependencies...
- Enter this repository URL.
- Select the version you want to use.
- Add
BLEorchestrationto your app target.
dependencies: [
.package(url: "https://github.com/rajeshm20/BLEOrchestration.git")
]Then add the product to your target dependencies:
.package(
url: "https://github.com/yourname/bleorchestration.git",
branch: "main"
)- Date:** 2026-05-08
- Target:** iOS 15+
- Primary goals:** Maintain stable concurrent connections (10+ wearables), reconnection resilience, high packet throughput under mixed device capabilities, strong isolation and determinism via actors, CoreBluetooth delegate isolation, and background restoration via
CBCentralManagerOptionRestoreIdentifierKey.
- Concurrent connections to 10+ BLE peripherals (wearables).
- Two acquisition modes:
- Known device reconnect by persisted peripheral UUID (use
retrievePeripherals(withIdentifiers:)). - Rediscovery via scanning (for devices not currently retrievable).
- Known device reconnect by persisted peripheral UUID (use
- Connection policies:
- Pinned set (always keep connected when possible).
- Dynamic pool (connect up to max-concurrency based on priority scoring).
- Both policies can be active simultaneously.
- Robust reconnection:
- Retry budgets and jittered exponential backoff.
- Gating to avoid fleet-wide thrash and radio starvation.
- Packet throughput optimization:
- Per-device scheduling, credit/backpressure for
writeWithoutResponse. - QoS queues, bounded buffering, fairness across devices.
- Per-device scheduling, credit/backpressure for
- Background behavior:
bluetooth-centralbackground mode expected.- CoreBluetooth state restoration using
CBCentralManagerOptionRestoreIdentifierKey. - Restoration rebinds
CBPeripheralinstances to per-device actors.
- UI / pairing screens / onboarding UX.
- Firmware update flows (DFU) unless later layered on top.
- Cross-process sharing of BLE resources.
- Security protocol design (assumes BLE pairing/bonding managed by iOS + device).
-
BLEOrchestratorActor(public façade + policy brain)- Owns lifecycle (
start/stop), pinned/dynamic policy decisions, and max concurrency. - Routes app intents to device sessions.
- Emits device lifecycle and decoded protocol events.
- Owns lifecycle (
-
BLECentralActor(CoreBluetooth owner)- The only owner allowed to call
CBCentralManagerAPIs: scan/connect/cancel/retrieve. - Accepts delegate events from the proxy, translates them to device/central events.
- The only owner allowed to call
-
DeviceConnectionActor(per peripheral UUID)- Single device authority: state machine, discovery, subscriptions, transport, retries.
- Owns per-device timers (timeouts, keepalive, backoff deadlines).
-
CoreBluetoothDelegateProxy(delegate isolation boundary)- A single
NSObjectimplementingCBCentralManagerDelegateandCBPeripheralDelegate. - Performs no business logic and minimal work inside callbacks.
- Forwards typed events asynchronously into
BLECentralActor/DeviceConnectionActor.
- A single
CentralControlling- Scan/retrieve/connect/cancel abstractions for CoreBluetooth.
PeripheralControlling- Wrapper surface for
CBPeripheralinteractions that need to be mocked in tests.
- Wrapper surface for
BackoffScheduling- Strategy for next retry time and retry budget classification.
PacketTransporting- Transport for framed payloads and backpressure signals.
StatePersisting- Persists pinned UUIDs and minimal metadata required for restoration rehydration.
Each DeviceConnectionActor owns a DeviceState value and transitions only through a reducer.
idle— known device, not attempting connection.resolving— attempting to obtain aCBPeripheralvia retrieve and/or scan match.connecting—CBCentralManager.connectin-flight.discovering— service/characteristic discovery + notification subscription.ready— transport open and validated; protocol handlers active.degraded(reason)— partial loss (e.g. notify disabled, timeouts); may self-heal or reconnect.backingOff(attempt, nextDeadline)— reconnect suppressed until deadline (jittered backoff).suspended(reason)— Bluetooth off/unauthorized/app stop; no connection attempts.
Events are normalized to a small set to keep transitions deterministic.
- Central-level:
centralStateChanged(CBManagerState)didConnectdidFailToConnect(Error?)didDisconnect(Error?)willRestoreState([CBPeripheral], …)
- Peripheral-level:
didDiscoverServices(Error?)didDiscoverCharacteristics(service, Error?)didUpdateValue(characteristic, Error?)didWriteValue(characteristic, Error?)isReadyToSendWriteWithoutResponse
- Internal:
timeout(kind)policyUpdated(pinned/priority/maxConcurrency)transportBackpressureChanged
- Reducer signature:
reduce(state:event:) -> (newState, [Effect])
- Effects are intent-like (e.g., connect, start discovery, subscribe notify, schedule retry).
- The actor executes effects serially, ensuring CoreBluetooth API ordering and eliminating reentrancy hazards.
Per device, a ReconnectController:
- Classifies failures:
transient(radio/link loss)bluetoothOff/unauthorizedprotocol(e.g., missing characteristic / handshake mismatch)unknown
- Applies retry budgets:
- Faster initial retry for transient disconnects.
- Exponential backoff with jitter for repeated failures.
- Cooldown/circuit-break behavior on repeated protocol failures.
Suppress reconnect attempts when any is true:
- Central not
.poweredOn - Engine is stopped/suspended by app
- Device not currently eligible (un-pinned and outside dynamic pool)
- Restoration in progress for that device (avoid duplicate connect)
OutgoingScheduler responsibilities:
- Bounded per-QoS queues (prevents unbounded growth).
- Flow-control for
writeWithoutResponseusing a credit model:- Credits replenished via
peripheralIsReady(toSendWriteWithoutResponse:).
- Credits replenished via
- Runtime selection of write type:
- Prefer
withoutResponsewhen supported and credits available. - Fall back to
withResponsewhen required.
- Prefer
- Fairness:
- Prevent a single device from starving others by applying per-device pacing and global policy knobs.
- Framing + reassembly (MTU-aware fragmentation).
- Validation (length/CRC if the protocol uses it).
- Decode + dispatch to protocol handlers.
- Per-device class config:
- max in-flight writes
- max queue depth per QoS class
- heartbeat interval
- optional coalescing window (feature flagged per device type)
- Delegate proxy does minimal work inside callbacks:
- capture references/identifiers
- forward events to actors via nonblocking handoff
- Actors own all ordering and side effects.
- No CoreBluetooth API calls from outside
BLECentralActorandDeviceConnectionActor(enforced via access control and protocol boundaries).
- Initialize
CBCentralManagerwith:CBCentralManagerOptionRestoreIdentifierKey = <stable identifier>- Optional: show power alert based on product requirements.
centralManager(_:willRestoreState:)receives restoredCBPeripherals.RestorationCoordinator:- matches restored peripherals to persisted known UUIDs
- rebinds
CBPeripheralinstances to the correspondingDeviceConnectionActor - triggers “resume” effect (validate GATT, resubscribe notifications if needed)
Required metrics per device:
- connection state + timestamps
- reconnect attempt count + backoff deadline
- queue depth per QoS class
- write rate (per type), error counts, last error classification
- ready time (connect → ready latency)
Logs are structured and correlated with device UUID + session id.
- Maintains stable operation with 10+ concurrent peripherals under:
- intermittent disconnects
- mixed
withResponse/withoutResponsecapabilities - background mode enabled
- No deadlocks/races in delegate → actor event handling.
- On app relaunch via state restoration, previously pinned devices are rehydrated and return to
ready(when available) without user intervention.