diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 21dd282..2a5bfa1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -42,6 +42,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
+
- name: Run copy script
run: |
python3 code_gen/code_gen.py Sources
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme
new file mode 100644
index 0000000..98b5a44
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme
new file mode 100644
index 0000000..300adb0
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/iOS-BLE-Library-Mock/Alias.swift b/Sources/iOS-BLE-Library-Mock/Alias.swift
index 3f8811d..b04c7c5 100644
--- a/Sources/iOS-BLE-Library-Mock/Alias.swift
+++ b/Sources/iOS-BLE-Library-Mock/Alias.swift
@@ -38,59 +38,50 @@ 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
+public let CBConnectPeripheralOptionNotifyOnConnectionKey = CBMConnectPeripheralOptionNotifyOnConnectionKey
+public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = CBMConnectPeripheralOptionNotifyOnDisconnectionKey
+public let CBConnectPeripheralOptionNotifyOnNotificationKey = CBMConnectPeripheralOptionNotifyOnNotificationKey
\ No newline at end of file
diff --git a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift
index 016756c..87dd282 100644
--- a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift
+++ b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift
@@ -18,8 +18,7 @@ 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:
@@ -55,7 +54,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)
@@ -64,7 +63,7 @@ public class CentralManager {
/// The underlying CBCentralManager instance.
public let centralManager: CBCentralManager
-
+
/// The reactive delegate for the ``centralManager``.
public let centralManagerDelegate: ReactiveCentralManagerDelegate
@@ -74,12 +73,10 @@ 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()
}
@@ -112,8 +109,28 @@ 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:
+ /// print("Peripheral disconnected successfully")
+ /// case .failure(let error):
+ /// print("Error: \(error)")
+ /// }
+ /// } receiveValue: { peripheral in
+ /// print("Peripheral connected: \(peripheral)")
+ /// }
+ /// .store(in: &cancellables)
+ /// ```
public func connect(_ peripheral: CBPeripheral, options: [String: Any]? = nil)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher
{
let killSwitch = self.disconnectedPeripheralsChannel.tryFirst(where: { p in
if let e = p.1 {
@@ -135,13 +152,14 @@ extension CentralManager {
.bluetooth {
self.centralManager.connect(peripheral, options: options)
}
+ .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)
- -> Publishers.BluetoothPublisher
+ public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher
{
return self.disconnectedPeripheralsChannel
.tryFilter { r in
@@ -157,15 +175,17 @@ extension CentralManager {
}
.map { $0.0 }
.first()
- .bluetooth {
- self.centralManager.cancelPeripheralConnection(peripheral)
- }
+ .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.
///
@@ -198,13 +218,13 @@ 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]?)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher
{
stopScan()
return centralManagerDelegate.stateSubject
@@ -230,6 +250,8 @@ extension CentralManager {
.bluetooth {
self.centralManager.scanForPeripherals(withServices: services)
}
+ .autoconnect()
+ .eraseToAnyPublisher()
}
/// Stops an ongoing scan for peripherals.
diff --git a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md
index 19e34cc..5a74a87 100644
--- a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md
+++ b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md
@@ -2,37 +2,12 @@
### Create a Central Manager
-Since it's not recommended to override the `CBCentralManager`'s methods, ``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside.
+``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside.
-The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:)``, or an existing instance can be passed using ``init(centralManager:)``.
+The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:options:)``, or an existing instance can be passed using ``init(centralManager:)``.
If you pass a central manager inside ``init(centralManager:)``, it should already have a delegate set. The delegate should be an instance of ``ReactiveCentralManagerDelegate``; otherwise, an error will be thrown.
-### Connection
-
-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.
-
-> The publisher returned by ``CentralManager/connect(_:options:)`` is a `ConnectablePublisher`. Therefore, you need to call `connect()` or `autoconnect()` to initiate the connection process.
-
-```swift
-centralManager.connect(peripheral)
- .autoconnect()
- .sink { completion in
- switch completion {
- case .finished:
- print("Peripheral disconnected successfully")
- case .failure(let error):
- print("Error: \(error)")
- }
- } receiveValue: { peripheral in
- print("Peripheral connected: \(peripheral)")
- }
- .store(in: &cancellables)
-```
-
### Channels
Channels are used to pass through data from the `CBCentralManagerDelegate` methods.
diff --git a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md
new file mode 100644
index 0000000..fdaa466
--- /dev/null
+++ b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md
@@ -0,0 +1,7 @@
+# ``iOS_BLE_Library/CentralManager/connect(_:options:)``
+
+## See Also
+
+- ``CentralManager/connectedPeripheralChannel``
+- ``CentralManager/disconnectedPeripheralsChannel``
+
diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift
index ca57ff6..f1329e1 100644
--- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift
+++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift
@@ -7,6 +7,7 @@
import Combine
import CoreBluetooth
+
import CoreBluetoothMock
import Foundation
@@ -42,31 +43,28 @@ private class NativeObserver: Observer {
}
private class MockObserver: Observer {
- @objc private var peripheral: CBMPeripheralMock
+ @objc private var peripheral: CBMPeripheralMock
- private weak var publisher: CurrentValueSubject!
- private var observation: NSKeyValueObservation?
+ private weak var publisher: CurrentValueSubject!
+ private var observation: NSKeyValueObservation?
- init(
- peripheral: CBMPeripheralMock,
- publisher: CurrentValueSubject
- ) {
- self.peripheral = peripheral
- self.publisher = publisher
- super.init()
- }
+ 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)
+ }
+ }
+ }
+ }
- 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 {
/// I'm Errr from Omicron Persei 8
@@ -96,22 +94,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()
+ }
}
}
@@ -124,13 +122,12 @@ extension Peripheral {
}
extension Peripheral {
- // TODO: Extract repeated code
/// 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.
+ ///
+ /// - 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]?)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher<[CBService], Error>
{
let allServices = peripheralDelegate.discoveredServicesSubject
.tryCompactMap { result throws -> [CBService]? in
@@ -139,34 +136,25 @@ extension Peripheral {
} else {
return result.0
}
- }
- .flatMap { services -> Publishers.Sequence<[CBService], Error> in
- Publishers.Sequence(sequence: services)
- }
+ }
+ .first()
- let filtered: AnyPublisher
-
- if let serviceList = serviceUUIDs {
- filtered = allServices.guestList(serviceList, keypath: \.uuid)
- .eraseToAnyPublisher()
- } else {
- filtered = allServices.eraseToAnyPublisher()
- }
-
- return filtered.bluetooth {
+ return allServices.bluetooth {
self.peripheral.discoverServices(serviceUUIDs)
}
+ .autoconnect()
+ .eraseToAnyPublisher()
}
/// 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
- ) -> Publishers.BluetoothPublisher {
+ ) -> AnyPublisher<[CBCharacteristic], Error> {
let allCharacteristics = peripheralDelegate.discoveredCharacteristicsSubject
.filter {
$0.0.uuid == service.uuid
@@ -178,33 +166,21 @@ extension Peripheral {
return result.1
}
}
- .flatMap {
- characteristics -> Publishers.Sequence<[CBCharacteristic], Error> in
- Publishers.Sequence(sequence: characteristics)
- }
+ .first()
- let filtered: AnyPublisher
-
- if let list = characteristicUUIDs {
- filtered =
- allCharacteristics
- .guestList(list, keypath: \.uuid)
- .eraseToAnyPublisher()
- } else {
- filtered = allCharacteristics.eraseToAnyPublisher()
- }
-
- return filtered.bluetooth {
+ return allCharacteristics.bluetooth {
self.peripheral.discoverCharacteristics(characteristicUUIDs, for: service)
}
+ .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)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher<[CBDescriptor], Error>
{
return peripheralDelegate.discoveredDescriptorsSubject
.filter {
@@ -217,25 +193,25 @@ extension Peripheral {
return result.1
}
}
- .flatMap { descriptors -> Publishers.Sequence<[CBDescriptor], Error> in
- Publishers.Sequence(sequence: descriptors)
- }
+ .first()
.bluetooth {
self.peripheral.discoverDescriptors(for: characteristic)
}
+ .autoconnect()
+ .eraseToAnyPublisher()
}
}
// 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)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher
{
return peripheralDelegate.writtenCharacteristicValuesSubject
.first(where: { $0.0.uuid == characteristic.uuid })
@@ -250,22 +226,24 @@ extension Peripheral {
self.peripheral.writeValue(
data, for: characteristic, type: .withResponse)
}
+ .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()
}
@@ -274,17 +252,17 @@ extension Peripheral {
// 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.
+ ///
+ /// - 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.
+ ///
+ /// - 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
@@ -300,9 +278,9 @@ extension Peripheral {
}
/// Read the value of a descriptor.
- ///
- /// - Parameter descriptor: The descriptor to read from.
- /// - Returns: A future emitting the read data or an error.
+ ///
+ /// - 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()
}
@@ -311,18 +289,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)
- -> Publishers.BluetoothPublisher
+ -> AnyPublisher
{
if characteristic.isNotifying == isEnabled {
return Just(isEnabled)
.setFailureType(to: Error.self)
- .bluetooth {}
+ .eraseToAnyPublisher()
}
return peripheralDelegate.notificationStateSubject
@@ -336,5 +314,7 @@ extension Peripheral {
.bluetooth {
self.peripheral.setNotifyValue(isEnabled, for: characteristic)
}
+ .autoconnect()
+ .eraseToAnyPublisher()
}
}
diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift
index c342031..725d6d0 100644
--- a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift
+++ b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift
@@ -11,12 +11,14 @@ import Foundation
public class ReactivePeripheralDelegate: NSObject {
let l = L(category: #file)
-
- // MARK: Subjects
+
+ // MARK: Discovering Services
public let discoveredServicesSubject = PassthroughSubject<([CBService]?, Error?), Never>()
public let discoveredIncludedServicesSubject = PassthroughSubject<
(CBService, [CBService]?, Error?), Never
>()
+
+ // MARK: Discovering Characteristics and their Descriptors
public let discoveredCharacteristicsSubject = PassthroughSubject<
(CBService, [CBCharacteristic]?, Error?), Never
>()
@@ -46,6 +48,7 @@ public class ReactivePeripheralDelegate: NSObject {
// MARK: Monitoring Changes to a Peripheral’s Name or Services
public let updateNameSubject = PassthroughSubject()
+ public let modifyServicesSubject = PassthroughSubject<[CBService], Never>()
}
extension ReactivePeripheralDelegate: CBPeripheralDelegate {
@@ -142,11 +145,6 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate {
fatalError()
}
- public func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) {
- l.i(#function)
- fatalError()
- }
-
// MARK: Monitoring Changes to a Peripheral’s Name or Services
public func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
@@ -158,16 +156,16 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate {
_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]
) {
l.i(#function)
- fatalError()
+ modifyServicesSubject.send(invalidatedServices)
}
// MARK: Monitoring L2CAP Channels
-
+/*
public func peripheral(
_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?
) {
l.i(#function)
fatalError()
}
-
+*/
}
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 c979d0d..bb41d71 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()
@@ -37,7 +37,7 @@ extension Publishers {
.store(in: &cancellables)
```
*/
- public class BluetoothPublisher