-
-
Notifications
You must be signed in to change notification settings - Fork 2
BLE Communication
Latisha. edited this page Dec 27, 2024
·
1 revision
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...
]
}
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
}
}
enum ConnectionState {
case disconnected
case scanning
case connecting
case connected
case ready
}
- Device Discovery
public func startScanning() {
centralManager.scanForPeripherals(withServices: nil, options: nil)
isScanning = true
}
- 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
}
- 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)
}
}
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
}
}
- 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
}
- 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
}
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
}
}
enum BLEError: Error {
case deviceNotFound
case connectionFailed
case serviceDiscoveryFailed
case characteristicNotFound
case writeFailure
case readTimeout
case invalidState
}
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))
}
}
}
}
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)
}
}