# BitChat BLE Stack Battery Optimization Strategies: 7 Power-Saving Mechanisms Explained

> Discover BitChat BLE stack battery optimization strategies. Learn 7 power-saving mechanisms like adaptive scanning and rate limiting to extend device life.

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

---

**The BitChat BLE stack minimizes radio time through adaptive duty-cycled scanning, flood throttling, announce rate limiting, and RSSI-gated connection scheduling to preserve device battery life.**

BitChat's Bluetooth Low Energy transport layer is engineered for longevity, combining multiple battery optimization strategies that keep the radio active only when necessary. These mechanisms work together to deliver a messaging experience that doesn't drain the device. Below are the seven core battery-saving techniques implemented in the permissionlesstech/bitchat repository.

## Adaptive Duty-Cycled Scanning and Advertising

The cornerstone of BitChat's power efficiency is **adaptive duty-cycled scanning**, controlled by `BLEScanDutyPolicy` and executed by `BLERadioController`.

In [`bitchat/Services/BLE/BLEScanDutyPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEScanDutyPolicy.swift), the policy engine decides between continuous scanning and duty-cycled operation based on three inputs:

- `appIsActive` — whether the UI is foreground
- `connectedCount` — number of active BLE links
- `forceContinuousScan` — override flag for debugging or emergencies

```swift
// BLEScanDutyPolicy.swift#L5-L24
enum BLEScanDuty {
    case continuous
    case dutyCycle(onDuration: TimeInterval, offDuration: TimeInterval)
}

struct BLEScanDutyPolicy {
    static func makePlan(
        appIsActive: Bool,
        connectedCount: Int,
        forceContinuousScan: Bool
    ) -> BLEScanDuty {
        if forceContinuousScan || (appIsActive && connectedCount > 0) {
            return .continuous
        }
        // Default: 100ms on, 900ms off = 10% duty cycle
        return .dutyCycle(onDuration: 0.1, offDuration: 0.9)
    }
}

```

The `BLERadioController` then implements this plan using a timer that toggles scanning:

```swift
// BLERadioController.swift#L47-L55 and #L106-L115
class BLERadioController {
    private var dutyTimer: Timer?
    var dutyEnabled: Bool = true
    
    func updateScanDutyPlan(_ plan: BLEScanDuty) {
        dutyTimer?.invalidate()
        switch plan {
        case .continuous:
            startScanning()
        case .dutyCycle(let on, let off):
            scheduleDutyCycle(onDuration: on, offDuration: off)
        }
    }
    
    private func scheduleDutyCycle(onDuration: TimeInterval, offDuration: TimeInterval) {
        // Turn on for burst, then schedule next window
        startScanning()
        dutyTimer = Timer.scheduledTimer(withTimeInterval: onDuration, repeats: false) { _ in
            self.stopScanning()
            Timer.scheduledTimer(withTimeInterval: offDuration, repeats: false) { _ in
                self.scheduleDutyCycle(onDuration: onDuration, offDuration: offDuration)
            }
        }
    }
}

```

This 10% default duty cycle reduces radio active time by 90% during background operation.

## Flood and Battery Throttling

Large-scale broadcast operations — "floods" — are gated by battery-aware limits to prevent sustained high-power transmission. In [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift), the implementation throttles long fragment trains:

```swift
// BLEService.swift#L200 — region where battery limits apply
// "Flood throttling: defer bulk sends when battery is low"

```

A critical guard appears at line 5531, where scanning pauses during fragment transmission to avoid simultaneous TX/RX power drain.

## Announce Throttling for Presence Broadcasts

Periodic presence announcements — the heartbeat of peer discovery — are strictly rate-limited by `BLEAnnounceThrottle`:

```swift
// BLEAnnounceThrottle.swift#L43-L52
struct BLEAnnounceThrottle {
    let normalMinimumInterval: TimeInterval = 600  // 10 minutes
    let forcedMinimumInterval: TimeInterval = 2    // 2 seconds urgent
    
    private var lastSendTimes: [UUID: Date] = [:]
    
    func shouldSend(force: Bool, now: Date) -> Bool {
        let minimum = force ? forcedMinimumInterval : normalMinimumInterval
        guard let last = lastSendTimes[currentPeer] else {
            return true  // Never sent before
        }
        return now.timeIntervalSince(last) >= minimum
    }
}

```

Normal announcements are capped at one per 10 minutes per envelope. Urgent messages can force a 2-second minimum interval when necessary.

## RSSI-Gated Connection Scheduling

BitChat prioritizes physically nearby peers to reduce transmission power and retry overhead. As documented in `WHITEPAPER.md#L71`:

> "Connection scheduling is RSSI-gated with duty-cycled scanning to bound battery drain."

Stronger signals permit lower TX power, and nearby peers complete handshakes faster with fewer retransmissions.

## Scan Pause During Long Fragment Trains

File transfers fragment large payloads across multiple BLE packets. During these trains, the stack suspends scanning entirely:

```swift
func sendLargeFile(_ file: Data) {
    // BLEService.swift#L5531 — scan-pause guard
    pauseScanning("fragment_train")
    // ... fragment transmission ...
    resumeScanning()
}

```

This eliminates the power penalty of simultaneous reception while transmitting.

## Background Connect: Wake-on-Proximity

Even when suspended, BitChat can restore connectivity without continuous scanning. The `BLERadioController` header documents this mode:

```swift
// BLERadioController.swift#L21-L23
/// Wake-on-proximity: advertise a connectable beacon that wakes
/// the app when a peer with matching service UUID comes near.
/// No scanning required in deep sleep.

```

A nearby peer's advertisement triggers the system wake, allowing opportunistic reconnect without background radio operation.

## Adaptive Power: Documented Strategy

The `README.md#L48` entry ties these mechanisms together under the **Adaptive Power** feature:

> **Adaptive Power**: Battery-optimized duty cycling — automatically reduces scanning aggressiveness based on app state, connected peers, and pending message urgency.

## Practical Implementation Example

Here's how to configure the battery optimization stack for a typical messaging session:

```swift
import BitChat

class ChatViewController: UIViewController {
    let bleService = BLEService.shared
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // Foreground: allow continuous scanning if we have peers
        let dutyPlan = BLEScanDutyPolicy.makePlan(
            appIsActive: true,
            connectedCount: bleService.currentLinkCount,
            forceContinuousScan: false
        )
        bleService.radioController.updateScanDutyPlan(dutyPlan)
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        // Background: force duty cycling to save battery
        let backgroundPlan = BLEScanDutyPolicy.makePlan(
            appIsActive: false,
            connectedCount: bleService.currentLinkCount,
            forceContinuousScan: false
        )
        bleService.radioController.updateScanDutyPlan(backgroundPlan)
    }
    
    func sendUrgentMessage(_ content: Data) {
        // Override announce throttle for priority delivery
        if bleService.announceThrottle.shouldSend(force: true, now: Date()) {
            bleService.broadcast(content, forceAnnounce: true)
        }
    }
}

```

## Key Source Files for Battery Optimization

| File | Battery-Saving Role |
|------|---------------------|
| [`bitchat/Services/BLE/BLEScanDutyPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEScanDutyPolicy.swift) | Determines continuous vs. duty-cycled scanning |
| [`bitchat/Services/BLE/BLERadioController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERadioController.swift) | Executes duty-cycle timers, wake-on-proximity |
| [`bitchat/Services/BLE/BLEAnnounceThrottle.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEAnnounceThrottle.swift) | Rate-limits presence announcements |
| [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) | Flood throttling, scan-pause during transfers |
| [`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) | RSSI-gated scheduling specification |
| [`README.md`](https://github.com/permissionlesstech/bitchat/blob/main/README.md) | Public feature documentation |

## Summary

- **Adaptive duty cycling** reduces scanning to 10% of time in background through `BLEScanDutyPolicy` and `BLERadioController`
- **Flood throttling** prevents sustained high-power broadcasts during bulk transfers
- **Announce throttling** limits presence broadcasts to 10-minute intervals normally, 2 seconds when urgent
- **RSSI-gated scheduling** prefers nearby peers for lower TX power and faster handshakes
- **Scan pause during fragments** avoids TX/RX overlap during file transfers
- **Wake-on-proximity** enables background reconnect without continuous scanning
- **Adaptive Power** documentation unifies these strategies as a public feature commitment

## Frequently Asked Questions

### How much battery does BitChat's duty cycling actually save?

The default 10% duty cycle (100ms on, 900ms off) reduces BLE radio active time by 90% compared to continuous scanning. According to the `BLERadioController` implementation, this translates to substantial savings since BLE receive powertypically exceeds 10mA while the radio is active.

### Can I force continuous scanning for debugging or emergencies?

Yes. Set `forceContinuousScan: true` when calling `BLEScanDutyPolicy.makePlan()`, or toggle the `dutyEnabled` flag in `BLERadioController` directly. This bypasses all duty cycling regardless of app state.

### What triggers the 2-second urgent announce interval?

Pass `force: true` to `BLEAnnounceThrottle.shouldSend()`. This is intended for critical messages requiring rapid peer discovery, but frequent use will impact battery life proportionally.

### Does RSSI-gated scheduling require additional hardware support?

No. The stack uses standard received signal strength indicators available in all BLE implementations. The white paper notes this is implemented in software scheduling logic within `BLERadioController` rather than hardware power control.