Skip to content

BLE Communication

Latisha. edited this page Dec 27, 2024 · 1 revision

BLE Communication

BLE Implementation Details

Core Components

class CoreBluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate {
    // Singleton instance
    public static let shared = CoreBluetoothManager()
    
    // Published properties
    @Published public var centralManager: CBCentralManager!
    @Published public var discoveredPeripherals: [CBPeripheral] = []
    @Published public var isPeripheralReady = false
    @Published public var connectedDevice: CBPeripheral?
    @Published public var isScanning = false
    
    // Service identifiers
    private let knownSerialServices: [SerialService] = [
        SerialService(uuid: "0000fefb-0000-1000-8000-00805f9b34fb", 
                     vendor: "Heinrichs-Weikamp", 
                     product: "Telit/Stollmann"),
        // Other services...
    ]
}

Service Discovery

extension CoreBluetoothManager {
    func discoverServices() -> Bool {
        guard let peripheral = self.peripheral else { return false }
        
        peripheral.discoverServices(nil)
        while writeCharacteristic == nil || notifyCharacteristic == nil {
            RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
        }
        return writeCharacteristic != nil && notifyCharacteristic != nil
    }
}

Connection Management

Connection States

enum ConnectionState {
    case disconnected
    case scanning
    case connecting
    case connected
    case ready
}

Connection Process

  1. Device Discovery
public func startScanning() {
    centralManager.scanForPeripherals(withServices: nil, options: nil)
    isScanning = true
}
  1. Connection Establishment
public func connectToDevice(_ address: String) -> Bool {
    guard let uuid = UUID(uuidString: address),
          let peripheral = centralManager.retrievePeripherals(
            withIdentifiers: [uuid]
          ).first else {
        return false
    }
    
    peripheral.delegate = self
    centralManager.connect(peripheral, options: nil)
    return true
}
  1. Service Configuration
public func peripheral(_ peripheral: CBPeripheral, 
                      didDiscoverServices error: Error?) {
    guard let services = peripheral.services else { return }
    
    for service in services {
        if isExcludedService(service.uuid) {
            continue
        }
        if let knownService = isKnownSerialService(service.uuid) {
            preferredService = service
        }
        peripheral.discoverCharacteristics(nil, for: service)
    }
}

Data Transfer Protocol

HDLC Implementation

struct HDLCProtocol {
    static let frameMarker: UInt8 = 0x7E
    static let escapeMarker: UInt8 = 0x7D
    static let escapeXOR: UInt8 = 0x20
    
    static func encode(_ data: Data) -> Data {
        var encoded = Data([frameMarker])
        for byte in data {
            if byte == frameMarker || byte == escapeMarker {
                encoded.append(escapeMarker)
                encoded.append(byte ^ escapeXOR)
            } else {
                encoded.append(byte)
            }
        }
        encoded.append(frameMarker)
        return encoded
    }
}

Data Transfer

  1. Write Operations
public func writeData(_ data: Data) -> Bool {
    guard let peripheral = self.peripheral,
          let characteristic = self.writeCharacteristic else { 
        return false 
    }
    peripheral.writeValue(data, 
                         for: characteristic, 
                         type: .withoutResponse)
    return true
}
  1. Read Operations
public func readDataPartial(_ requested: Int) -> Data? {
    let startTime = Date()
    let timeout: TimeInterval = 1.0
    
    while Date().timeIntervalSince(startTime) < timeout {
        var outData: Data?
        queue.sync {
            if receivedData.count > 0 {
                let amount = min(requested, receivedData.count)
                outData = receivedData.prefix(amount)
                receivedData.removeSubrange(0..<amount)
            }
        }
        if let data = outData {
            return data
        }
        RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.01))
    }
    return nil
}

Buffer Management

class BufferManager {
    private var buffer = Data()
    private let queue = DispatchQueue(label: "com.buffer.queue")
    
    func append(_ data: Data) {
        queue.sync {
            buffer.append(data)
        }
    }
    
    func read(_ size: Int) -> Data? {
        var result: Data?
        queue.sync {
            if buffer.count >= size {
                result = buffer.prefix(size)
                buffer.removeSubrange(0..<size)
            }
        }
        return result
    }
}

Error Handling

Connection Errors

enum BLEError: Error {
    case deviceNotFound
    case connectionFailed
    case serviceDiscoveryFailed
    case characteristicNotFound
    case writeFailure
    case readTimeout
    case invalidState
}

Error Recovery

class ConnectionRecovery {
    static let maxRetries = 3
    static let retryDelay: TimeInterval = 1.0
    
    func attemptReconnection() async throws {
        for attempt in 1...Self.maxRetries {
            do {
                try await reconnect()
                return
            } catch {
                if attempt == Self.maxRetries {
                    throw BLEError.connectionFailed
                }
                try await Task.sleep(nanoseconds: UInt64(Self.retryDelay * 1_000_000_000))
            }
        }
    }
}

State Monitoring

extension CoreBluetoothManager {
    func monitorState() {
        bleStatePublisher
            .sink { state in
                switch state {
                case .poweredOn:
                    self.handlePoweredOn()
                case .poweredOff:
                    self.handlePoweredOff()
                case .unauthorized:
                    self.handleUnauthorized()
                default:
                    break
                }
            }
            .store(in: &cancellables)
    }
}