627 lines
26 KiB
Swift
627 lines
26 KiB
Swift
//
|
|
// BlePeripheral.swift
|
|
// NewtManager
|
|
//
|
|
// Created by Antonio García on 12/09/2016.
|
|
// Copyright © 2016 Adafruit. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import CoreBluetooth
|
|
#if COMMANDLINE
|
|
#else
|
|
import MSWeakTimer
|
|
#endif
|
|
|
|
// TODO: Modernize completion blocks to use Swift.Result
|
|
|
|
class BlePeripheral: NSObject {
|
|
// Config
|
|
private static var kProfileCharacteristicUpdates = true
|
|
|
|
// Notifications
|
|
enum NotificationUserInfoKey: String {
|
|
case uuid = "uuid"
|
|
case name = "name"
|
|
case invalidatedServices = "invalidatedServices"
|
|
}
|
|
|
|
enum PeripheralError: Error {
|
|
case timeout
|
|
}
|
|
|
|
// Data
|
|
var peripheral: CBPeripheral
|
|
var rssi: Int? // rssi only is updated when a non undefined value is received from CoreBluetooth. Note: this is slighty different to the CoreBluetooth implementation, because it will not be updated with undefined values
|
|
var lastSeenTime: CFAbsoluteTime
|
|
|
|
var identifier: UUID {
|
|
return peripheral.identifier
|
|
}
|
|
|
|
var name: String? {
|
|
return peripheral.name
|
|
}
|
|
|
|
var state: CBPeripheralState {
|
|
return peripheral.state
|
|
}
|
|
|
|
struct Advertisement {
|
|
var advertisementData: [String: Any]
|
|
|
|
init(advertisementData: [String: Any]?) {
|
|
self.advertisementData = advertisementData ?? [String: Any]()
|
|
}
|
|
|
|
// Advertisement data formatted
|
|
var localName: String? {
|
|
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
|
}
|
|
|
|
var manufacturerData: Data? {
|
|
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
|
}
|
|
|
|
var manufacturerHexDescription: String? {
|
|
guard let manufacturerData = manufacturerData else { return nil }
|
|
return HexUtils.hexDescription(data: manufacturerData)
|
|
// return String(data: manufacturerData, encoding: .utf8)
|
|
}
|
|
|
|
var manufacturerIdentifier: Data? {
|
|
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
|
|
let manufacturerIdentifierData = manufacturerData[0..<2]
|
|
return manufacturerIdentifierData
|
|
}
|
|
|
|
var services: [CBUUID]? {
|
|
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
|
}
|
|
|
|
var servicesOverflow: [CBUUID]? {
|
|
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
|
|
}
|
|
|
|
var servicesSolicited: [CBUUID]? {
|
|
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
|
|
}
|
|
|
|
var serviceData: [CBUUID: Data]? {
|
|
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
|
|
}
|
|
|
|
var txPower: Int? {
|
|
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
|
|
return number?.intValue
|
|
}
|
|
|
|
var isConnectable: Bool? {
|
|
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
|
|
return connectableNumber?.boolValue
|
|
}
|
|
}
|
|
var advertisement: Advertisement
|
|
|
|
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
|
|
private class CaptureReadHandler {
|
|
|
|
var identifier: String
|
|
var result: CapturedReadCompletionHandler
|
|
var timeoutTimer: MSWeakTimer?
|
|
var timeoutAction: ((String)->())?
|
|
var isNotifyOmitted: Bool
|
|
|
|
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
|
|
self.identifier = identifier
|
|
self.result = result
|
|
self.isNotifyOmitted = isNotifyOmitted
|
|
|
|
if let timeout = timeout {
|
|
self.timeoutAction = timeoutAction
|
|
timeoutTimer = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(timerFired), userInfo: nil, repeats: false, dispatchQueue: .global(qos: .background))
|
|
}
|
|
}
|
|
|
|
@objc private func timerFired() {
|
|
timeoutTimer?.invalidate()
|
|
timeoutTimer = nil
|
|
result(nil, PeripheralError.timeout)
|
|
timeoutAction?(identifier)
|
|
}
|
|
}
|
|
|
|
private func timeOutRemoveCaptureHandler(identifier: String) { // Default behaviour for a capture handler timeout
|
|
guard captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) else { return }
|
|
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
|
|
|
|
// Remove capture handler
|
|
captureReadHandlers.remove(at: index)
|
|
finishedExecutingCommand(error: PeripheralError.timeout)
|
|
}
|
|
|
|
// Internal data
|
|
private var notifyHandlers = [String: ((Error?) -> Void)]() // Nofify handlers for each service-characteristic
|
|
private var captureReadHandlers = [CaptureReadHandler]()
|
|
private var commandQueue = CommandQueue<BleCommand>()
|
|
|
|
// Profiling
|
|
//private var profileStartTime: CFTimeInterval = 0
|
|
|
|
// MARK: - Init
|
|
init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
|
self.peripheral = peripheral
|
|
self.advertisement = Advertisement(advertisementData: advertisementData)
|
|
self.rssi = rssi
|
|
self.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
|
|
|
super.init()
|
|
self.peripheral.delegate = self
|
|
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
|
commandQueue.executeHandler = executeCommand
|
|
}
|
|
|
|
deinit {
|
|
//DLog("peripheral deinit")
|
|
}
|
|
|
|
func reset() {
|
|
rssi = nil
|
|
notifyHandlers.removeAll()
|
|
captureReadHandlers.removeAll()
|
|
commandQueue.first()?.isCancelled = true // Stop current command if is processing
|
|
commandQueue.removeAll()
|
|
}
|
|
|
|
// MARK: - Discover
|
|
func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
|
|
let command = BleCommand(type: .discoverService, parameters: serviceUuids, completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func discover(characteristicUuids: [CBUUID]?, service: CBService, completion: ((Error?) -> Void)?) {
|
|
let command = BleCommand(type: .discoverCharacteristic, parameters: [characteristicUuids as Any, service], completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func discover(characteristicUuids: [CBUUID]?, serviceUuid: CBUUID, completion: ((Error?) -> Void)?) {
|
|
// Discover service
|
|
discover(serviceUuids: [serviceUuid]) { [unowned self] error in
|
|
guard error == nil else {
|
|
completion?(error)
|
|
return
|
|
}
|
|
|
|
guard let service = self.peripheral.services?.first(where: {$0.uuid == serviceUuid}) else {
|
|
completion?(BleCommand.CommandError.invalidService)
|
|
return
|
|
}
|
|
|
|
// Discover characteristic
|
|
self.discover(characteristicUuids: characteristicUuids, service: service, completion: completion)
|
|
}
|
|
}
|
|
|
|
func discoverDescriptors(characteristic: CBCharacteristic, completion: ((Error?) -> Void)?) {
|
|
let command = BleCommand(type: .discoverDescriptor, parameters: [characteristic], completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
// MARK: - Service
|
|
func discoveredService(uuid: CBUUID) -> CBService? {
|
|
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
|
return service
|
|
}
|
|
|
|
func service(uuid: CBUUID, completion: ((CBService?, Error?) -> Void)?) {
|
|
|
|
if let discoveredService = discoveredService(uuid: uuid) { // Service was already discovered
|
|
completion?(discoveredService, nil)
|
|
} else {
|
|
discover(serviceUuids: [uuid], completion: { [unowned self] (error) in // Discover service
|
|
var discoveredService: CBService?
|
|
if error == nil {
|
|
discoveredService = self.discoveredService(uuid: uuid)
|
|
}
|
|
completion?(discoveredService, error)
|
|
})
|
|
}
|
|
}
|
|
|
|
// MARK: - Characteristic
|
|
func discoveredCharacteristic(uuid: CBUUID, service: CBService) -> CBCharacteristic? {
|
|
let characteristic = service.characteristics?.first(where: {$0.uuid == uuid})
|
|
return characteristic
|
|
}
|
|
|
|
func characteristic(uuid: CBUUID, service: CBService, completion: ((CBCharacteristic?, Error?) -> Void)?) {
|
|
|
|
if let discoveredCharacteristic = discoveredCharacteristic(uuid: uuid, service: service) { // Characteristic was already discovered
|
|
completion?(discoveredCharacteristic, nil)
|
|
} else {
|
|
discover(characteristicUuids: [uuid], service: service, completion: { [unowned self] (error) in // Discover characteristic
|
|
var discoveredCharacteristic: CBCharacteristic?
|
|
if error == nil {
|
|
discoveredCharacteristic = self.discoveredCharacteristic(uuid: uuid, service: service)
|
|
}
|
|
completion?(discoveredCharacteristic, error)
|
|
})
|
|
}
|
|
}
|
|
|
|
func characteristic(uuid: CBUUID, serviceUuid: CBUUID, completion: ((CBCharacteristic?, Error?) -> Void)?) {
|
|
if let discoveredService = discoveredService(uuid: serviceUuid) { // Service was already discovered
|
|
characteristic(uuid: uuid, service: discoveredService, completion: completion)
|
|
} else { // Discover service
|
|
service(uuid: serviceUuid) { (service, error) in
|
|
if let service = service, error == nil { // Discover characteristic
|
|
self.characteristic(uuid: uuid, service: service, completion: completion)
|
|
} else {
|
|
completion?(nil, error != nil ? error: BleCommand.CommandError.invalidService)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func enableNotify(for characteristic: CBCharacteristic, handler: ((Error?) -> Void)?, completion: ((Error?) -> Void)? = nil) {
|
|
let command = BleCommand(type: .setNotify, parameters: [characteristic, true, handler as Any], completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func disableNotify(for characteristic: CBCharacteristic, completion: ((Error?) -> Void)? = nil) {
|
|
let command = BleCommand(type: .setNotify, parameters: [characteristic, false], completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func updateNotifyHandler(for characteristic: CBCharacteristic, handler: ((Error?) -> Void)? = nil) {
|
|
let identifier = handlerIdentifier(from: characteristic)
|
|
if notifyHandlers[identifier] == nil {
|
|
DLog("Warning: trying to update inexistent notifyHandler")
|
|
}
|
|
notifyHandlers[identifier] = handler
|
|
}
|
|
|
|
func readCharacteristic(_ characteristic: CBCharacteristic, completion readCompletion: @escaping CapturedReadCompletionHandler) {
|
|
let command = BleCommand(type: .readCharacteristic, parameters: [characteristic, readCompletion as Any], completion: nil)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func write(data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType, completion: ((Error?) -> Void)? = nil) {
|
|
let command = BleCommand(type: .writeCharacteristic, parameters: [characteristic, type, data], completion: completion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
func writeAndCaptureNotify(data: Data, for characteristic: CBCharacteristic, writeCompletion: ((Error?) -> Void)? = nil, readCharacteristic: CBCharacteristic, readTimeout: Double? = nil, readCompletion: CapturedReadCompletionHandler? = nil) {
|
|
let type: CBCharacteristicWriteType = .withResponse // Force write with response
|
|
let command = BleCommand(type: .writeCharacteristicAndWaitNofity, parameters: [characteristic, type, data, readCharacteristic, readCompletion as Any, readTimeout as Any], timeout: readTimeout, completion: writeCompletion)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
// MARK: - Descriptors
|
|
func readDescriptor(_ descriptor: CBDescriptor, completion readCompletion: @escaping CapturedReadCompletionHandler) {
|
|
let command = BleCommand(type: .readDescriptor, parameters: [descriptor, readCompletion as Any], completion: nil)
|
|
commandQueue.append(command)
|
|
}
|
|
|
|
// MARK: - Rssi
|
|
func readRssi() {
|
|
peripheral.readRSSI()
|
|
}
|
|
|
|
// MARK: - Command Queue
|
|
private class BleCommand: Equatable {
|
|
enum CommandType {
|
|
case discoverService
|
|
case discoverCharacteristic
|
|
case discoverDescriptor
|
|
case setNotify
|
|
case readCharacteristic
|
|
case writeCharacteristic
|
|
case writeCharacteristicAndWaitNofity
|
|
case readDescriptor
|
|
}
|
|
|
|
enum CommandError: Error {
|
|
case invalidService
|
|
}
|
|
|
|
var type: CommandType
|
|
var parameters: [Any]?
|
|
var completion: ((Error?) -> Void)?
|
|
var isCancelled = false
|
|
|
|
init(type: CommandType, parameters: [Any]?, timeout: Double? = nil, completion: ((Error?) -> Void)?) {
|
|
self.type = type
|
|
self.parameters = parameters
|
|
self.completion = completion
|
|
}
|
|
|
|
func completion(withError error: Error?) {
|
|
completion?(error)
|
|
}
|
|
|
|
static func == (left: BleCommand, right: BleCommand) -> Bool {
|
|
return left.type == right.type
|
|
}
|
|
}
|
|
|
|
private func executeCommand(command: BleCommand) {
|
|
|
|
switch command.type {
|
|
case .discoverService:
|
|
discoverService(with: command)
|
|
case .discoverCharacteristic:
|
|
discoverCharacteristic(with: command)
|
|
case .discoverDescriptor:
|
|
discoverDescriptor(with: command)
|
|
case .setNotify:
|
|
setNotify(with: command)
|
|
case .readCharacteristic:
|
|
readCharacteristic(with: command)
|
|
case .writeCharacteristic, .writeCharacteristicAndWaitNofity:
|
|
write(with: command)
|
|
case .readDescriptor:
|
|
readDescriptor(with: command)
|
|
}
|
|
}
|
|
|
|
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
|
|
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
|
}
|
|
|
|
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
|
|
return "\(descriptor.characteristic.service.uuid.uuidString)-\(descriptor.characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
|
}
|
|
|
|
private func finishedExecutingCommand(error: Error?) {
|
|
//DLog("finishedExecutingCommand")
|
|
|
|
// Result Callback
|
|
if let command = commandQueue.first(), !command.isCancelled {
|
|
command.completion(withError: error)
|
|
}
|
|
commandQueue.executeNext()
|
|
}
|
|
|
|
// MARK: - Commands
|
|
private func discoverService(with command: BleCommand) {
|
|
var serviceUuids = command.parameters as? [CBUUID]
|
|
let discoverAll = serviceUuids == nil
|
|
|
|
// Remove services already discovered from the query
|
|
if let services = peripheral.services, let serviceUuidsToDiscover = serviceUuids {
|
|
for (i, serviceUuid) in serviceUuidsToDiscover.enumerated().reversed() {
|
|
if services.contains(where: {$0.uuid == serviceUuid}) {
|
|
serviceUuids!.remove(at: i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Discover remaining uuids
|
|
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
|
|
peripheral.discoverServices(serviceUuids)
|
|
} else {
|
|
// Everthing was already discovered
|
|
finishedExecutingCommand(error: nil)
|
|
}
|
|
}
|
|
|
|
private func discoverCharacteristic(with command: BleCommand) {
|
|
var characteristicUuids = command.parameters![0] as? [CBUUID]
|
|
let discoverAll = characteristicUuids == nil
|
|
let service = command.parameters![1] as! CBService
|
|
|
|
// Remove services already discovered from the query
|
|
if let characteristics = service.characteristics, let characteristicUuidsToDiscover = characteristicUuids {
|
|
for (i, characteristicUuid) in characteristicUuidsToDiscover.enumerated().reversed() {
|
|
if characteristics.contains(where: {$0.uuid == characteristicUuid}) {
|
|
characteristicUuids!.remove(at: i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Discover remaining uuids
|
|
if discoverAll || (characteristicUuids != nil && characteristicUuids!.count > 0) {
|
|
//DLog("discover \(characteristicUuids == nil ? "all": String(characteristicUuids!.count)) characteristics for \(service.uuid.uuidString)")
|
|
peripheral.discoverCharacteristics(characteristicUuids, for: service)
|
|
} else {
|
|
// Everthing was already discovered
|
|
finishedExecutingCommand(error: nil)
|
|
}
|
|
}
|
|
|
|
private func discoverDescriptor(with command: BleCommand) {
|
|
let characteristic = command.parameters![0] as! CBCharacteristic
|
|
peripheral.discoverDescriptors(for: characteristic)
|
|
}
|
|
|
|
private func setNotify(with command: BleCommand) {
|
|
let characteristic = command.parameters![0] as! CBCharacteristic
|
|
let enabled = command.parameters![1] as! Bool
|
|
let identifier = handlerIdentifier(from: characteristic)
|
|
if enabled {
|
|
let handler = command.parameters![2] as? ((Error?) -> Void)
|
|
notifyHandlers[identifier] = handler
|
|
} else {
|
|
notifyHandlers.removeValue(forKey: identifier)
|
|
}
|
|
peripheral.setNotifyValue(enabled, for: characteristic)
|
|
}
|
|
|
|
private func readCharacteristic(with command: BleCommand) {
|
|
let characteristic = command.parameters!.first as! CBCharacteristic
|
|
let completion = command.parameters![1] as! CapturedReadCompletionHandler
|
|
|
|
let identifier = handlerIdentifier(from: characteristic)
|
|
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: completion, timeout: nil, timeoutAction: timeOutRemoveCaptureHandler)
|
|
captureReadHandlers.append(captureReadHandler)
|
|
|
|
peripheral.readValue(for: characteristic)
|
|
}
|
|
|
|
private func write(with command: BleCommand) {
|
|
let characteristic = command.parameters![0] as! CBCharacteristic
|
|
let writeType = command.parameters![1] as! CBCharacteristicWriteType
|
|
let data = command.parameters![2] as! Data
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: writeType)
|
|
|
|
if writeType == .withoutResponse {
|
|
if !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
|
let readCharacteristic = command.parameters![3] as! CBCharacteristic
|
|
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
|
let timeout = command.parameters![5] as? Double
|
|
|
|
let identifier = handlerIdentifier(from: readCharacteristic)
|
|
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
|
|
captureReadHandlers.append(captureReadHandler)
|
|
}
|
|
|
|
finishedExecutingCommand(error: nil)
|
|
}
|
|
}
|
|
|
|
private func readDescriptor(with command: BleCommand) {
|
|
let descriptor = command.parameters!.first as! CBDescriptor
|
|
let completion = command.parameters![1] as! CapturedReadCompletionHandler
|
|
|
|
let identifier = handlerIdentifier(from: descriptor)
|
|
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: completion, timeout: nil, timeoutAction: timeOutRemoveCaptureHandler)
|
|
captureReadHandlers.append(captureReadHandler)
|
|
|
|
peripheral.readValue(for: descriptor)
|
|
}
|
|
}
|
|
|
|
// MARK: - CBPeripheralDelegate
|
|
extension BlePeripheral: CBPeripheralDelegate {
|
|
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
|
DLog("peripheralDidUpdateName: \(name ?? "{ No Name }")")
|
|
NotificationCenter.default.post(name: .peripheralDidUpdateName, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.name.rawValue: name as Any])
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
|
DLog("didModifyServices")
|
|
NotificationCenter.default.post(name: .peripheralDidModifyServices, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.invalidatedServices.rawValue: invalidatedServices])
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
|
|
let identifier = handlerIdentifier(from: characteristic)
|
|
|
|
/*
|
|
if (BlePeripheral.kProfileCharacteristicUpdates) {
|
|
let currentTime = CACurrentMediaTime()
|
|
let elapsedTime = currentTime - profileStartTime
|
|
DLog("elapsed: \(String(format: "%.1f", elapsedTime * 1000))")
|
|
profileStartTime = currentTime
|
|
}
|
|
*/
|
|
//DLog("didUpdateValueFor \(characteristic.uuid.uuidString): \(String(data: characteristic.value ?? Data(), encoding: .utf8) ?? "<invalid>")")
|
|
|
|
// Check if waiting to capture this read
|
|
var isNotifyOmitted = false
|
|
var hasCaptureHandler = false
|
|
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
|
|
hasCaptureHandler = true
|
|
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
|
|
|
|
// Remove capture handler
|
|
let captureReadHandler = captureReadHandlers.remove(at: index)
|
|
|
|
// DLog("captureReadHandlers postRemove count: \(captureReadHandlers.count)")
|
|
|
|
// Cancel timeout timer
|
|
captureReadHandler.timeoutTimer?.invalidate()
|
|
captureReadHandler.timeoutTimer = nil
|
|
|
|
// Send result
|
|
let value = characteristic.value
|
|
// DLog("updated value: \(String(data: value!, encoding: .utf8)!)")
|
|
captureReadHandler.result(value, error)
|
|
|
|
isNotifyOmitted = captureReadHandler.isNotifyOmitted
|
|
}
|
|
|
|
// Notify
|
|
if !isNotifyOmitted {
|
|
if let notifyHandler = notifyHandlers[identifier] {
|
|
|
|
//let currentTime = CACurrentMediaTime()
|
|
notifyHandler(error)
|
|
//DLog("elapsed: \(String(format: "%.1f", (CACurrentMediaTime() - currentTime) * 1000))")
|
|
}
|
|
}
|
|
|
|
if hasCaptureHandler {
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
|
let characteristic = command.parameters![3] as! CBCharacteristic
|
|
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
|
let timeout = command.parameters![5] as? Double
|
|
let identifier = handlerIdentifier(from: characteristic)
|
|
|
|
//DLog("read timeout started")
|
|
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
|
|
captureReadHandlers.append(captureReadHandler)
|
|
} else {
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
|
let identifier = handlerIdentifier(from: descriptor)
|
|
|
|
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
|
|
// Remove capture handler
|
|
let captureReadHandler = captureReadHandlers.remove(at: index)
|
|
|
|
// Send result
|
|
let value = descriptor.value
|
|
captureReadHandler.result(value, error)
|
|
|
|
finishedExecutingCommand(error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
|
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
|
|
|
|
let rssi = RSSI.intValue
|
|
if rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
|
|
self.rssi = rssi
|
|
}
|
|
|
|
NotificationCenter.default.post(name: .peripheralDidUpdateRssi, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
|
}
|
|
}
|
|
|
|
// MARK: - Custom Notifications
|
|
extension Notification.Name {
|
|
private static let kPrefix = Bundle.main.bundleIdentifier!
|
|
static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
|
static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
|
static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
|
}
|