How the BLE Connection Scheduler Optimizes Battery Life Through Duty Cycling
The BLE connection scheduler in bitchat implements a deterministic duty-cycling strategy that aligns all radio operations to scheduled engine ticks, exponentially backing off during idle periods to minimize time spent in high-power advertising, scanning, and connection states.
The bitchat repository implements a power-efficient Bluetooth Low Energy mesh protocol that relies on a centralized connection scheduling system. By routing all BLE radio operations through a clock-driven scheduler, the framework ensures that devices only activate their radios during brief, pre-calculated windows, dramatically reducing power consumption compared to always-on BLE implementations. This architecture is defined in the BLE-ARCHITECTURE-V3.md design document and implemented across the BLEEngineScheduler.swift and BLEService.swift source files.
Core Components of the BLE Connection Scheduler
The duty-cycling implementation rests on three primary abstractions that separate timing logic from radio operations.
The BLEEngineScheduling Protocol
The BLEEngineScheduling protocol defines the contract that all schedulers must implement to supply the next moment the engine should perform work. By centralizing timing logic behind this interface, the BLE stack can remain completely idle until the schedule explicitly triggers the next wake-up window. This abstraction allows the system to swap between production and test schedulers without altering the duty-cycling logic itself.
BLEEngineDispatchScheduler
The BLEEngineDispatchScheduler is the production implementation that ties the engine to the system's monotonic clock. It posts work to a DispatchQueue only when the next computed deadline arrives, ensuring the radio is powered on exactly when needed and deactivated immediately after. According to the source code in BLEEngineScheduler.swift, this scheduler computes deadlines based on pending outbound packets, scan windows, and connection intervals.
BLEEngineManualScheduler
For unit testing deterministic behavior, the BLEEngineManualScheduler allows tests to advance time manually without waiting for real clock ticks. This test-only scheduler demonstrates that the same duty-cycling logic and back-off strategies function correctly regardless of the clock source, enabling validation of power-saving algorithms in fast-forwarded time.
Duty Cycling Mechanisms
The scheduler optimizes battery life through four distinct mechanisms that limit radio activation time.
Engine Tick Generation and Radio Wake-Up
The scheduler generates discrete engine ticks that represent the only moments when the BLE radio may activate. When a tick fires, BLEEngineDispatchScheduler briefly wakes the CoreBluetooth radio to process pending work—such as advertising, scanning, or transmitting fragments—then immediately returns control to the scheduler for the next sleep calculation. This approach ensures the radio remains in a low-power state for the majority of each second.
Exponential Back-Off and Throttling
When the outbound queue is empty, the scheduler implements an exponential back-off strategy to extend sleep durations. The idle interval increases from 200 ms to 500 ms to 1 second until new work arrives, dramatically cutting idle radio time. This back-off logic is enforced by the BLEQueueContract, which also specifies a minimum inter-packet gap to prevent bursty transmissions that would otherwise keep the radio active for extended periods.
BLEQueueContract Enforcement
The BLEQueueContract class enforces timing constraints that prevent premature wake-ups. Developers can adjust the duty cycle by modifying the minInterPacketGap property, which defines the mandatory quiet period between transmissions. Increasing this gap from the default 25 ms to 100 ms or higher reduces the frequency of radio activations, trading latency for battery life.
Fragment Reassembly Timeouts
To prevent the radio from staying active indefinitely while waiting for missing packet fragments, the scheduler tracks a 30-second fragment reassembly window. If incomplete fragments remain unassembled after this timeout, the buffer is discarded and the radio returns to sleep, eliminating power drain from stalled connections.
Code Implementation Examples
The following snippets demonstrate how to configure and utilize the duty-cycling scheduler in production and test environments.
Initializing the Production Scheduler
To instantiate a BLE service with the default dispatch scheduler that synchronizes with the system clock:
import Bitchat
let bleService = BLEService(
engineScheduler: BLEEngineDispatchScheduler(),
// other dependencies omitted for brevity
)
This initialization pattern is defined in BLEService.swift, which wires the scheduler into the CoreBluetooth callback layer.
Configuring the Queue Contract
Adjust the minimum inter-packet gap to increase the quiet period between transmissions and reduce duty cycle frequency:
// Increase the minimum gap from the default 25 ms to 100 ms
BLEQueueContract.shared.minInterPacketGap = .milliseconds(100)
This configuration directly impacts the BLEEngineDispatchScheduler timing calculations, as verified in BLEQueueContractTests.swift.
Testing with Manual Time Advancement
Use the manual scheduler to test duty-cycling behavior without real-time delays:
let manualScheduler = BLEEngineManualScheduler()
let bleService = BLEService(engineScheduler: manualScheduler)
manualScheduler.advanceTime(by: .seconds(2)) // forces the engine to run all pending work
This approach is implemented in BLEEngineManualScheduler.swift within the test target.
Peer Selection for Reduced Radio Load
Limit the number of simultaneous peer connections per duty cycle to minimize radio contention:
let selector = BLEFanoutSelector()
let peersToContact = selector.selectPeers(maxFanout: 3, for: currentSlot)
The BLEFanoutSelector and BLEIngressLinkRegistry work together to restrict active links, allowing the device to spend more time in low-power states between scheduled slots.
Key Source Files
The duty-cycling implementation spans the following files in the permissionlesstech/bitchat repository:
bitchat/Services/BLE/BLEEngineScheduler.swift— Defines theBLEEngineSchedulingprotocol andBLEEngineDispatchSchedulerimplementation.bitchat/Services/BLE/BLEService.swift— Core service that integrates the scheduler with CoreBluetooth.bitchat/Services/BLE/BLELinkLayer.swift— Wraps peripheral and central roles, funneling callbacks through the scheduler.bitchatTests/Mocks/BLEEngineManualScheduler.swift— Test scheduler enabling deterministic time control.docs/BLE-ARCHITECTURE-V3.md— Comprehensive design document outlining the duty-cycling strategy.
Summary
- The BLE connection scheduler centralizes all radio activity behind a deterministic clock to eliminate unnecessary wake-ups.
- BLEEngineDispatchScheduler uses the system monotonic clock to activate the radio only during pre-calculated engine ticks.
- Exponential back-off (200 ms → 500 ms → 1 s) extends sleep periods when the transmission queue is idle.
- BLEQueueContract enforces minimum inter-packet gaps and prevents bursty transmissions that drain battery.
- A 30-second fragment reassembly timeout prevents stalled connections from keeping the radio active indefinitely.
- The architecture supports deterministic testing via BLEEngineManualScheduler, ensuring duty-cycling logic works correctly across different clock sources.
Frequently Asked Questions
What is the difference between BLEEngineDispatchScheduler and BLEEngineManualScheduler?
BLEEngineDispatchScheduler is the production scheduler that ties BLE operations to the real-time system clock, posting work to dispatch queues only when deadlines arrive. BLEEngineManualScheduler is a test-only mock that allows unit tests to manually advance time, enabling deterministic validation of duty-cycling logic without waiting for actual clock ticks.
How does the BLEQueueContract reduce power consumption?
The BLEQueueContract enforces a minimum inter-packet gap that prevents the radio from rapid successive transmissions. By mandating quiet periods between packets and implementing exponential back-off during idle states, it ensures the radio remains deactivated for the maximum possible time between scheduled engine ticks.
What happens when no BLE work is available?
When the outbound queue is empty, the scheduler enters an exponential back-off loop, increasing sleep intervals from 200 ms to 500 ms to 1 second. The radio remains completely off during these periods, and the scheduler only re-evaluates when new data arrives or the maximum back-off interval elapses.
How does the scheduler prevent battery drain from incomplete data transfers?
The scheduler implements a 30-second fragment reassembly timeout. If incomplete packet fragments remain unassembled after this window expires, the buffer is discarded and the radio returns to sleep, preventing the device from wasting power waiting for missing data that may never arrive.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →