
# Bleu 2
**Modern Bluetooth Low Energy Framework with Swift Distributed Actors**
[](https://swift.org)
[](https://developer.apple.com/swift/)
[](https://swift.org/package-manager/)
[](https://github.com/1amageek/Bleu/actions/workflows/test.yml)
[](LICENSE)
[](https://github.com/sponsors/1amageek)
---
## Overview
Bleu 2 is a revolutionary Bluetooth Low Energy framework that leverages **Swift's Distributed Actor System** and **`@Resolvable` protocols** to create seamless, type-safe communication between BLE devices.
**Define a protocol, implement it, and call methods over BLEβit's that simple.**
No complex BLE APIs. No manual serialization. No boilerplate code. Just define your distributed actor protocol with `@Resolvable`, and Bleu handles everything else automatically.
## β¨ Key Features
### π― **Protocol-Oriented BLE with @Resolvable**
- **Define a protocol** with distributed methods
- **Implement on peripheral** as a distributed actor
- **Resolve on central** using auto-generated stubs
- **Call methods over BLE** as if they were local
- Zero boilerplate, maximum simplicity
### π **Distributed Actor Architecture**
- Transparent RPC over BLE using Swift's native distributed actors
- Type-safe remote method invocation
- Automatic serialization and error handling
- Actor isolation for thread safety
### π **High Performance**
- Binary packet fragmentation with 24-byte headers
- Efficient data transport with checksums
- Adaptive MTU negotiation
- Automatic packet reassembly
### π± **Modern Swift Integration**
- Full async/await support
- AsyncStream for real-time data
- Swift 6 concurrency features
- Sendable protocol compliance
### π§ **Developer Friendly**
- Simple, intuitive API
- Comprehensive logging system
- Automatic resource management
- Clean error handling
## π Quick Start
### Installation
Add Bleu to your project using Swift Package Manager:
```swift
dependencies: [
.package(url: "https://github.com/1amageek/Bleu.git", from: "2.2.0")
]
```
### The Simplest Way: Using @Resolvable
```swift
import Bleu
import Distributed
// 1. Define your BLE device API as a protocol
@Resolvable
protocol TemperatureSensor: PeripheralActor {
distributed func getTemperature() async throws -> Double
distributed func setUpdateInterval(_ seconds: Int) async throws
}
// 2. Peripheral: Implement the protocol
distributed actor MyThermometer: TemperatureSensor {
typealias ActorSystem = BLEActorSystem
distributed func getTemperature() async throws -> Double {
return 25.5 // Read from actual sensor
}
distributed func setUpdateInterval(_ seconds: Int) async throws {
// Configure update rate
}
}
// 3. Central: Resolve and call methods over BLE
let actorSystem = BLEActorSystem(
peripheralManager: CoreBluetoothPeripheralManager(),
centralManager: CoreBluetoothCentralManager()
)
// Discover the sensor
let sensors = try await actorSystem.discover(MyThermometer.self, timeout: 10.0)
// Resolve using the protocol (works even if you only know the ID!)
let sensor = try $TemperatureSensor.resolve(id: sensors[0].id, using: actorSystem)
// Call methods as if the sensor were local
let temp = try await sensor.getTemperature() // π That's it!
```
### Traditional Approach: Concrete Actor Types
You can also define distributed actors directly without protocols:
```swift
import Bleu
import Distributed
// Define a distributed actor that runs on a BLE peripheral
distributed actor TemperatureSensor: PeripheralActor {
typealias ActorSystem = BLEActorSystem
distributed func getTemperature() async throws -> Double {
// Read from actual sensor hardware
return 25.5
}
distributed func setUpdateInterval(_ seconds: Int) async throws {
// Configure sensor update rate
}
}
```
#### Peripheral Side
```swift
// Create BLE actor system with CoreBluetooth managers
let peripheralManager = CoreBluetoothPeripheralManager()
let centralManager = CoreBluetoothCentralManager()
let actorSystem = BLEActorSystem(
peripheralManager: peripheralManager,
centralManager: centralManager
)
// Create and advertise the sensor
let sensor = TemperatureSensor(actorSystem: actorSystem)
// Start advertising the sensor service
try await actorSystem.startAdvertising(sensor)
```
#### Central Side
```swift
// Create BLE actor system with CoreBluetooth managers
let peripheralManager = CoreBluetoothPeripheralManager()
let centralManager = CoreBluetoothCentralManager()
let actorSystem = BLEActorSystem(
peripheralManager: peripheralManager,
centralManager: centralManager
)
// Discover and connect to sensors
let sensors = try await actorSystem.discover(TemperatureSensor.self, timeout: 10.0)
if let remoteSensor = sensors.first {
// Call methods on the remote sensor as if it were local!
let temperature = try await remoteSensor.getTemperature()
print("Current temperature: \(temperature)Β°C")
// Configure the remote sensor
try await remoteSensor.setUpdateInterval(5)
}
```
## π― Advanced Features
### Protocol-Based Actor Resolution with @Resolvable
Bleu 2 leverages Swift's `@Resolvable` macro ([SE-0428](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0428-resolve-distributed-actor-protocols.md)) to enable protocol-oriented distributed actor design. You can define your own protocols with distributed methods and use the compiler-generated stubs to resolve remote actors without knowing their concrete implementations.
#### Why Use @Resolvable?
- **Protocol-First API Design**: Define your BLE device APIs as protocols
- **Implementation Flexibility**: Peripheral implementations remain private
- **Type-Safe Resolution**: Resolve actors by ID using protocol types
- **Module Separation**: Share protocol definitions across app modules
- **Easy Testing**: Mock protocol implementations for unit tests
#### Define Your Own Protocol
**Important**: Add `@Resolvable` to **your custom protocols**, not to the base `PeripheralActor` protocol. The `PeripheralActor` protocol is a marker protocol without distributed methods.
```swift
// Step 1: Define a custom protocol with @Resolvable and distributed methods
@Resolvable
protocol TemperatureSensor: PeripheralActor {
distributed func getTemperature() async throws -> Double
distributed func setTemperatureUnit(_ unit: String) async throws
}
// Step 2: Peripheral side - Implement the protocol
distributed actor IndoorSensor: TemperatureSensor {
typealias ActorSystem = BLEActorSystem
private var unit = "celsius"
distributed func getTemperature() async throws -> Double {
return unit == "celsius" ? 25.5 : 77.9
}
distributed func setTemperatureUnit(_ unit: String) async throws {
self.unit = unit
}
}
// Step 3: Central side - Work with the protocol, not the concrete type
let actorSystem = BLEActorSystem(
peripheralManager: CoreBluetoothPeripheralManager(),
centralManager: CoreBluetoothCentralManager()
)
// Option 1: Discover sensors using concrete type
let sensors = try await actorSystem.discover(IndoorSensor.self, timeout: 10.0)
if let sensor = sensors.first {
let temp = try await sensor.getTemperature()
}
// Option 2: Resolve by ID using @Resolvable-generated stub
// The @Resolvable macro generates a $TemperatureSensor type automatically
let knownSensorID = UUID(/* saved sensor ID */)
let sensor = try $TemperatureSensor.resolve(id: knownSensorID, using: actorSystem)
// Call methods defined in the protocol
try await sensor.setTemperatureUnit("fahrenheit")
let temp = try await sensor.getTemperature()
// Pass around as protocol type - no concrete type needed!
func monitorTemperature(_ sensor: any TemperatureSensor) async throws {
let temp = try await sensor.getTemperature()
print("Current temperature: \(temp)")
}
try await monitorTemperature(sensor)
```
#### Use Cases
```swift
// Example: Multiple sensor types with same protocol
@Resolvable
protocol EnvironmentSensor: PeripheralActor {
distributed func readValue() async throws -> Double
distributed func calibrate() async throws
}
distributed actor TemperatureSensor: EnvironmentSensor { /* ... */ }
distributed actor HumiditySensor: EnvironmentSensor { /* ... */ }
distributed actor PressureSensor: EnvironmentSensor { /* ... */ }
// Central can work with all sensors uniformly
func readAllSensors(_ sensors: [any EnvironmentSensor]) async throws -> [Double] {
try await withThrowingTaskGroup(of: Double.self) { group in
for sensor in sensors {
group.addTask { try await sensor.readValue() }
}
var values: [Double] = []
for try await value in group {
values.append(value)
}
return values
}
}
```
#### Key Benefits
- **Protocol-First Design**: Define your device APIs as protocols with distributed methods
- **Automatic Stub Generation**: Swift compiler generates `$ProtocolName` types for resolution
- **Location Transparency**: Work with protocol types without knowing concrete implementations
- **Module Separation**: Share protocol definitions across modules, keep implementations private
- **Type Safety**: Full compiler verification of distributed method calls
- **Flexible Resolution**: Resolve actors by ID without discovery process
### Custom Service Metadata
```swift
distributed actor SmartLight: PeripheralActor {
typealias ActorSystem = BLEActorSystem
// Custom service configuration
static var serviceMetadata: ServiceMetadata {
ServiceMetadata(
uuid: UUID(uuidString: "12345678-1234-5678-9ABC-123456789ABC")!,
characteristics: [
CharacteristicMetadata(
uuid: UUID(uuidString: "87654321-4321-8765-CBA9-987654321CBA")!,
properties: [.read, .write, .notify],
permissions: [.readable, .writeable]
)
]
)
}
distributed func setBrightness(_ level: Int) async throws {
// Control light brightness
}
distributed func setColor(_ rgb: (r: Int, g: Int, b: Int)) async throws {
// Set RGB color
}
}
```
### Notifications and Subscriptions
```swift
distributed actor HeartRateMonitor: PeripheralActor {
typealias ActorSystem = BLEActorSystem
// Stream heart rate data
distributed func streamHeartRate() -> AsyncStream