From fdef4b0a09917304c7755b1ecc90e713c860604f Mon Sep 17 00:00:00 2001 From: NickKibish Date: Sun, 5 Nov 2023 23:21:19 +0000 Subject: [PATCH] Copied files from native CoreBluetooth version to CoreBluetoothMock --- Sources/iOS-BLE-Library-Mock/Alias.swift | 87 +++-- .../CentralManager/CentralManager.swift | 64 +-- .../Peripheral/Peripheral.swift | 368 +++++++++--------- .../ReactivePeripheralDelegate.swift | 78 ++-- .../Utilities/Logger.swift | 4 +- .../Publishers/Publishers+Bluetooth.swift | 10 +- .../Utilities/Queue.swift | 62 +-- 7 files changed, 347 insertions(+), 326 deletions(-) diff --git a/Sources/iOS-BLE-Library-Mock/Alias.swift b/Sources/iOS-BLE-Library-Mock/Alias.swift index b04c7c5..3f8811d 100644 --- a/Sources/iOS-BLE-Library-Mock/Alias.swift +++ b/Sources/iOS-BLE-Library-Mock/Alias.swift @@ -38,50 +38,59 @@ import CoreBluetoothMock // disabled for Xcode 12.5 beta //typealias CBPeer = CBMPeer //typealias CBAttribute = CBMAttribute -public typealias CBCentralManagerFactory = CBMCentralManagerFactory -public typealias CBUUID = CBMUUID -public typealias CBError = CBMError -public typealias CBATTError = CBMATTError -public typealias CBManagerState = CBMManagerState -public typealias CBPeripheralState = CBMPeripheralState -public typealias CBCentralManager = CBMCentralManager -public typealias CBCentralManagerDelegate = CBMCentralManagerDelegate -public typealias CBPeripheral = CBMPeripheral -public typealias CBPeripheralDelegate = CBMPeripheralDelegate -public typealias CBService = CBMService -public typealias CBCharacteristic = CBMCharacteristic -public typealias CBCharacteristicWriteType = CBMCharacteristicWriteType -public typealias CBCharacteristicProperties = CBMCharacteristicProperties -public typealias CBDescriptor = CBMDescriptor -public typealias CBConnectionEvent = CBMConnectionEvent +public typealias CBCentralManagerFactory = CBMCentralManagerFactory +public typealias CBUUID = CBMUUID +public typealias CBError = CBMError +public typealias CBATTError = CBMATTError +public typealias CBManagerState = CBMManagerState +public typealias CBPeripheralState = CBMPeripheralState +public typealias CBCentralManager = CBMCentralManager +public typealias CBCentralManagerDelegate = CBMCentralManagerDelegate +public typealias CBPeripheral = CBMPeripheral +public typealias CBPeripheralDelegate = CBMPeripheralDelegate +public typealias CBService = CBMService +public typealias CBCharacteristic = CBMCharacteristic +public typealias CBCharacteristicWriteType = CBMCharacteristicWriteType +public typealias CBCharacteristicProperties = CBMCharacteristicProperties +public typealias CBDescriptor = CBMDescriptor +public typealias CBConnectionEvent = CBMConnectionEvent public typealias CBConnectionEventMatchingOption = CBMConnectionEventMatchingOption @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *) -public typealias CBL2CAPPSM = CBML2CAPPSM +public typealias CBL2CAPPSM = CBML2CAPPSM @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *) -public typealias CBL2CAPChannel = CBML2CAPChannel +public typealias CBL2CAPChannel = CBML2CAPChannel -public let CBCentralManagerScanOptionAllowDuplicatesKey = CBMCentralManagerScanOptionAllowDuplicatesKey -public let CBCentralManagerOptionShowPowerAlertKey = CBMCentralManagerOptionShowPowerAlertKey -public let CBCentralManagerOptionRestoreIdentifierKey = CBMCentralManagerOptionRestoreIdentifierKey -public let CBCentralManagerScanOptionSolicitedServiceUUIDsKey = CBMCentralManagerScanOptionSolicitedServiceUUIDsKey -public let CBConnectPeripheralOptionStartDelayKey = CBMConnectPeripheralOptionStartDelayKey +public let CBCentralManagerScanOptionAllowDuplicatesKey = + CBMCentralManagerScanOptionAllowDuplicatesKey +public let CBCentralManagerOptionShowPowerAlertKey = CBMCentralManagerOptionShowPowerAlertKey +public let CBCentralManagerOptionRestoreIdentifierKey = CBMCentralManagerOptionRestoreIdentifierKey +public let CBCentralManagerScanOptionSolicitedServiceUUIDsKey = + CBMCentralManagerScanOptionSolicitedServiceUUIDsKey +public let CBConnectPeripheralOptionStartDelayKey = CBMConnectPeripheralOptionStartDelayKey #if !os(macOS) -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public let CBConnectPeripheralOptionRequiresANCS = CBMConnectPeripheralOptionRequiresANCS + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + public let CBConnectPeripheralOptionRequiresANCS = CBMConnectPeripheralOptionRequiresANCS #endif -public let CBCentralManagerRestoredStatePeripheralsKey = CBMCentralManagerRestoredStatePeripheralsKey -public let CBCentralManagerRestoredStateScanServicesKey = CBMCentralManagerRestoredStateScanServicesKey -public let CBCentralManagerRestoredStateScanOptionsKey = CBMCentralManagerRestoredStateScanOptionsKey +public let CBCentralManagerRestoredStatePeripheralsKey = + CBMCentralManagerRestoredStatePeripheralsKey +public let CBCentralManagerRestoredStateScanServicesKey = + CBMCentralManagerRestoredStateScanServicesKey +public let CBCentralManagerRestoredStateScanOptionsKey = + CBMCentralManagerRestoredStateScanOptionsKey -public let CBAdvertisementDataLocalNameKey = CBMAdvertisementDataLocalNameKey -public let CBAdvertisementDataServiceUUIDsKey = CBMAdvertisementDataServiceUUIDsKey -public let CBAdvertisementDataIsConnectable = CBMAdvertisementDataIsConnectable -public let CBAdvertisementDataTxPowerLevelKey = CBMAdvertisementDataTxPowerLevelKey -public let CBAdvertisementDataServiceDataKey = CBMAdvertisementDataServiceDataKey -public let CBAdvertisementDataManufacturerDataKey = CBMAdvertisementDataManufacturerDataKey -public let CBAdvertisementDataOverflowServiceUUIDsKey = CBMAdvertisementDataOverflowServiceUUIDsKey -public let CBAdvertisementDataSolicitedServiceUUIDsKey = CBMAdvertisementDataSolicitedServiceUUIDsKey +public let CBAdvertisementDataLocalNameKey = CBMAdvertisementDataLocalNameKey +public let CBAdvertisementDataServiceUUIDsKey = CBMAdvertisementDataServiceUUIDsKey +public let CBAdvertisementDataIsConnectable = CBMAdvertisementDataIsConnectable +public let CBAdvertisementDataTxPowerLevelKey = CBMAdvertisementDataTxPowerLevelKey +public let CBAdvertisementDataServiceDataKey = CBMAdvertisementDataServiceDataKey +public let CBAdvertisementDataManufacturerDataKey = CBMAdvertisementDataManufacturerDataKey +public let CBAdvertisementDataOverflowServiceUUIDsKey = CBMAdvertisementDataOverflowServiceUUIDsKey +public let CBAdvertisementDataSolicitedServiceUUIDsKey = + CBMAdvertisementDataSolicitedServiceUUIDsKey -public let CBConnectPeripheralOptionNotifyOnConnectionKey = CBMConnectPeripheralOptionNotifyOnConnectionKey -public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = CBMConnectPeripheralOptionNotifyOnDisconnectionKey -public let CBConnectPeripheralOptionNotifyOnNotificationKey = CBMConnectPeripheralOptionNotifyOnNotificationKey \ No newline at end of file +public let CBConnectPeripheralOptionNotifyOnConnectionKey = + CBMConnectPeripheralOptionNotifyOnConnectionKey +public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = + CBMConnectPeripheralOptionNotifyOnDisconnectionKey +public let CBConnectPeripheralOptionNotifyOnNotificationKey = + CBMConnectPeripheralOptionNotifyOnNotificationKey diff --git a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift index 87dd282..185f6e4 100644 --- a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift @@ -18,7 +18,8 @@ extension CentralManager { public var localizedDescription: String { switch self { case .wrongManager: - return "Incorrect manager instance provided. Delegate must be of type ReactiveCentralManagerDelegate." + return + "Incorrect manager instance provided. Delegate must be of type ReactiveCentralManagerDelegate." case .badState(let state): return "Bad state: \(state)." case .unknownError: @@ -54,7 +55,7 @@ private class Observer: NSObject { } /// A Custom Central Manager class. -/// +/// /// It wraps the standard CBCentralManager and has similar API. However, instead of using delegate, it uses publishers, thus bringing the reactive programming paradigm to the CoreBluetooth framework. public class CentralManager { private let isScanningSubject = CurrentValueSubject(false) @@ -63,7 +64,7 @@ public class CentralManager { /// The underlying CBCentralManager instance. public let centralManager: CBCentralManager - + /// The reactive delegate for the ``centralManager``. public let centralManagerDelegate: ReactiveCentralManagerDelegate @@ -73,10 +74,12 @@ public class CentralManager { /// - queue: The queue to perform operations on. Default is the main queue. public init( centralManagerDelegate: ReactiveCentralManagerDelegate = - ReactiveCentralManagerDelegate(), queue: DispatchQueue = .main, options: [String : Any]? = nil + ReactiveCentralManagerDelegate(), queue: DispatchQueue = .main, + options: [String: Any]? = nil ) { self.centralManagerDelegate = centralManagerDelegate -self.centralManager = CBMCentralManagerFactory.instance(delegate: centralManagerDelegate, queue: queue) + self.centralManager = CBMCentralManagerFactory.instance( + delegate: centralManagerDelegate, queue: queue) observer.setup() } @@ -109,17 +112,17 @@ extension CentralManager { /// If the peripheral was disconnected successfully, the publisher finishes without error. /// If the connection was unsuccessful or disconnection returns an error (e.g., peripheral disconnected unexpectedly), /// the publisher finishes with an error. - /// - /// Use ``CentralManager/connect(_:options:)`` to connect to a peripheral. - /// The returned publisher will emit the connected peripheral or an error if the connection fails. - /// The publisher will not complete until the peripheral is disconnected. - /// If the connection fails, or the peripheral is unexpectedly disconnected, the publisher will fail with an error. - /// - /// ```swift - /// centralManager.connect(peripheral) - /// .sink { completion in - /// switch completion { - /// case .finished: + /// + /// Use ``CentralManager/connect(_:options:)`` to connect to a peripheral. + /// The returned publisher will emit the connected peripheral or an error if the connection fails. + /// The publisher will not complete until the peripheral is disconnected. + /// If the connection fails, or the peripheral is unexpectedly disconnected, the publisher will fail with an error. + /// + /// ```swift + /// centralManager.connect(peripheral) + /// .sink { completion in + /// switch completion { + /// case .finished: /// print("Peripheral disconnected successfully") /// case .failure(let error): /// print("Error: \(error)") @@ -152,15 +155,16 @@ extension CentralManager { .bluetooth { self.centralManager.connect(peripheral, options: options) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Cancels the connection with the specified peripheral. /// - Parameter peripheral: The peripheral to disconnect from. /// - Returns: A publisher that emits the disconnected peripheral. - public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher - { + public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher< + CBPeripheral, Error + > { return self.disconnectedPeripheralsChannel .tryFilter { r in guard r.0.identifier == peripheral.identifier else { @@ -175,17 +179,17 @@ extension CentralManager { } .map { $0.0 } .first() - .bluetooth { - self.centralManager.cancelPeripheralConnection(peripheral) - } - .autoconnect() - .eraseToAnyPublisher() + .bluetooth { + self.centralManager.cancelPeripheralConnection(peripheral) + } + .autoconnect() + .eraseToAnyPublisher() } } // MARK: Retrieving Lists of Peripherals extension CentralManager { - #warning("check `connect` method") + #warning("check `connect` method") /// Returns a list of the peripherals connected to the system whose /// services match a given set of criteria. /// @@ -218,9 +222,9 @@ extension CentralManager { extension CentralManager { #warning("Question: Should we throw an error if the scan is already running?") /// Initiates a scan for peripherals with the specified services. - /// + /// /// Calling this method stops an ongoing scan if it is already running and finishes the publisher returned by ``scanForPeripherals(withServices:)``. - /// + /// /// - Parameter services: The services to scan for. /// - Returns: A publisher that emits scan results or an error. public func scanForPeripherals(withServices services: [CBUUID]?) @@ -250,8 +254,8 @@ extension CentralManager { .bluetooth { self.centralManager.scanForPeripherals(withServices: services) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Stops an ongoing scan for peripherals. diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index fa2b670..bab0af6 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -7,7 +7,6 @@ import Combine import CoreBluetooth - import CoreBluetoothMock import Foundation @@ -33,42 +32,45 @@ private class NativeObserver: Observer { override func setup() { observation = peripheral.observe(\.state, options: [.new]) { [weak self] _, change in - // TODO: Check threads - guard let self else { return } - self.publisher.send(self.peripheral.state) + // TODO: Check threads + guard let self else { return } + self.publisher.send(self.peripheral.state) } } } private class MockObserver: Observer { - @objc private var peripheral: CBMPeripheralMock - - private weak var publisher: CurrentValueSubject! - private var observation: NSKeyValueObservation? + @objc private var peripheral: CBMPeripheralMock - init(peripheral: CBMPeripheralMock, publisher: CurrentValueSubject) { - self.peripheral = peripheral - self.publisher = publisher - super.init() - } + private weak var publisher: CurrentValueSubject! + private var observation: NSKeyValueObservation? - override func setup() { - observation = peripheral.observe(\.state, options: [.new]) { [weak self] _, change in - #warning("queue can be not only main") - DispatchQueue.main.async { - guard let self else { return } - self.publisher.send(self.peripheral.state) - } - } - } - } + init( + peripheral: CBMPeripheralMock, + publisher: CurrentValueSubject + ) { + self.peripheral = peripheral + self.publisher = publisher + super.init() + } + override func setup() { + observation = peripheral.observe(\.state, options: [.new]) { + [weak self] _, change in + #warning("queue can be not only main") + DispatchQueue.main.async { + guard let self else { return } + self.publisher.send(self.peripheral.state) + } + } + } +} public class Peripheral { - private var serviceDiscoveryQueue = Queue() - - let l = L(category: #file) - + private var serviceDiscoveryQueue = Queue() + + let l = L(category: #file) + /// I'm Errr from Omicron Persei 8 public enum Err: Error { case badDelegate @@ -77,10 +79,10 @@ public class Peripheral { /// The underlying CBPeripheral instance. public let peripheral: CBPeripheral - // MARK: Identifying a Peripheralin page link - /// The name of the peripheral. - public var name: String? { peripheral.name } - + // MARK: Identifying a Peripheralin page link + /// The name of the peripheral. + public var name: String? { peripheral.name } + /// The delegate for handling peripheral events. public let peripheralDelegate: ReactivePeripheralDelegate @@ -100,22 +102,22 @@ public class Peripheral { // TODO: Why don't we use default delegate? /// Initializes a Peripheral instance. - /// - /// - Parameters: - /// - peripheral: The CBPeripheral to manage. - /// - delegate: The delegate for handling peripheral events. + /// + /// - Parameters: + /// - peripheral: The CBPeripheral to manage. + /// - delegate: The delegate for handling peripheral events. public init(peripheral: CBPeripheral, delegate: ReactivePeripheralDelegate) { self.peripheral = peripheral self.peripheralDelegate = delegate peripheral.delegate = delegate -if let p = peripheral as? CBMPeripheralNative { - observer = NativeObserver(peripheral: p.peripheral, publisher: stateSubject) - observer.setup() - } else if let p = peripheral as? CBMPeripheralMock { - observer = MockObserver(peripheral: p, publisher: stateSubject) - observer.setup() - } + if let p = peripheral as? CBMPeripheralNative { + observer = NativeObserver(peripheral: p.peripheral, publisher: stateSubject) + observer.setup() + } else if let p = peripheral as? CBMPeripheralMock { + observer = MockObserver(peripheral: p, publisher: stateSubject) + observer.setup() + } } } @@ -129,157 +131,159 @@ extension Peripheral { // MARK: - Discovering Servicesin page link extension Peripheral { - /// Discover services for the peripheral. - /// - /// - Parameter serviceUUIDs: An optional array of service UUIDs to filter the discovery results. If nil, all services will be discovered. - /// - Returns: A publisher emitting discovered services or an error. - public func discoverServices(serviceUUIDs: [CBUUID]?) - -> AnyPublisher<[CBService], Error> - { - let id = UUID() - - let allServices = peripheralDelegate.discoveredServicesSubject - .first(where: { $0.id == id } ) - .tryCompactMap { result throws -> [CBService]? in - if let e = result.error { - throw e - } else { - return result.value - } - } - .first() - - return allServices.bluetooth { - self.peripheralDelegate.discoveredServicesQueue.enqueue(id) - self.peripheral.discoverServices(serviceUUIDs) - } - .autoconnect() - .eraseToAnyPublisher() - } - - /// Discovers the specified included services of a previously-discovered service. - public func discoverIncludedServices(_ includedServiceUUIDs: [CBUUID]?, for: CBService) -> AnyPublisher<[CBService], Error> { - fatalError() - } - - /// A list of a peripheral’s discovered services. - public var services: [CBService]? { - peripheral.services - } + /// Discover services for the peripheral. + /// + /// - Parameter serviceUUIDs: An optional array of service UUIDs to filter the discovery results. If nil, all services will be discovered. + /// - Returns: A publisher emitting discovered services or an error. + public func discoverServices(serviceUUIDs: [CBUUID]?) + -> AnyPublisher<[CBService], Error> + { + let id = UUID() + + let allServices = peripheralDelegate.discoveredServicesSubject + .first(where: { $0.id == id }) + .tryCompactMap { result throws -> [CBService]? in + if let e = result.error { + throw e + } else { + return result.value + } + } + .first() + + return allServices.bluetooth { + self.peripheralDelegate.discoveredServicesQueue.enqueue(id) + self.peripheral.discoverServices(serviceUUIDs) + } + .autoconnect() + .eraseToAnyPublisher() + } + + /// Discovers the specified included services of a previously-discovered service. + public func discoverIncludedServices(_ includedServiceUUIDs: [CBUUID]?, for: CBService) + -> AnyPublisher<[CBService], Error> + { + fatalError() + } + + /// A list of a peripheral’s discovered services. + public var services: [CBService]? { + peripheral.services + } } //MARK: - Discovering Characteristics and Descriptorsin page link extension Peripheral { /// Discover characteristics for a given service. - /// - /// - Parameters: - /// - characteristicUUIDs: An optional array of characteristic UUIDs to filter the discovery results. If nil, all characteristics will be discovered. - /// - service: The service for which to discover characteristics. - /// - Returns: A publisher emitting discovered characteristics or an error. + /// + /// - Parameters: + /// - characteristicUUIDs: An optional array of characteristic UUIDs to filter the discovery results. If nil, all characteristics will be discovered. + /// - service: The service for which to discover characteristics. + /// - Returns: A publisher emitting discovered characteristics or an error. public func discoverCharacteristics( _ characteristicUUIDs: [CBUUID]?, for service: CBService ) -> AnyPublisher<[CBCharacteristic], Error> { - let id = UUID() - + let id = UUID() + let allCharacteristics = peripheralDelegate.discoveredCharacteristicsSubject - .filter { - $0.value.0.uuid == service.uuid - } - .first(where: { $0.id == id } ) + .filter { + $0.value.0.uuid == service.uuid + } + .first(where: { $0.id == id }) .tryCompactMap { result throws -> [CBCharacteristic]? in - if let e = result.error { + if let e = result.error { throw e } else { - return result.value.1 + return result.value.1 } } - .first() + .first() return allCharacteristics.bluetooth { - self.peripheralDelegate.discoveredCharacteristicsQueue.enqueue(id) + self.peripheralDelegate.discoveredCharacteristicsQueue.enqueue(id) self.peripheral.discoverCharacteristics(characteristicUUIDs, for: service) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Discover descriptors for a given characteristic. - /// - /// - Parameter characteristic: The characteristic for which to discover descriptors. - /// - Returns: A publisher emitting discovered descriptors or an error. + /// + /// - Parameter characteristic: The characteristic for which to discover descriptors. + /// - Returns: A publisher emitting discovered descriptors or an error. public func discoverDescriptors(for characteristic: CBCharacteristic) -> AnyPublisher<[CBDescriptor], Error> { - let id = UUID() - + let id = UUID() + return peripheralDelegate.discoveredDescriptorsSubject .filter { - $0.value.0.uuid == characteristic.uuid + $0.value.0.uuid == characteristic.uuid } - .first(where: { $0.id == id }) + .first(where: { $0.id == id }) .tryCompactMap { result throws -> [CBDescriptor]? in - if let e = result.error { + if let e = result.error { throw e } else { - return result.value.1 + return result.value.1 } } - .first() + .first() .bluetooth { - self.peripheralDelegate.discoveredDescriptorsQueue.enqueue(id) + self.peripheralDelegate.discoveredDescriptorsQueue.enqueue(id) self.peripheral.discoverDescriptors(for: characteristic) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } } // MARK: - Reading Characteristic and Descriptor Values extension Peripheral { - /// Read the value of a characteristic. - /// - /// - Parameter characteristic: The characteristic to read from. - /// - Returns: A future emitting the read data or an error. - public func readValue(for characteristic: CBCharacteristic) -> Future { - return reader.readValue(from: characteristic) - } - - /// Listen for updates to the value of a characteristic. - /// - /// - Parameter characteristic: The characteristic to monitor for updates. - /// - Returns: A publisher emitting characteristic values or an error. - public func listenValues(for characteristic: CBCharacteristic) -> AnyPublisher - { - return peripheralDelegate.updatedCharacteristicValuesSubject - .filter { $0.0.uuid == characteristic.uuid } - .tryCompactMap { (ch, err) in - if let err { - throw err - } - - return ch.value - } - .eraseToAnyPublisher() - } - - /// Read the value of a descriptor. - /// - /// - Parameter descriptor: The descriptor to read from. - /// - Returns: A future emitting the read data or an error. - public func readValue(for descriptor: CBDescriptor) -> Future { - fatalError() - } + /// Read the value of a characteristic. + /// + /// - Parameter characteristic: The characteristic to read from. + /// - Returns: A future emitting the read data or an error. + public func readValue(for characteristic: CBCharacteristic) -> Future { + return reader.readValue(from: characteristic) + } + + /// Listen for updates to the value of a characteristic. + /// + /// - Parameter characteristic: The characteristic to monitor for updates. + /// - Returns: A publisher emitting characteristic values or an error. + public func listenValues(for characteristic: CBCharacteristic) -> AnyPublisher + { + return peripheralDelegate.updatedCharacteristicValuesSubject + .filter { $0.0.uuid == characteristic.uuid } + .tryCompactMap { (ch, err) in + if let err { + throw err + } + + return ch.value + } + .eraseToAnyPublisher() + } + + /// Read the value of a descriptor. + /// + /// - Parameter descriptor: The descriptor to read from. + /// - Returns: A future emitting the read data or an error. + public func readValue(for descriptor: CBDescriptor) -> Future { + fatalError() + } } // MARK: - Writing Characteristic and Descriptor Values extension Peripheral { /// Write data to a characteristic and wait for a response. - /// - /// - Parameters: - /// - data: The data to write. - /// - characteristic: The characteristic to write to. - /// - Returns: A publisher indicating success or an error. + /// + /// - Parameters: + /// - data: The data to write. + /// - characteristic: The characteristic to write to. + /// - Returns: A publisher indicating success or an error. public func writeValueWithResponse(_ data: Data, for characteristic: CBCharacteristic) -> AnyPublisher { @@ -296,24 +300,24 @@ extension Peripheral { self.peripheral.writeValue( data, for: characteristic, type: .withResponse) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Write data to a characteristic without waiting for a response. - /// - /// - Parameters: - /// - data: The data to write. - /// - characteristic: The characteristic to write to. + /// + /// - Parameters: + /// - data: The data to write. + /// - characteristic: The characteristic to write to. public func writeValueWithoutResponse(_ data: Data, for characteristic: CBCharacteristic) { peripheral.writeValue(data, for: characteristic, type: .withoutResponse) } /// Write data to a descriptor. - /// - /// - Parameters: - /// - data: The data to write. - /// - descriptor: The descriptor to write to. + /// + /// - Parameters: + /// - data: The data to write. + /// - descriptor: The descriptor to write to. public func writeValue(_ data: Data, for descriptor: CBDescriptor) { fatalError() } @@ -322,18 +326,18 @@ extension Peripheral { // MARK: - Setting Notifications for a Characteristic’s Value extension Peripheral { /// Set notification state for a characteristic. - /// - /// - Parameters: - /// - isEnabled: Whether notifications should be enabled or disabled. - /// - characteristic: The characteristic for which to set the notification state. - /// - Returns: A publisher indicating success or an error. + /// + /// - Parameters: + /// - isEnabled: Whether notifications should be enabled or disabled. + /// - characteristic: The characteristic for which to set the notification state. + /// - Returns: A publisher indicating success or an error. public func setNotifyValue(_ isEnabled: Bool, for characteristic: CBCharacteristic) -> AnyPublisher { if characteristic.isNotifying == isEnabled { return Just(isEnabled) .setFailureType(to: Error.self) - .eraseToAnyPublisher() + .eraseToAnyPublisher() } return peripheralDelegate.notificationStateSubject @@ -347,28 +351,28 @@ extension Peripheral { .bluetooth { self.peripheral.setNotifyValue(isEnabled, for: characteristic) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } } // MARK: - Accessing a Peripheral’s Signal Strengthin page link extension Peripheral { - /// Retrieves the current RSSI value for the peripheral while connected to the central manager. - public func readRSSI() -> AnyPublisher { - peripheralDelegate.readRSSISubject - .tryMap { rssi in - if let error = rssi.1 { - throw error - } else { - return rssi.0 - } - } - .first() - .bluetooth { - self.peripheral.readRSSI() - } - .autoconnect() - .eraseToAnyPublisher() - } + /// Retrieves the current RSSI value for the peripheral while connected to the central manager. + public func readRSSI() -> AnyPublisher { + peripheralDelegate.readRSSISubject + .tryMap { rssi in + if let error = rssi.1 { + throw error + } else { + return rssi.0 + } + } + .first() + .bluetooth { + self.peripheral.readRSSI() + } + .autoconnect() + .eraseToAnyPublisher() + } } diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift index 23f4ec9..885d58a 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift @@ -10,37 +10,37 @@ import CoreBluetoothMock import Foundation struct BluetoothOperationResult { - let value: T - let error: Error? - let id: UUID + let value: T + let error: Error? + let id: UUID } public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { let l = L(category: #file) - - typealias NonFailureSubject = PassthroughSubject - - var discoveredServicesQueue = Queue() - var discoveredCharacteristicsQueue = Queue() - var discoveredDescriptorsQueue = Queue() - - // MARK: Discovering Services + + typealias NonFailureSubject = PassthroughSubject + + var discoveredServicesQueue = Queue() + var discoveredCharacteristicsQueue = Queue() + var discoveredDescriptorsQueue = Queue() + + // MARK: Discovering Services let discoveredServicesSubject = NonFailureSubject< - BluetoothOperationResult<[CBService]?> - >() - - /* + BluetoothOperationResult<[CBService]?> + >() + + /* let discoveredIncludedServicesSubject = PassthroughSubject< BluetoothOperationResult<(CBService, [CBService]?)>, Never >() */ - - // MARK: Discovering Characteristics and their Descriptors + + // MARK: Discovering Characteristics and their Descriptors let discoveredCharacteristicsSubject = NonFailureSubject< - BluetoothOperationResult<(CBService, [CBCharacteristic]?)> + BluetoothOperationResult<(CBService, [CBCharacteristic]?)> >() let discoveredDescriptorsSubject = NonFailureSubject< - BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)> + BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)> >() // MARK: Retrieving Characteristic and Descriptor Values @@ -65,18 +65,19 @@ public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { // MARK: Monitoring Changes to a Peripheral’s Name or Services let updateNameSubject = PassthroughSubject() - let modifyServicesSubject = PassthroughSubject<[CBService], Never>() - + let modifyServicesSubject = PassthroughSubject<[CBService], Never>() + // MARK: Discovering Services public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { - let operationId = discoveredServicesQueue.dequeue()! - let result = BluetoothOperationResult<[CBService]?>(value: peripheral.services, error: error, id: operationId) - + let operationId = discoveredServicesQueue.dequeue()! + let result = BluetoothOperationResult<[CBService]?>( + value: peripheral.services, error: error, id: operationId) + discoveredServicesSubject.send(result) } - /* + /* public func peripheral( _ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error? @@ -90,10 +91,11 @@ public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { public func peripheral( _ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error? - ) { - let operationId = discoveredCharacteristicsQueue.dequeue()! - let result = BluetoothOperationResult<(CBService, [CBCharacteristic]?)>(value: (service, service.characteristics), error: error, id: operationId) - + ) { + let operationId = discoveredCharacteristicsQueue.dequeue()! + let result = BluetoothOperationResult<(CBService, [CBCharacteristic]?)>( + value: (service, service.characteristics), error: error, id: operationId) + discoveredCharacteristicsSubject.send(result) } @@ -101,9 +103,11 @@ public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { _ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error? ) { - let operationId = discoveredDescriptorsQueue.dequeue()! - let result = BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)>(value: (characteristic, characteristic.descriptors), error: error, id: operationId) - + let operationId = discoveredDescriptorsQueue.dequeue()! + let result = BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)>( + value: (characteristic, characteristic.descriptors), error: error, + id: operationId) + discoveredDescriptorsSubject.send(result) } @@ -154,12 +158,12 @@ public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { // MARK: Retrieving a Peripheral’s RSSI Data - public let readRSSISubject = PassthroughSubject<(NSNumber, Error?), Never>() - + public let readRSSISubject = PassthroughSubject<(NSNumber, Error?), Never>() + public func peripheral( _ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error? ) { - readRSSISubject.send((RSSI, error)) + readRSSISubject.send((RSSI, error)) } // MARK: Monitoring Changes to a Peripheral’s Name or Services @@ -171,11 +175,11 @@ public class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { public func peripheral( _ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService] ) { - modifyServicesSubject.send(invalidatedServices) + modifyServicesSubject.send(invalidatedServices) } // MARK: Monitoring L2CAP Channels -/* + /* public func peripheral( _ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error? ) { diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift index d5bf8d2..9def274 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift @@ -10,7 +10,7 @@ import os struct L { @inline(__always) - static let enabled: Bool = false + static let enabled: Bool = false let subsystem: String let category: String @@ -19,7 +19,7 @@ struct L { init( subsystem: String = "com.nordicsemi.ios_ble_library", category: String, - enabled: Bool = Self.enabled + enabled: Bool = Self.enabled ) { self.subsystem = subsystem self.category = category diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift index bb41d71..6c86d86 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift @@ -17,16 +17,16 @@ extension Publisher { } extension Publishers { - - /** + + /** A publisher that is used for most of the Bluetooth operations. - + # Overview This publisher conforms to the `ConnectablePublisher` protocol because most of the Bluetooth operations have to be set up before they can be used. - + It means that the publisher will not emit any values until it is connected. The connection is established by calling the `connect()` or `autoconnect()` methods. To learn more about the `ConnectablePublisher` protocol, see [Apple's documentation](https://developer.apple.com/documentation/combine/connectablepublisher). - + ```swift let publisher = centralManager.scanForPeripherals(withServices: nil) .autoconnect() diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift index fdd7bd5..ab452bf 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Nick Kibysh on 03/11/2023. // @@ -8,38 +8,38 @@ import Foundation struct Queue { - private var queue = [T]() - private let accessQueue = DispatchQueue(label: "com.example.threadSafeQueue") + private var queue = [T]() + private let accessQueue = DispatchQueue(label: "com.example.threadSafeQueue") - mutating func enqueue(_ element: T) { - accessQueue.sync { - queue.append(element) - } - } + mutating func enqueue(_ element: T) { + accessQueue.sync { + queue.append(element) + } + } - mutating func dequeue() -> T? { - var element: T? - accessQueue.sync { - if !queue.isEmpty { - element = queue.removeFirst() - } - } - return element - } + mutating func dequeue() -> T? { + var element: T? + accessQueue.sync { + if !queue.isEmpty { + element = queue.removeFirst() + } + } + return element + } - var isEmpty: Bool { - var empty = false - accessQueue.sync { - empty = queue.isEmpty - } - return empty - } + var isEmpty: Bool { + var empty = false + accessQueue.sync { + empty = queue.isEmpty + } + return empty + } - var count: Int { - var queueCount = 0 - accessQueue.sync { - queueCount = queue.count - } - return queueCount - } + var count: Int { + var queueCount = 0 + accessQueue.sync { + queueCount = queue.count + } + return queueCount + } }