// // BluetoothManager.swift // mcrl-app // // Created by Simon Oberzier on 17.02.26. // import CoreBluetooth import Combine class BluetoothManager: NSObject, ObservableObject { @Published var sensorValues = SensorData() @Published var connectionStatus = "Disconnected" var centralManager: CBCentralManager! var picoPeripheral: CBPeripheral? let serviceUUID = CBUUID(string: "0000FF00-0000-1000-8000-00805F9B34FB") let charUUID = CBUUID(string: "0000FF01-0000-1000-8000-00805F9B34FB") override init() { super.init() centralManager = CBCentralManager(delegate: self, queue: nil) } func parseSensorString(_ str: String) { let cleanStr = str.trimmingCharacters(in: .whitespacesAndNewlines) let components = cleanStr.components(separatedBy: ",") guard components.count >= 7, components[0] == "S" else { print("Received malformed string: \(cleanStr)") return } DispatchQueue.main.async { self.sensorValues.ax = Double(components[1]) ?? 0 self.sensorValues.ay = Double(components[2]) ?? 0 self.sensorValues.az = Double(components[3]) ?? 0 self.sensorValues.lat = Double(components[4]) ?? 0 self.sensorValues.lon = Double(components[5]) ?? 0 self.sensorValues.hasFix = components[6].contains("1") } } } extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: connectionStatus = "Scanning..." // Scanning with nil finds EVERYTHING. Safer for debugging. centralManager.scanForPeripherals(withServices: nil, options: nil) case .poweredOff: connectionStatus = "Bluetooth is Off" case .unauthorized: connectionStatus = "Permission Denied" default: connectionStatus = "Unknown State" } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { let name = peripheral.name ?? "Unknown" if name.contains("Pico") { print("Discovered: \(name)") connectionStatus = "Pico Found!" picoPeripheral = peripheral picoPeripheral?.delegate = self centralManager.stopScan() centralManager.connect(peripheral, options: nil) } } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { connectionStatus = "Connected" print("Connected to \(peripheral.name ?? "Device")") peripheral.discoverServices([serviceUUID]) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { connectionStatus = "Disconnected" print("Disconnected. Restarting scan...") centralManager.scanForPeripherals(withServices: nil, options: nil) } } extension BluetoothManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let services = peripheral.services else { return } for service in services { print("Service discovered: \(service.uuid)") peripheral.discoverCharacteristics([charUUID], for: service) } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristics = service.characteristics else { return } for characteristic in characteristics { if characteristic.uuid == charUUID { print("Found Data Characteristic!") peripheral.setNotifyValue(true, for: characteristic) } } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let data = characteristic.value, let str = String(data: data, encoding: .utf8) { parseSensorString(str) } } }