Files
universal_ble/darwin/Classes/UniversalBleHelper.swift
T
Rohit Sangwan 1a0e48ba1a Add manufacturer data filtering (#23)
* Add manufacturer data filtering

* Implement manufacturer data filter in windows

* Fix manufacturerData filtering on windows

* Fix linux

* Clean comments

---------

Co-authored-by: Foti Dim <fdimanidis@gmail.com>
2024-03-18 18:44:44 +01:00

239 lines
7.1 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
}
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
}
}
}
extension Error {
func toFlutterError() -> FlutterError {
let nsError = self as NSError
let errorCode: String = .init(nsError.code)
let errorDescription: String = nsError.localizedDescription
return FlutterError(code: errorCode, message: errorDescription, details: nil)
}
}
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 String {
var validFullUUID: String {
let uuidLength = count
if uuidLength == 4 || uuidLength == 8 {
let baseUuid = "00000000-0000-1000-8000-00805F9B34FB"
let start = baseUuid.startIndex
let range = baseUuid.index(start, offsetBy: 4 - uuidLength) ..< baseUuid.index(start, offsetBy: 4)
return baseUuid.replacingCharacters(in: range, with: self)
} else {
return self
}
}
}
extension FlutterStandardTypedData {
func toData() -> Data {
return Data(data)
}
}
func isManufacturerDataMatchingFilters(filters: [UniversalManufacturerDataFilter], msd: Data?) -> Bool {
guard let msd = msd, !msd.isEmpty else {
return false
}
for filter in filters {
guard let companyIdentifier: Int64 = filter.companyIdentifier else {
continue
}
let manufacturerId = msd.subdata(in: 0 ..< 2).withUnsafeBytes { $0.load(as: UInt16.self) }
let manufacturerData = msd.subdata(in: 2 ..< msd.count)
if manufacturerId == companyIdentifier && findData(find: filter.data?.toData(), inData: manufacturerData, usingMask: filter.mask?.toData()) {
return true
}
}
return false
}
func findData(find: Data?, inData data: Data, usingMask mask: Data?) -> Bool {
if let find = find {
// If mask is null, use a default mask of all 1s
let mask = mask ?? Data(repeating: 0xFF, count: find.count)
// Ensure find & mask are same length
guard find.count == mask.count else {
return false
}
for i in 0 ..< find.count {
// Perform bitwise AND with mask and then compare
if (find[i] & mask[i]) != (data[i] & mask[i]) {
return false
}
}
}
return true
}
// 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 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
}
}