Bleu

2.0.0

BLE (Bluetooth LE) for U🎁 Bleu is the best in the Bluetooth library.
1amageek/Bleu

What's New

Bleu 2.0.0 - Protocol-Oriented BLE with @Resolvable

2025-11-05T02:25:53Z

🎯 Major Features

Protocol-Oriented BLE with @resolvable

  • Define custom distributed actor protocols with @Resolvable macro
  • Automatic stub generation for protocol-based actor resolution
  • Zero boilerplate, maximum simplicity
  • Call BLE methods as if they were local

Distributed Actor Architecture

  • Transparent RPC over BLE using Swift's 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

✨ What's New in 2.0.0

  • @Resolvable Protocol Support: Define your BLE device APIs as protocols
  • Custom Protocol Testing: Comprehensive test suite for @Resolvable usage
  • Enhanced Documentation: Clear examples showing protocol-oriented design
  • Compiler Warning Fixes: Clean build with zero warnings
  • GitHub Actions CI: Automated testing on macOS 26

πŸš€ Quick Start

// 1. Define your BLE device API as a protocol
@Resolvable
protocol TemperatureSensor: PeripheralActor {
    distributed func getTemperature() async throws -> Double
}

// 2. Peripheral: Implement the protocol
distributed actor MyThermometer: TemperatureSensor {
    typealias ActorSystem = BLEActorSystem
    distributed func getTemperature() async throws -> Double {
        return 25.5
    }
}

// 3. Central: Resolve and call methods over BLE
let sensor = try $TemperatureSensor.resolve(id: sensorID, using: actorSystem)
let temp = try await sensor.getTemperature()  // πŸŽ‰ That's it!

πŸ“ Documentation

  • Updated README with @Resolvable quick start guide
  • Step-by-step protocol definition examples
  • Clear separation between protocol-based and concrete actor approaches
  • Links to Swift Evolution proposal SE-0428

πŸ§ͺ Testing

  • New ResolvableTests suite demonstrating custom protocols
  • All tests passing on macOS 26
  • Continuous integration via GitHub Actions

πŸ“¦ Installation

Add Bleu to your project using Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/1amageek/Bleu.git", from: "2.0.0")
]

πŸ”— Links

Bleu Logo

Bleu 2

Modern Bluetooth Low Energy Framework with Swift Distributed Actors

Swift 6.0 Platforms Swift Package Manager Test License


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:

dependencies: [
    .package(url: "https://github.com/1amageek/Bleu.git", from: "2.0.0")
]

The Simplest Way: Using @Resolvable

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:

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

// 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

// 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) 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.

// 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

// 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

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

distributed actor HeartRateMonitor: PeripheralActor {
    typealias ActorSystem = BLEActorSystem
    
    // Stream heart rate data
    distributed func streamHeartRate() -> AsyncStream<Int> {
        AsyncStream { continuation in
            // Setup sensor monitoring
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                let heartRate = Int.random(in: 60...100)
                continuation.yield(heartRate)
            }
        }
    }
}

// Client side - subscribe to updates
let monitor = try await actorSystem.connect(to: deviceID, as: HeartRateMonitor.self)
for await heartRate in await monitor.streamHeartRate() {
    print("Heart rate: \(heartRate) BPM")
}

Error Handling

do {
    let devices = try await actorSystem.discover(TemperatureSensor.self)
    // ... use devices
} catch BleuError.bluetoothPoweredOff {
    print("Please enable Bluetooth")
} catch BleuError.connectionTimeout {
    print("Connection timed out")
} catch {
    print("Unexpected error: \(error)")
}

πŸ“‹ Architecture

Core Components

  • BLEActorSystem: The distributed actor system managing BLE communication
  • PeripheralActor: Protocol for actors that can be advertised as BLE peripherals
  • BLETransport: Handles packet fragmentation and reassembly
  • ServiceMapper: Maps actor types to BLE service metadata
  • Manager Protocols: Protocol-oriented design for testability without hardware

Communication Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Central   β”‚                    β”‚ Peripheral  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                   β”‚
       β”‚  discover(TemperatureSensor)      β”‚
       │────────────────────────────────>  β”‚
       β”‚                                   β”‚
       β”‚  <────────────────────────────    β”‚
       β”‚     [TemperatureSensor actors]    β”‚
       β”‚                                   β”‚
       β”‚  remoteSensor.getTemperature()    β”‚
       │────────────────────────────────>  β”‚
       β”‚                                   β”‚
       β”‚  <────────────────────────────    β”‚
       β”‚            25.5Β°C                 β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Binary Packet Format

