From a14322b9253cfe8a9fe9cfb0343543018eb652e6 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Wed, 17 Jan 2024 13:45:44 +0100 Subject: [PATCH 1/7] fixed unit tests --- Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift | 4 ++-- Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift index 437601a..616f6e9 100644 --- a/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift +++ b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift @@ -144,7 +144,7 @@ final class CentralManagerTests: XCTestCase { func testConnect() async throws { let connectionPeripheral = try await central.scanForPeripherals(withServices: nil) - .value + .firstValue .peripheral let connectionExpectation = XCTestExpectation(description: "Connection expectation") @@ -180,7 +180,7 @@ final class CentralManagerTests: XCTestCase { func testDisconnectFromPeripheral() async throws { let connectionPeripheral = try await central.scanForPeripherals(withServices: nil) - .value + .firstValue .peripheral let connectionExpectation = XCTestExpectation(description: "Connection expectation") diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift index b5c97e1..4449110 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift @@ -69,7 +69,7 @@ final class PeripheralMultitaskingTests: XCTestCase { let p = try await central.scanForPeripherals(withServices: nil) .flatMap { self.central.connect($0.peripheral) } .map { Peripheral(peripheral: $0, delegate: ReactivePeripheralDelegate()) } - .value + .firstValue let batteryExp = expectation(description: "Battery Service Expectation") let hrExp = expectation(description: "Heart Rate Service Expectation") From 4c56444c316696890966dcf8001c22ea79abe324 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Mon, 5 Feb 2024 15:21:42 +0100 Subject: [PATCH 2/7] test(Peripheral): Discover characteristics added test for discovering characteristics and descriptors. Fixed test for discovering services --- .../PeripheralMultitaskingTests.swift | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift index 4449110..473a518 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift @@ -17,7 +17,15 @@ private extension CBMUUID { static let heartRateMonitorService = CBUUID(string: "180D") static let deviceInformationService = CBUUID(string: "180A") - static var all: [CBMUUID] = [.batteryService, .runningSpeedCadenceService, .heartRateMonitorService, .deviceInformationService] + static var allServices: [CBMUUID] = [ + .batteryService, + .runningSpeedCadenceService, + .heartRateMonitorService, + .deviceInformationService + ] + + static let batteryLevel = CBUUID(string: "2A19") + static let presentationFormat = CBUUID(string: "2904") } class MockPeripheral: CBMPeripheralSpecDelegate { @@ -35,7 +43,18 @@ class MockPeripheral: CBMPeripheralSpecDelegate { ) .connectable( name: "Running Sensor", - services: CBMUUID.all.map { CBMServiceMock(type: $0, primary: true) }, + services: CBMUUID.allServices.map { + if $0 == .batteryService { + return CBMServiceMock( + type: $0, + primary: true, + characteristics: [ + CBMCharacteristicMock(type: .batteryLevel, properties: .read, descriptors: CBMDescriptorMock(type: .presentationFormat)) + ]) + } else { + return CBMServiceMock(type: $0, primary: true) + } + }, delegate: self ) .build() @@ -43,6 +62,10 @@ class MockPeripheral: CBMPeripheralSpecDelegate { func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveServiceDiscoveryRequest serviceUUIDs: [CBMUUID]?) -> Result { return .success(()) } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveCharacteristicsDiscoveryRequest characteristicUUIDs: [CBMUUID]?, for service: CBMServiceMock) -> Result { + return .success(()) + } } final class PeripheralMultitaskingTests: XCTestCase { @@ -86,7 +109,7 @@ final class PeripheralMultitaskingTests: XCTestCase { } .store(in: &cancelables) - p.discoverServices(serviceUUIDs: [.deviceInformationService]) + p.discoverServices(serviceUUIDs: [.batteryService]) .sink { completion in if case .failure = completion { XCTFail("Should not fail") @@ -100,4 +123,29 @@ final class PeripheralMultitaskingTests: XCTestCase { await fulfillment(of: [batteryExp, hrExp], timeout: 4) } + + func testDiscoverCharacteristics() async throws { + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: ReactivePeripheralDelegate()) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + + let characteristicExp = expectation(description: "Characteristic expectation") + let descriptorExp = expectation(description: "Descriptor expectation") + + Task { + let ch = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + characteristicExp.fulfill() + + let desc = try await p.discoverDescriptors(for: ch).firstValue.first! + descriptorExp.fulfill() + + XCTAssert(ch.uuid == CBUUID.batteryLevel) + XCTAssert(desc.uuid == CBUUID.presentationFormat) + } + + await fulfillment(of: [characteristicExp, descriptorExp], timeout: 4) + } } From 25394367b0f8f19d0cdafa9e3513b7b10fe1ad45 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Fri, 23 Feb 2024 22:04:26 +0100 Subject: [PATCH 3/7] test(peripheral): Peripheral Tests Added tests: - readCharacteristic - writeWithoutResponse Added chennals to Perapheral Added additional subject to notify that peripheral is ready to send wtrite without response --- .../Peripheral/Peripheral.swift | 120 +++++++++++- .../ReactivePeripheralDelegate.swift | 7 +- .../Peripheral/Peripheral.swift | 120 +++++++++++- .../ReactivePeripheralDelegate.swift | 7 +- .../PeripheralMultitaskingTests.swift | 4 +- .../PeripheralReadWriteDescriptorTests.swift | 173 ++++++++++++++++++ 6 files changed, 421 insertions(+), 10 deletions(-) create mode 100644 Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index ceadfc6..472b193 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -282,7 +282,21 @@ extension Peripheral { /// - 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() + return Future.init { promis in + promis(.failure(Err.badDelegate)) + } + /* + return peripheralDelegate.updatedDescriptorValuesSubject + .filter { $0.0.uuid == descriptor.uuid } + .tryCompactMap { (desc, err) in + if let err { + throw err + } + + return desc.value + } + .eraseToAnyPublisher() + */ } } @@ -386,3 +400,107 @@ extension Peripheral { .eraseToAnyPublisher() } } + +// MARK: - Channels +extension Peripheral { + /// A publisher that emits the discovered services of the peripheral. + public var discoveredServicesChannel: AnyPublisher<[CBService]?, Error> { + peripheralDelegate.discoveredServicesSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered characteristics of a service. + public var discoveredCharacteristicsChannel: AnyPublisher<(CBService, [CBCharacteristic]?)?, Error> { + peripheralDelegate.discoveredCharacteristicsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered descriptors of a characteristic. + public var discoveredDescriptorsChannel: AnyPublisher<(CBCharacteristic, [CBDescriptor]?)?, Error> { + peripheralDelegate.discoveredDescriptorsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a characteristic. + public var updatedCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.updatedCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a descriptor. + public var updatedDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.updatedDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a characteristic. + public var writtenCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.writtenCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a descriptor. + public var writtenDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.writtenDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the notification state of a characteristic. + public var notificationStateChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.notificationStateSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the update name of a peripheral. + public var updateNameChannel: AnyPublisher { + peripheralDelegate.updateNameSubject + .eraseToAnyPublisher() + } + + public var modifyServices: AnyPublisher<[CBService], Never> { + peripheralDelegate.modifyServicesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the read RSSI value of a peripheral. + public var readRSSIChannel: AnyPublisher { + peripheralDelegate.readRSSISubject + .tryMap { rssi in + if let error = rssi.1 { + throw error + } else { + return rssi.0 + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the isReadyToSendWriteWithoutResponse value of a peripheral. + public var isReadyToSendWriteWithoutResponseChannel: AnyPublisher { + peripheralDelegate.isReadyToSendWriteWithoutResponseSubject + .first() + .eraseToAnyPublisher() + } + +} diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift index 27141b4..bae33bd 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift @@ -97,6 +97,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { let updatedDescriptorValuesSubject = PassthroughSubject< (CBDescriptor, Error?), Never >() + + let isReadyToSendWriteWithoutResponseSubject = PassthroughSubject() let writtenCharacteristicValuesSubject = PassthroughSubject< (CBCharacteristic, Error?), Never @@ -193,9 +195,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { } open func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { - l.i(#function) - fatalError() - } + isReadyToSendWriteWithoutResponseSubject.send(()) + } // MARK: Managing Notifications for a Characteristic’s Value diff --git a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift index 51c57e5..2068eef 100644 --- a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift @@ -301,7 +301,21 @@ extension Peripheral { /// - 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() + return Future.init { promis in + promis(.failure(Err.badDelegate)) + } + /* + return peripheralDelegate.updatedDescriptorValuesSubject + .filter { $0.0.uuid == descriptor.uuid } + .tryCompactMap { (desc, err) in + if let err { + throw err + } + + return desc.value + } + .eraseToAnyPublisher() + */ } } @@ -405,3 +419,107 @@ extension Peripheral { .eraseToAnyPublisher() } } + +// MARK: - Channels +extension Peripheral { + /// A publisher that emits the discovered services of the peripheral. + public var discoveredServicesChannel: AnyPublisher<[CBService]?, Error> { + peripheralDelegate.discoveredServicesSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered characteristics of a service. + public var discoveredCharacteristicsChannel: AnyPublisher<(CBService, [CBCharacteristic]?)?, Error> { + peripheralDelegate.discoveredCharacteristicsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered descriptors of a characteristic. + public var discoveredDescriptorsChannel: AnyPublisher<(CBCharacteristic, [CBDescriptor]?)?, Error> { + peripheralDelegate.discoveredDescriptorsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a characteristic. + public var updatedCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.updatedCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a descriptor. + public var updatedDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.updatedDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a characteristic. + public var writtenCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.writtenCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a descriptor. + public var writtenDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.writtenDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the notification state of a characteristic. + public var notificationStateChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.notificationStateSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the update name of a peripheral. + public var updateNameChannel: AnyPublisher { + peripheralDelegate.updateNameSubject + .eraseToAnyPublisher() + } + + public var modifyServices: AnyPublisher<[CBService], Never> { + peripheralDelegate.modifyServicesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the read RSSI value of a peripheral. + public var readRSSIChannel: AnyPublisher { + peripheralDelegate.readRSSISubject + .tryMap { rssi in + if let error = rssi.1 { + throw error + } else { + return rssi.0 + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the isReadyToSendWriteWithoutResponse value of a peripheral. + public var isReadyToSendWriteWithoutResponseChannel: AnyPublisher { + peripheralDelegate.isReadyToSendWriteWithoutResponseSubject + .first() + .eraseToAnyPublisher() + } + +} diff --git a/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift index 74b9929..9026ba5 100644 --- a/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift @@ -103,6 +103,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { let updatedDescriptorValuesSubject = PassthroughSubject< (CBDescriptor, Error?), Never >() + + let isReadyToSendWriteWithoutResponseSubject = PassthroughSubject() let writtenCharacteristicValuesSubject = PassthroughSubject< (CBCharacteristic, Error?), Never @@ -199,9 +201,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { } open func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { - l.i(#function) - fatalError() - } + isReadyToSendWriteWithoutResponseSubject.send(()) + } // MARK: Managing Notifications for a Characteristic’s Value diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift index 473a518..207dfb9 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift @@ -28,7 +28,7 @@ private extension CBMUUID { static let presentationFormat = CBUUID(string: "2904") } -class MockPeripheral: CBMPeripheralSpecDelegate { +private class MockPeripheral: CBMPeripheralSpecDelegate { public private (set) lazy var peripheral = CBMPeripheralSpec .simulatePeripheral(proximity: .far) .advertising( @@ -71,7 +71,7 @@ class MockPeripheral: CBMPeripheralSpecDelegate { final class PeripheralMultitaskingTests: XCTestCase { var cancelables: Set! var central: CentralManager! - var rs: MockPeripheral! + private var rs: MockPeripheral! override func setUpWithError() throws { try super.setUpWithError() diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift new file mode 100644 index 0000000..0785dfc --- /dev/null +++ b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift @@ -0,0 +1,173 @@ +// +// PeripheralReadWriteDescriptorTests.swift +// +// +// Created by Nick Kibysh on 23/02/2024. +// + +import Combine +import CoreBluetoothMock +import XCTest + +@testable import iOS_BLE_Library_Mock + +private extension CBMUUID { + static let runningSpeedCadenceService = CBUUID(string: "1814") + static let batteryService = CBUUID(string: "180F") + static let heartRateMonitorService = CBUUID(string: "180D") + static let deviceInformationService = CBUUID(string: "180A") + + static var allServices: [CBMUUID] = [ + .batteryService, + .runningSpeedCadenceService, + .heartRateMonitorService, + .deviceInformationService + ] + + static let batteryLevel = CBUUID(string: "2A19") + static let presentationFormat = CBUUID(string: "2904") +} + +private class MockPeripheral: CBMPeripheralSpecDelegate { + + private var batteryLevel: UInt8 = 0 + private (set) var lastWroteCommandValue: Data? + + public private (set) lazy var peripheral = CBMPeripheralSpec + .simulatePeripheral(proximity: .far) + .advertising( + advertisementData: [ + CBAdvertisementDataIsConnectable : true as NSNumber, + CBAdvertisementDataLocalNameKey : "Running Speed and Cadence sensor", + CBAdvertisementDataServiceUUIDsKey : [CBMUUID.runningSpeedCadenceService] + ], + withInterval: 2.0, + delay: 5.0, + alsoWhenConnected: false + ) + .connectable( + name: "Running Sensor", + services: CBMUUID.allServices.map { + if $0 == .batteryService { + return CBMServiceMock( + type: $0, + primary: true, + characteristics: [ + CBMCharacteristicMock(type: .batteryLevel, properties: .read, descriptors: CBMDescriptorMock(type: .presentationFormat)) + ]) + } else { + return CBMServiceMock(type: $0, primary: true) + } + }, + delegate: self + ) + .build() + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveServiceDiscoveryRequest serviceUUIDs: [CBMUUID]?) -> Result { + return .success(()) + } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveCharacteristicsDiscoveryRequest characteristicUUIDs: [CBMUUID]?, for service: CBMServiceMock) -> Result { + return .success(()) + } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveReadRequestFor characteristic: CBMCharacteristicMock) -> Result { + defer { batteryLevel += 1 } + + if batteryLevel % 2 == 0 { + return .failure(NSError(domain: "com.ble.characteristic", code: 1, userInfo: ["value":batteryLevel])) + } else { + return .success(Data([batteryLevel])) + } + } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveWriteCommandFor characteristic: CBMCharacteristicMock, data: Data) { + lastWroteCommandValue = data + } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveWriteRequestFor characteristic: CBMCharacteristicMock, data: Data) -> Result { + defer { lastWroteCommandValue = data } + + if data[0] % 2 == 0 { + return .failure(NSError(domain: "com.ble.characteristic", code: 2, userInfo: ["value": data])) + } else { + return .success(()) + } + } +} + +final class PeripheralReadWriteDescriptorTests: XCTestCase { + var cancelables: Set! + var central: CentralManager! + private var rs: MockPeripheral! + + override func setUpWithError() throws { + try super.setUpWithError() + + self.rs = MockPeripheral() + + CBMCentralManagerMock.simulateInitialState(.poweredOn) + CBMCentralManagerMock.simulatePeripherals([rs.peripheral]) + + let cmd = ReactiveCentralManagerDelegate() + let cm = CBCentralManagerFactory.instance(delegate: cmd, queue: .main, forceMock: true) + self.central = try CentralManager(centralManager: cm) + + cancelables = Set() + } + + func testReadCharacteristic() async throws { + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: ReactivePeripheralDelegate()) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + + do { + _ = try await p.readValue(for: batteryLevelCharacteristic).firstValue + } catch let e as NSError { + let level = try XCTUnwrap(e.userInfo["value"] as? UInt8) + XCTAssertEqual(level, 0) + XCTAssertEqual(e.code, 1) + } catch { + XCTFail("unexpected error") + } + + do { + let data = try await p.readValue(for: batteryLevelCharacteristic).firstValue + let level = try XCTUnwrap(data?[0] as? UInt8) + XCTAssertEqual(level, 1) + } catch { + XCTFail("unexpected error") + } + } + + func testWriteWithoutResponse() async throws { + let delegate = ReactivePeripheralDelegate() + + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: delegate) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + + let isReadyExp = expectation(description: "Peripheral is ready to write value without response") + p.isReadyToSendWriteWithoutResponseChannel + .sink { _ in + isReadyExp.fulfill() + } receiveValue: { _ in + } + .store(in: &cancelables) + + + let data = Data([1, 2, 3]) + p.writeValueWithoutResponse(data, for: batteryLevelCharacteristic) + XCTAssertEqual(data, rs.lastWroteCommandValue) + + await fulfillment(of: [isReadyExp], timeout: 3) + } +} From a52e364bba342bcfe40a1e00b62724471380ef6a Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Sat, 24 Feb 2024 22:06:58 +0100 Subject: [PATCH 4/7] test(characteristic): Write and Notify Addet tests: - write with response - notify Added teardown simulations to 'tearDown' method --- .../PeripheralReadWriteDescriptorTests.swift | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift index 0785dfc..ec194c4 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift @@ -53,7 +53,7 @@ private class MockPeripheral: CBMPeripheralSpecDelegate { type: $0, primary: true, characteristics: [ - CBMCharacteristicMock(type: .batteryLevel, properties: .read, descriptors: CBMDescriptorMock(type: .presentationFormat)) + CBMCharacteristicMock(type: .batteryLevel, properties: .indicate, descriptors: CBMDescriptorMock(type: .presentationFormat)) ]) } else { return CBMServiceMock(type: $0, primary: true) @@ -91,6 +91,7 @@ private class MockPeripheral: CBMPeripheralSpecDelegate { if data[0] % 2 == 0 { return .failure(NSError(domain: "com.ble.characteristic", code: 2, userInfo: ["value": data])) } else { + peripheral.simulateValueUpdate(data, for: characteristic) return .success(()) } } @@ -116,6 +117,11 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { cancelables = Set() } + override func tearDown() async throws { + CBMCentralManagerMock.tearDownSimulation() + try await super.tearDown() + } + func testReadCharacteristic() async throws { let p = try await central.scanForPeripherals(withServices: nil) .flatMap { self.central.connect($0.peripheral) } @@ -170,4 +176,65 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { await fulfillment(of: [isReadyExp], timeout: 3) } + + func testWriteWithResponse() async throws { + let delegate = ReactivePeripheralDelegate() + + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: delegate) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + + let sendData = Data([0]) + do { + _ = try await p.writeValueWithResponse(sendData, for: batteryLevelCharacteristic).firstValue + } catch let e as NSError { + let level = try XCTUnwrap(e.userInfo["value"] as? Data) + XCTAssertEqual(level, sendData) + XCTAssertEqual(e.code, 2) + } catch { + XCTFail("unexpected error") + } + + do { + _ = try await p.writeValueWithResponse(Data([1]), for: batteryLevelCharacteristic).firstValue + } catch { + XCTFail("unexpected error") + } + } + + func testNotifyCharacteristic() async throws { + let delegate = ReactivePeripheralDelegate() + + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: delegate) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + + let exp = expectation(description: "write response expectation") + + p.listenValues(for: batteryLevelCharacteristic) + .sink { _ in + + } receiveValue: { data in + XCTAssertEqual(data, Data([1])) + exp.fulfill() + } + .store(in: &cancelables) + + do { + _ = try await p.setNotifyValue(true, for: batteryLevelCharacteristic).firstValue + _ = try await p.writeValueWithResponse(Data([1]), for: batteryLevelCharacteristic).firstValue + } catch { + XCTFail("unexpected error") + } + + await fulfillment(of: [exp], timeout: 3) + } } From 2720a5ebd39a875fa07f02e1981ae3ee9d840a50 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Sun, 25 Feb 2024 21:24:43 +0100 Subject: [PATCH 5/7] feat(peripheral-descriptors): Read / Write descriptor values Added ability to read and write Descriptor's values --- .../Peripheral/Peripheral+Writer.swift | 153 ++++++++++++++++++ .../Peripheral/Peripheral.swift | 38 ++--- .../Peripheral/Peripheral+Writer.swift | 153 ++++++++++++++++++ .../Peripheral/Peripheral.swift | 38 ++--- 4 files changed, 340 insertions(+), 42 deletions(-) diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift index 0f0b052..52ae97a 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift @@ -43,6 +43,30 @@ extension Peripheral { super.init(peripheral: peripheral) } } + + class DescriptorWriter: OperationQueue { + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.writtenEventsPublisher = writtenEventsPublisher + super.init(peripheral: peripheral) + } + } + + class DescriptorReader: OperationQueue { + let updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.updateEventsPublisher = updateEventsPublisher + super.init(peripheral: peripheral) + } + } } extension Peripheral.CharacteristicWriter { @@ -73,6 +97,35 @@ extension Peripheral.CharacteristicReader { } } +extension Peripheral.DescriptorWriter { + func write(_ value: Data, to dsecriptor: CBDescriptor) -> Future { + let operation = WriteDescriptorOperation( + data: value, + writtenEventsPublisher: writtenEventsPublisher, + descriptor: dsecriptor, + peripheral: peripheral + ) + + queue.addOperation(operation) + + return operation.future + } +} + +extension Peripheral.DescriptorReader { + func readValue(from descriptor: CBDescriptor) -> Future { + let operation = ReadDescriptorOperation( + updateEventPublisher: updateEventsPublisher, + descriptor: descriptor, + peripheral: peripheral + ) + + queue.addOperation(operation) + + return operation.future + } +} + private class BasicOperation: Operation { let peripheral: CBPeripheral var cancelable: AnyCancellable? @@ -222,5 +275,105 @@ private class ReadCharacteristicOperation: BasicOperation { state = .executing main() } +} + +private class WriteDescriptorOperation: BasicOperation { + + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + let data: Data + + init( + data: Data, writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.data = data + self.writtenEventsPublisher = writtenEventsPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.writeValue(data, for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = writtenEventsPublisher.share() + .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0 + } + } + .sink { [unowned self] completion in + switch completion { + case .finished: + self.promise?(.success(())) + case .failure(let e): + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { _ in + + } + + state = .executing + main() + } +} +private class ReadDescriptorOperation: BasicOperation { + let updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + init( + updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.updateEventPublisher = updateEventPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.readValue(for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = updateEventPublisher.share() + .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0.value + } + } + .sink { [unowned self] completion in + if case .failure(let e) = completion { + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { v in + self.promise?(.success(v)) + } + + state = .executing + main() + } } diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index 472b193..600637b 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -88,17 +88,27 @@ public class Peripheral { private let stateSubject = CurrentValueSubject(.disconnected) private var observer: Observer! - private lazy var writer = CharacteristicWriter( + private lazy var characteristicWriter = CharacteristicWriter( writtenEventsPublisher: self.peripheralDelegate.writtenCharacteristicValuesSubject .eraseToAnyPublisher(), peripheral: self.peripheral ) - private lazy var reader = CharacteristicReader( + private lazy var characteristicReader = CharacteristicReader( updateEventPublisher: self.peripheralDelegate.updatedCharacteristicValuesSubject .eraseToAnyPublisher(), peripheral: peripheral ) + + private lazy var descriptorWriter = DescriptorWriter( + writtenEventsPublisher: self.peripheralDelegate.writtenDescriptorValuesSubject.eraseToAnyPublisher(), + peripheral: peripheral + ) + + private lazy var descriptorReader = DescriptorReader( + updateEventsPublisher: self.peripheralDelegate.updatedDescriptorValuesSubject.eraseToAnyPublisher(), + peripheral: peripheral + ) // TODO: Why don't we use default delegate? /// Initializes a Peripheral instance. @@ -256,7 +266,7 @@ extension Peripheral { /// - 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) + return characteristicReader.readValue(from: characteristic) } /// Listen for updates to the value of a characteristic. @@ -281,22 +291,8 @@ extension Peripheral { /// /// - Parameter descriptor: The descriptor to read from. /// - Returns: A future emitting the read data or an error. - public func readValue(for descriptor: CBDescriptor) -> Future { - return Future.init { promis in - promis(.failure(Err.badDelegate)) - } - /* - return peripheralDelegate.updatedDescriptorValuesSubject - .filter { $0.0.uuid == descriptor.uuid } - .tryCompactMap { (desc, err) in - if let err { - throw err - } - - return desc.value - } - .eraseToAnyPublisher() - */ + public func readValue(for descriptor: CBDescriptor) -> Future { + return descriptorReader.readValue(from: descriptor) } } @@ -342,8 +338,8 @@ extension Peripheral { /// - Parameters: /// - data: The data to write. /// - descriptor: The descriptor to write to. - public func writeValue(_ data: Data, for descriptor: CBDescriptor) { - fatalError() + public func writeValue(_ data: Data, for descriptor: CBDescriptor) -> Future { + return descriptorWriter.write(data, to: descriptor) } } diff --git a/Sources/iOS-BLE-Library/Peripheral/Peripheral+Writer.swift b/Sources/iOS-BLE-Library/Peripheral/Peripheral+Writer.swift index 9d1a6de..9270e17 100644 --- a/Sources/iOS-BLE-Library/Peripheral/Peripheral+Writer.swift +++ b/Sources/iOS-BLE-Library/Peripheral/Peripheral+Writer.swift @@ -49,6 +49,30 @@ extension Peripheral { super.init(peripheral: peripheral) } } + + class DescriptorWriter: OperationQueue { + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.writtenEventsPublisher = writtenEventsPublisher + super.init(peripheral: peripheral) + } + } + + class DescriptorReader: OperationQueue { + let updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.updateEventsPublisher = updateEventsPublisher + super.init(peripheral: peripheral) + } + } } extension Peripheral.CharacteristicWriter { @@ -79,6 +103,35 @@ extension Peripheral.CharacteristicReader { } } +extension Peripheral.DescriptorWriter { + func write(_ value: Data, to dsecriptor: CBDescriptor) -> Future { + let operation = WriteDescriptorOperation( + data: value, + writtenEventsPublisher: writtenEventsPublisher, + descriptor: dsecriptor, + peripheral: peripheral + ) + + queue.addOperation(operation) + + return operation.future + } +} + +extension Peripheral.DescriptorReader { + func readValue(from descriptor: CBDescriptor) -> Future { + let operation = ReadDescriptorOperation( + updateEventPublisher: updateEventsPublisher, + descriptor: descriptor, + peripheral: peripheral + ) + + queue.addOperation(operation) + + return operation.future + } +} + private class BasicOperation: Operation { let peripheral: CBPeripheral var cancelable: AnyCancellable? @@ -228,5 +281,105 @@ private class ReadCharacteristicOperation: BasicOperation { state = .executing main() } +} + +private class WriteDescriptorOperation: BasicOperation { + + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + let data: Data + + init( + data: Data, writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.data = data + self.writtenEventsPublisher = writtenEventsPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.writeValue(data, for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = writtenEventsPublisher.share() + .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0 + } + } + .sink { [unowned self] completion in + switch completion { + case .finished: + self.promise?(.success(())) + case .failure(let e): + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { _ in + + } + + state = .executing + main() + } +} +private class ReadDescriptorOperation: BasicOperation { + let updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + init( + updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.updateEventPublisher = updateEventPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.readValue(for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = updateEventPublisher.share() + .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0.value + } + } + .sink { [unowned self] completion in + if case .failure(let e) = completion { + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { v in + self.promise?(.success(v)) + } + + state = .executing + main() + } } diff --git a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift index 2068eef..2772791 100644 --- a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift @@ -100,17 +100,27 @@ public class Peripheral { private let stateSubject = CurrentValueSubject(.disconnected) private var observer: Observer! - private lazy var writer = CharacteristicWriter( + private lazy var characteristicWriter = CharacteristicWriter( writtenEventsPublisher: self.peripheralDelegate.writtenCharacteristicValuesSubject .eraseToAnyPublisher(), peripheral: self.peripheral ) - private lazy var reader = CharacteristicReader( + private lazy var characteristicReader = CharacteristicReader( updateEventPublisher: self.peripheralDelegate.updatedCharacteristicValuesSubject .eraseToAnyPublisher(), peripheral: peripheral ) + + private lazy var descriptorWriter = DescriptorWriter( + writtenEventsPublisher: self.peripheralDelegate.writtenDescriptorValuesSubject.eraseToAnyPublisher(), + peripheral: peripheral + ) + + private lazy var descriptorReader = DescriptorReader( + updateEventsPublisher: self.peripheralDelegate.updatedDescriptorValuesSubject.eraseToAnyPublisher(), + peripheral: peripheral + ) // TODO: Why don't we use default delegate? /// Initializes a Peripheral instance. @@ -275,7 +285,7 @@ extension Peripheral { /// - 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) + return characteristicReader.readValue(from: characteristic) } /// Listen for updates to the value of a characteristic. @@ -300,22 +310,8 @@ extension Peripheral { /// /// - Parameter descriptor: The descriptor to read from. /// - Returns: A future emitting the read data or an error. - public func readValue(for descriptor: CBDescriptor) -> Future { - return Future.init { promis in - promis(.failure(Err.badDelegate)) - } - /* - return peripheralDelegate.updatedDescriptorValuesSubject - .filter { $0.0.uuid == descriptor.uuid } - .tryCompactMap { (desc, err) in - if let err { - throw err - } - - return desc.value - } - .eraseToAnyPublisher() - */ + public func readValue(for descriptor: CBDescriptor) -> Future { + return descriptorReader.readValue(from: descriptor) } } @@ -361,8 +357,8 @@ extension Peripheral { /// - Parameters: /// - data: The data to write. /// - descriptor: The descriptor to write to. - public func writeValue(_ data: Data, for descriptor: CBDescriptor) { - fatalError() + public func writeValue(_ data: Data, for descriptor: CBDescriptor) -> Future { + return descriptorWriter.write(data, to: descriptor) } } From d96d9bec9a7ca72e0ef3f58ed86d1f1684fd3efc Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Sun, 25 Feb 2024 21:27:27 +0100 Subject: [PATCH 6/7] test(peripheral-descriptor): Descriptors read-write Unit Tests --- .../CentralManagerTests.swift | 4 +- .../PeripheralMultitaskingTests.swift | 5 + .../PeripheralReadWriteDescriptorTests.swift | 98 ++++++++++++++++++- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift index 616f6e9..ebfa312 100644 --- a/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift +++ b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift @@ -32,13 +32,13 @@ final class CentralManagerTests: XCTestCase { } override func tearDownWithError() throws { - try super.tearDownWithError() - cancelables.removeAll() cancelables = nil central = nil rs = nil CBMCentralManagerMock.tearDownSimulation() + + try super.tearDownWithError() } func testCentralManagerCreation() throws { diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift index 207dfb9..748459b 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralMultitaskingTests.swift @@ -88,6 +88,11 @@ final class PeripheralMultitaskingTests: XCTestCase { cancelables = Set() } + override func tearDown() async throws { + CBMCentralManagerMock.tearDownSimulation() + try await super.tearDown() + } + func testDiscoverServices() async throws { let p = try await central.scanForPeripherals(withServices: nil) .flatMap { self.central.connect($0.peripheral) } diff --git a/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift index ec194c4..d8a3290 100644 --- a/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift +++ b/Tests/iOS-BLE-LibraryTests/PeripheralReadWriteDescriptorTests.swift @@ -95,6 +95,27 @@ private class MockPeripheral: CBMPeripheralSpecDelegate { return .success(()) } } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveReadRequestFor descriptor: CBMDescriptorMock) -> Result { + defer { batteryLevel += 1 } + + if batteryLevel % 2 == 0 { + let e = NSError(domain: "com.ble.characteristic", code: 3, userInfo: ["value": batteryLevel]) + return .failure(e) + } else { + return .success(Data([batteryLevel])) + } + } + + func peripheral(_ peripheral: CBMPeripheralSpec, didReceiveWriteRequestFor descriptor: CBMDescriptorMock, data: Data) -> Result { + if data[0] % 2 == 0 { + let e = NSError(domain: "com.ble.characteristic", code: 3, userInfo: ["value": data]) + return .failure(e) + } else { + lastWroteCommandValue = data + return .success(()) + } + } } final class PeripheralReadWriteDescriptorTests: XCTestCase { @@ -133,12 +154,13 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { do { _ = try await p.readValue(for: batteryLevelCharacteristic).firstValue + XCTFail("Error should be thrown") } catch let e as NSError { let level = try XCTUnwrap(e.userInfo["value"] as? UInt8) XCTAssertEqual(level, 0) XCTAssertEqual(e.code, 1) } catch { - XCTFail("unexpected error") + XCTFail("Unexpected Error: \(error.localizedDescription)") } do { @@ -146,7 +168,7 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { let level = try XCTUnwrap(data?[0] as? UInt8) XCTAssertEqual(level, 1) } catch { - XCTFail("unexpected error") + XCTFail("Unexpected Error: \(error.localizedDescription)") } } @@ -191,18 +213,19 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { let sendData = Data([0]) do { _ = try await p.writeValueWithResponse(sendData, for: batteryLevelCharacteristic).firstValue + XCTFail("Error should be thrown") } catch let e as NSError { let level = try XCTUnwrap(e.userInfo["value"] as? Data) XCTAssertEqual(level, sendData) XCTAssertEqual(e.code, 2) } catch { - XCTFail("unexpected error") + XCTFail("Unexpected Error: \(error.localizedDescription)") } do { _ = try await p.writeValueWithResponse(Data([1]), for: batteryLevelCharacteristic).firstValue } catch { - XCTFail("unexpected error") + XCTFail("Unexpected Error: \(error.localizedDescription)") } } @@ -232,9 +255,74 @@ final class PeripheralReadWriteDescriptorTests: XCTestCase { _ = try await p.setNotifyValue(true, for: batteryLevelCharacteristic).firstValue _ = try await p.writeValueWithResponse(Data([1]), for: batteryLevelCharacteristic).firstValue } catch { - XCTFail("unexpected error") + XCTFail("Unexpected Error: \(error.localizedDescription)") } await fulfillment(of: [exp], timeout: 3) } + + func testReadDescriptor() async throws { + let delegate = ReactivePeripheralDelegate() + + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: delegate) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + let presentationFormat = try await p.discoverDescriptors(for: batteryLevelCharacteristic).firstValue.first! + + do { + let v = try await p.readValue(for: presentationFormat).firstValue + XCTFail("Error should be thrown") + } catch let e as NSError { + XCTAssertEqual(e.code, 3) + + let value = try XCTUnwrap(e.userInfo["value"] as? UInt8) + XCTAssertEqual(value, 0) + } catch { + XCTFail("Unexpected Error: \(error.localizedDescription)") + } + + do { + let v = try await p.readValue(for: presentationFormat).firstValue + let bl = try XCTUnwrap(v as? Data) + XCTAssertEqual(1, bl[0]) + } catch { + XCTFail("Unexpected Error: \(error.localizedDescription)") + } + } + + func testWriteDescriptor() async throws { + let delegate = ReactivePeripheralDelegate() + + let p = try await central.scanForPeripherals(withServices: nil) + .flatMap { self.central.connect($0.peripheral) } + .map { Peripheral(peripheral: $0, delegate: delegate) } + .firstValue + + let batteryService = try await p.discoverServices(serviceUUIDs: [.batteryService ]).firstValue.first! + let batteryLevelCharacteristic = try await p.discoverCharacteristics([.batteryLevel], for: batteryService).firstValue.first! + let presentationFormat = try await p.discoverDescriptors(for: batteryLevelCharacteristic).firstValue.first! + + let writeData1 = Data([0]) + do { + try await p.writeValue(writeData1, for: presentationFormat).firstValue + XCTFail("Error should be thrown") + } catch let e as NSError { + XCTAssertEqual(e.code, 3) + let data = try XCTUnwrap(e.userInfo["value"] as? Data) + XCTAssertEqual(data, writeData1) + } catch { + XCTFail("Unexpected Error: \(error.localizedDescription)") + } + + let writeData2 = Data([1]) + do { + try await p.writeValue(writeData2, for: presentationFormat).firstValue + } catch { + XCTFail("Unexpected Error: \(error.localizedDescription)") + } + } } From 667407ab252f81d18349b7b60d4bd73f73fe90dd Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Mon, 26 Feb 2024 15:21:28 +0100 Subject: [PATCH 7/7] updated version in README file --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1c514b..f426bf4 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ let package = Package( /// . . . dependencies: [ // Set the link to the library and choose the version - .package(url: "https://github.com/NordicSemiconductor/IOS-BLE-Library.git", from: "0.1.3"), + .package(url: "https://github.com/NordicSemiconductor/IOS-BLE-Library.git", from: "0.3.1"), ], targets: [ .target( @@ -51,12 +51,12 @@ The library can be installed using CocoaPods. Add the following line to your Podfile: ```ruby -pod 'iOS-BLE-Library', '~> 0.1.3' +pod 'iOS-BLE-Library', '~> 0.3.1' ``` or ```ruby -pod 'iOS-BLE-Library-Mock', '~> 0.1.3' +pod 'iOS-BLE-Library-Mock', '~> 0.3.1' ``` # Use At Your Own Risk (Currently)