# How the BLE Connection Scheduler Optimizes Battery Life Through Duty Cycling

> Discover how the BLE connection scheduler optimizes battery life. Learn about its duty-cycling strategy, reducing power consumption by minimizing active radio states.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-09

---

**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](https://github.com/permissionlesstech/bitchat/blob/main/docs/BLE-ARCHITECTURE-V3.md) design document and implemented across the [`BLEEngineScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEEngineScheduler.swift) and [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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:

```swift
import Bitchat

let bleService = BLEService(
    engineScheduler: BLEEngineDispatchScheduler(),
    // other dependencies omitted for brevity
)

```

This initialization pattern is defined in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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:

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEQueueContractTests.swift).

### Testing with Manual Time Advancement

Use the manual scheduler to test duty-cycling behavior without real-time delays:

```swift
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`](https://github.com/permissionlesstech/bitchat/blob/main/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:

```swift
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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEEngineScheduler.swift) — Defines the `BLEEngineScheduling` protocol and `BLEEngineDispatchScheduler` implementation.
- [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) — Core service that integrates the scheduler with CoreBluetooth.
- [`bitchat/Services/BLE/BLELinkLayer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLELinkLayer.swift) — Wraps peripheral and central roles, funneling callbacks through the scheduler.
- [`bitchatTests/Mocks/BLEEngineManualScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Mocks/BLEEngineManualScheduler.swift) — Test scheduler enabling deterministic time control.
- [`docs/BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/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.