Bleu uses an efficient binary packet format for BLE communication:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  UUID (16B)  β”‚  Seq (2B)   β”‚  Total (2B)  β”‚ CRC (4B) β”‚ Payload
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        24-byte header

πŸ§ͺ Testing

Bleu 2 features a Protocol-Oriented Testing Architecture that enables comprehensive testing without requiring real Bluetooth hardware or TCC permissions. This architecture provides in-memory BLE simulation, allowing unit and integration tests to run in CI/CD environments.

Key Testing Benefits

  • βœ… No Hardware Required: Mock implementations simulate complete BLE behavior
  • βœ… No TCC Permissions: Unit and integration tests run without Bluetooth access
  • βœ… Fast Execution: Tests complete in seconds, not minutes
  • βœ… CI/CD Friendly: All tests (except hardware validation) run in automated environments
  • βœ… Type-Safe: Full type safety across mock and production implementations

Test Directory Structure

Tests/BleuTests/
β”œβ”€β”€ Unit/                          # Pure unit tests (no BLE)
β”‚   β”œβ”€β”€ UnitTests.swift            # Core functionality tests
β”‚   β”œβ”€β”€ RPCTests.swift             # RPC mechanism tests
β”‚   β”œβ”€β”€ EventBridgeTests.swift    # Event routing tests
β”‚   └── TransportLayerTests.swift # Message transport tests
β”‚
β”œβ”€β”€ Integration/                   # Mock-based integration tests
β”‚   β”œβ”€β”€ MockActorSystemTests.swift # Mock manager tests
β”‚   β”œβ”€β”€ FullWorkflowTests.swift    # Complete workflows
β”‚   └── ErrorHandlingTests.swift   # Error scenarios
β”‚
β”œβ”€β”€ Hardware/                      # Real BLE hardware tests
β”‚   └── RealBLETests.swift         # Requires real hardware
β”‚
└── Mocks/                         # Test utilities
    β”œβ”€β”€ TestHelpers.swift          # Common test helpers
    └── MockActorExamples.swift    # Example distributed actors

Running Tests

# Run all tests (unit + integration, skips hardware)
swift test

# Run specific test suite
swift test --filter "Mock Actor System Tests"

# Run with verbose output
swift test --verbose

# Parallel execution
swift test --parallel

Quick Start: Writing Tests

Unit Tests (No BLE Dependency)

import Testing
@testable import Bleu

@Suite("Transport Layer")
struct TransportTests {
    @Test("Packet fragmentation")
    func testFragmentation() async {
        let transport = BLETransport.shared
        let data = Data(repeating: 0xFF, count: 1000)

        let packets = await transport.fragment(data)
        #expect(packets.count > 1)
    }
}

Integration Tests with Mock BLE

import Testing
import Distributed
@testable import Bleu

@Suite("BLE System Integration")
struct MockBLESystemTests {
    @Test("Complete discovery to RPC flow")
    func testCompleteFlow() async throws {
        // Create bridge for cross-system communication
        let bridge = MockBLEBridge()

        // Create peripheral system with mock managers
        var peripheralConfig = TestHelpers.fastPeripheralConfig()
        peripheralConfig.bridge = bridge

        let mockPeripheral1 = MockPeripheralManager(configuration: peripheralConfig)
        let mockCentral1 = MockCentralManager()
        let peripheralSystem = BLEActorSystem(
            peripheralManager: mockPeripheral1,
            centralManager: mockCentral1
        )

        // Create central system with mock managers
        var centralConfig = TestHelpers.fastCentralConfig()
        centralConfig.bridge = bridge

        let mockPeripheral2 = MockPeripheralManager()
        let mockCentral2 = MockCentralManager(configuration: centralConfig)
        let centralSystem = BLEActorSystem(
            peripheralManager: mockPeripheral2,
            centralManager: mockCentral2
        )

        // Wait for systems to be ready
        try await TestHelpers.waitForReady(peripheralSystem)
        try await TestHelpers.waitForReady(centralSystem)

        // Define test actor
        distributed actor TestSensor: PeripheralActor {
            typealias ActorSystem = BLEActorSystem

            distributed func getValue() async -> Int {
                return 42
            }
        }

        // Setup peripheral
        let sensor = TestSensor(actorSystem: peripheralSystem)
        await mockPeripheral1.setPeripheralID(sensor.id)
        try await peripheralSystem.startAdvertising(sensor)

        // Register peripheral for discovery
        let serviceUUID = UUID.serviceUUID(for: TestSensor.self)
        let serviceMetadata = ServiceMapper.createServiceMetadata(from: TestSensor.self)

        let discovered = TestHelpers.createDiscoveredPeripheral(
            id: sensor.id,
            name: "TestSensor",
            serviceUUIDs: [serviceUUID]
        )

        await mockCentral2.registerPeripheral(discovered, services: [serviceMetadata])

        // Test discovery and RPC
        let sensors = try await centralSystem.discover(TestSensor.self, timeout: 1.0)
        #expect(sensors.count == 1)

        let value = try await sensors[0].getValue()
        #expect(value == 42)
    }
}

