Files
universal_ble/darwin/Classes/UniversalBleHelper.swift
T
2026-01-05 17:49:55 +01:00

271 lines
8.2 KiB
Swift

//
// 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<FlutterStandardTypedData, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<FlutterStandardTypedData, Error>) -> 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, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<Void, Error>) -> 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, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<Void, Error>) -> 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<Int64, Error>) -> Void
init(deviceId: String, result: @escaping (Result<Int64, Error>) -> Void) {
self.deviceId = deviceId
self.result = result
}
}