1a0e48ba1a
* 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>
239 lines
7.1 KiB
Swift
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
|
|
}
|
|
}
|