Using Test Helpers

Bleu provides comprehensive test utilities:

// Fast mock configurations (10ms delays)
let peripheralConfig = TestHelpers.fastPeripheralConfig()
let centralConfig = TestHelpers.fastCentralConfig()

let mockPeripheral = MockPeripheralManager(configuration: peripheralConfig)
let mockCentral = MockCentralManager(configuration: centralConfig)
let system = BLEActorSystem(
    peripheralManager: mockPeripheral,
    centralManager: mockCentral
)

// Generate test data
let randomData = TestHelpers.randomData(size: 100)
let deterministicData = TestHelpers.deterministicData(size: 100, pattern: 0xAB)

// Create test peripherals
let peripheral = TestHelpers.createDiscoveredPeripheral(
    id: UUID(),
    name: "TestDevice",
    rssi: -50,
    serviceUUIDs: [serviceUUID]
)

// Pre-built test actors
let sensor = SensorActor(actorSystem: system)
let temp = try await sensor.readTemperature() // Returns 22.5

Mock Actor Examples

Pre-defined distributed actors for common testing scenarios:

// Simple value actor
let actor = SimpleValueActor(actorSystem: system)
let value = try await actor.getValue() // Returns 42

// Stateful counter actor
let counter = CounterActor(actorSystem: system)
let count = try await counter.increment() // Returns 1

// Error-throwing actor
let errorActor = ErrorThrowingActor(actorSystem: system)
try await errorActor.alwaysThrows() // Throws TestError

Mock System Configuration

Mocks support extensive configuration for testing various scenarios:

// Configure mock delays
var peripheralConfig = MockPeripheralManager.Configuration()
peripheralConfig.advertisingDelay = 0.01 // Fast for testing
peripheralConfig.writeResponseDelay = 0.01

var centralConfig = MockCentralManager.Configuration()
centralConfig.scanDelay = 0.01
centralConfig.connectionDelay = 0.01

// Configure failure scenarios
peripheralConfig.shouldFailAdvertising = true
centralConfig.shouldFailConnection = true

let mockPeripheral = MockPeripheralManager(configuration: peripheralConfig)
let mockCentral = MockCentralManager(configuration: centralConfig)
let system = BLEActorSystem(
    peripheralManager: mockPeripheral,
    centralManager: mockCentral
)

Hardware Tests

Hardware tests require real BLE hardware and TCC permissions. They are disabled by default:

@Suite("Real BLE Hardware Tests", .disabled("Requires real BLE hardware"))
struct RealBLETests {
    @Test("Real device communication")
    func testRealDevice() async throws {
        // Create system with real CoreBluetooth managers (requires TCC)
        let peripheralManager = CoreBluetoothPeripheralManager()
        let centralManager = CoreBluetoothCentralManager()
        let system = BLEActorSystem(
            peripheralManager: peripheralManager,
            centralManager: centralManager
        )
        // ... test with real hardware
    }
}

To run hardware tests, remove the .disabled attribute and ensure your app has proper Info.plist with Bluetooth permissions.

CI/CD Integration

GitHub Actions example:

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: macos-26
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: swift test

Hardware tests are automatically skipped in CI/CD due to .disabled attribute.

Comprehensive Testing Guide

For complete documentation including:

  • Detailed architecture explanation
  • Mock system usage patterns
  • Simulating BLE events
  • Error handling tests
  • Best practices
  • Troubleshooting guide

See the Complete Testing Guide.

πŸ“± Platform Requirements

  • iOS 18.0+ / macOS 15.0+ / watchOS 11.0+ / tvOS 18.0+
  • Swift 6.0+
  • Xcode 16.0+

πŸ“š Documentation

Comprehensive documentation is available in the docs/ directory:

For contributors and maintainers:

πŸ“„ License

Bleu is available under the MIT license. See the LICENSE file for more info.


Made with ❀️ by @1amageek

Documentation β€’ Issues β€’ Discussions

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

Last updated: Sun Nov 09 2025 02:27:50 GMT-1000 (Hawaii-Aleutian Standard Time)