// // UniversalBleHelper.swift // universal_ble // // Created by Rohit Sangwan on 25/10/23. // import CoreBluetooth import Foundation #if os(iOS) import Flutter #elseif os(OSX) import FlutterMacOS #endif enum BleInputProperty: Int { case disabled = 0 case notification = 1 case indication = 2 } enum BleOutputProperty: Int { case withResponse = 0 case withoutResponse = 1 } enum BlueConnectionState: Int64 { case connected = 0 case disconnected = 1 case connecting = 2 case disconnecting = 3 } enum AvailabilityState: Int64 { case unknown = 0 case resetting = 1 case unsupported = 2 case unauthorized = 3 case poweredOff = 4 case poweredOn = 5 } enum CharacteristicProperty: Int64 { case broadcast = 0 case read = 1 case writeWithoutResponse = 2 case write = 3 case notify = 4 case indicate = 5 case authenticatedSignedWrites = 6 case extendedProperties = 7 } extension CBCharacteristicProperties { var toCharacteristicProperty: [Int64] { var properties = [Int64]() if contains(.broadcast) { properties.append(CharacteristicProperty.broadcast.rawValue) } if contains(.read) { properties.append(CharacteristicProperty.read.rawValue) } if contains(.writeWithoutResponse) { properties.append(CharacteristicProperty.writeWithoutResponse.rawValue) } if contains(.write) { properties.append(CharacteristicProperty.write.rawValue) } if contains(.notify) { properties.append(CharacteristicProperty.notify.rawValue) } if contains(.indicate) { properties.append(CharacteristicProperty.indicate.rawValue) } if contains(.authenticatedSignedWrites) { properties.append(CharacteristicProperty.authenticatedSignedWrites.rawValue) } if contains(.extendedProperties) { properties.append(CharacteristicProperty.extendedProperties.rawValue) } return properties } } extension CBManagerState { func toAvailabilityState() -> AvailabilityState { switch self { case .unknown: return AvailabilityState.unknown case .resetting: return AvailabilityState.resetting case .unsupported: return AvailabilityState.unsupported case .unauthorized: return AvailabilityState.unauthorized case .poweredOff: return AvailabilityState.poweredOff case .poweredOn: return AvailabilityState.poweredOn @unknown default: return AvailabilityState.unknown } } } /// Maps string error codes to UniversalBleErrorCode enum func mapErrorCodeToEnum(_ code: String) -> UniversalBleErrorCode { switch code.lowercased() { case "notsupported", "not_supported": return .notSupported case "notimplemented", "not_implemented": return .notImplemented case "channel-error", "channelerror": return .channelError case "failed": return .failed case "devicedisconnected", "device_disconnected": return .deviceDisconnected case "illegalargument", "illegal_argument": return .illegalArgument case "invalidaction", "invalid_action": return .invalidAction case "readfailed", "read_failed": return .readFailed case "devicenotfound", "device_not_found": return .deviceNotFound case "servicenotfound", "service_not_found": return .serviceNotFound case "characteristicnotfound", "characteristic_not_found": return .characteristicNotFound case "invalidserviceuuid", "invalid_service_uuid": return .invalidServiceUuid case "characteristicdoesnotsupportread": return .characteristicDoesNotSupportRead case "characteristicdoesnotsupportwrite": return .characteristicDoesNotSupportWrite case "characteristicdoesnotsupportwritewithoutresponse": return .characteristicDoesNotSupportWriteWithoutResponse case "characteristicdoesnotsupportnotify": return .characteristicDoesNotSupportNotify case "characteristicdoesnotsupportindicate": return .characteristicDoesNotSupportIndicate default: return .unknownError } } /// Creates a PigeonError with the error code enum in details func createFlutterError( code: UniversalBleErrorCode, message: String? = nil, details: String? = nil ) -> PigeonError { // Pass the enum's rawValue (Int) in code as string, and enum name in details return PigeonError( code: code.rawValue.description, message: message, details: details ?? code.rawValue ) } extension Error { func toFlutterError() -> PigeonError { let nsError = self as NSError let errorCode: String = .init(nsError.code) let errorDescription: String = nsError.localizedDescription let mappedCode = mapErrorCodeToEnum(errorCode) return createFlutterError(code: mappedCode, message: errorDescription, details: errorCode) } } public extension CBUUID { var uuidStr: String { uuidString.lowercased() } } public extension CBPeripheral { // FIXME: https://forums.developer.apple.com/thread/84375 var uuid: UUID { value(forKey: "identifier") as! NSUUID as UUID } func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? { let GSS_SUFFIX = "0000-1000-8000-00805f9b34fb" let s = services?.first { $0.uuid.uuidStr.lowercased() == service.lowercased() || service.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased() } let c = s?.characteristics?.first { $0.uuid.uuidStr.lowercased() == characteristic.lowercased() || characteristic.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased() } return c } func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) { guard let characteristic = getCharacteristic(characteristic, of: service) else { return } setNotifyValue(bleInputProperty != "disabled", for: characteristic) } } extension FlutterStandardTypedData { func toData() -> Data { return Data(data) } } // Future classes class CharacteristicReadFuture { let deviceId: String let characteristicId: String let serviceId: String? let result: (Result) -> Void init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result) -> Void) { self.deviceId = deviceId self.characteristicId = characteristicId self.serviceId = serviceId self.result = result } } class CharacteristicWriteFuture { let deviceId: String let characteristicId: String let serviceId: String? let result: (Result) -> Void init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result) -> Void) { self.deviceId = deviceId self.characteristicId = characteristicId self.serviceId = serviceId self.result = result } } class CharacteristicNotifyFuture { let deviceId: String let characteristicId: String let serviceId: String? let result: (Result) -> Void init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result) -> Void) { self.deviceId = deviceId self.characteristicId = characteristicId self.serviceId = serviceId self.result = result } } class DiscoverServicesFuture { let deviceId: String let result: (Result<[UniversalBleService], Error>) -> Void init(deviceId: String, result: @escaping (Result<[UniversalBleService], Error>) -> Void) { self.deviceId = deviceId self.result = result } } class RssiReadFuture { let deviceId: String let result: (Result) -> Void init(deviceId: String, result: @escaping (Result) -> Void) { self.deviceId = deviceId self.result = result } }