11import Foundation
2+ import CoreBluetooth
23
34/// The `CombineCoreBluetooth` wrapper around `CBPeripheral`.
45public struct Peripheral {
56 let rawValue : CBPeripheral ?
7+ let delegate : Delegate
68
79 var _name : ( ) -> String ?
810 var _identifier : ( ) -> UUID
@@ -13,19 +15,18 @@ public struct Peripheral {
1315 @available ( macOS, unavailable)
1416 var _ancsAuthorized : ( ) -> Bool
1517
16- var _readRSSI : ( ) -> AnyPublisher < Double , Error >
17- var _discoverServices : ( _ serviceUUIDs: [ CBUUID ] ? ) -> AnyPublisher < [ CBService ] , Error >
18- var _discoverIncludedServices : ( _ includedServiceUUIDs: [ CBUUID ] ? , _ service: CBService ) -> AnyPublisher < [ CBService ] ? , Error >
19- var _discoverCharacteristics : ( _ characteristicUUIDs: [ CBUUID ] ? , _ service: CBService ) -> AnyPublisher < [ CBCharacteristic ] , Error >
20- var _readValueForCharacteristic : ( _ characteristic: CBCharacteristic ) -> AnyPublisher < Data ? , Error >
18+ var _readRSSI : ( ) -> Void
19+ var _discoverServices : ( _ serviceUUIDs: [ CBUUID ] ? ) -> Void
20+ var _discoverIncludedServices : ( _ includedServiceUUIDs: [ CBUUID ] ? , _ service: CBService ) -> Void
21+ var _discoverCharacteristics : ( _ characteristicUUIDs: [ CBUUID ] ? , _ service: CBService ) -> Void
22+ var _readValueForCharacteristic : ( _ characteristic: CBCharacteristic ) -> Void
2123 var _maximumWriteValueLength : ( _ type: CBCharacteristicWriteType ) -> Int
22- var _writeValueForCharacteristic : ( _ data: Data , _ characteristic: CBCharacteristic , _ type: CBCharacteristicWriteType ) -> AnyPublisher < Void , Error >
23- var _setNotifyValue : ( _ enabled: Bool , _ characteristic: CBCharacteristic ) -> AnyPublisher < Void , Error >
24- var _discoverDescriptors : ( _ characteristic: CBCharacteristic ) -> AnyPublisher < [ CBDescriptor ] ? , Error >
25- var _readValueForDescriptor : ( _ descriptor: CBDescriptor ) -> AnyPublisher < Any ? , Error >
26- var _writeValueForDescriptor : ( _ data: Data , _ descriptor: CBDescriptor ) -> AnyPublisher < Void , Error >
27- var _openL2CAPChannel : ( _ PSM: CBL2CAPPSM ) -> AnyPublisher < L2CAPChannel , Error >
28- var _listenForUpdatesToCharacteristic : ( _ characteristic: CBCharacteristic ) -> AnyPublisher < Data ? , Error >
24+ var _writeValueForCharacteristic : ( _ data: Data , _ characteristic: CBCharacteristic , _ type: CBCharacteristicWriteType ) -> Void
25+ var _setNotifyValue : ( _ enabled: Bool , _ characteristic: CBCharacteristic ) -> Void
26+ var _discoverDescriptors : ( _ characteristic: CBCharacteristic ) -> Void
27+ var _readValueForDescriptor : ( _ descriptor: CBDescriptor ) -> Void
28+ var _writeValueForDescriptor : ( _ data: Data , _ descriptor: CBDescriptor ) -> Void
29+ var _openL2CAPChannel : ( _ PSM: CBL2CAPPSM ) -> Void
2930
3031 public var isReadyToSendWriteWithoutResponse : AnyPublisher < Void , Never >
3132 public var nameUpdates : AnyPublisher < String ? , Never >
@@ -57,55 +58,234 @@ public struct Peripheral {
5758 }
5859
5960 public func readRSSI( ) -> AnyPublisher < Double , Error > {
60- _readRSSI ( )
61+ delegate
62+ . didReadRSSI
63+ . tryMap { result in
64+ try result. get ( )
65+ }
66+ . first ( )
67+ . handleEvents ( receiveSubscription: { [ _readRSSI] _ in
68+ _readRSSI ( )
69+ } )
70+ . shareCurrentValue ( )
71+ . eraseToAnyPublisher ( )
6172 }
6273
6374 public func discoverServices( _ serviceUUIDs: [ CBUUID ] ? ) -> AnyPublisher < [ CBService ] , Error > {
64- _discoverServices ( serviceUUIDs)
75+ delegate
76+ . didDiscoverServices
77+ . filterFirstValueOrThrow ( where: { services in
78+ // nil identifiers means we want to discover anything we can
79+ guard let identifiers = serviceUUIDs else { return true }
80+ // Only progress if the peripheral contains all the services we are looking for.
81+ let neededUUIDs = Set ( identifiers)
82+ let foundUUIDs = Set ( services. map ( \. uuid) )
83+ let allFound = foundUUIDs. isSuperset ( of: neededUUIDs)
84+ return allFound
85+ } )
86+ . handleEvents ( receiveSubscription: { [ _discoverServices] _ in
87+ _discoverServices ( serviceUUIDs)
88+ } )
89+ . shareCurrentValue ( )
90+ . eraseToAnyPublisher ( )
6591 }
6692
6793 public func discoverIncludedServices( _ serviceUUIDS: [ CBUUID ] ? , for service: CBService ) -> AnyPublisher < [ CBService ] ? , Error > {
68- _discoverIncludedServices ( serviceUUIDS, service)
94+ delegate
95+ . didDiscoverIncludedServices
96+ . filterFirstValueOrThrow ( where: { discoveredService in
97+ // ignore characteristics from services we're not interested in.
98+ guard discoveredService. uuid == service. uuid else { return false }
99+ // nil identifiers means we want to discover anything we can
100+ guard let identifiers = serviceUUIDS else { return true }
101+ // Only progress if the discovered service contains all the included services we are looking for.
102+ let neededUUIDs = Set ( identifiers)
103+ let foundUUIDs = Set ( ( discoveredService. includedServices ?? [ ] ) . map ( \. uuid) )
104+ let allFound = foundUUIDs. isSuperset ( of: neededUUIDs)
105+ return allFound
106+ } )
107+ . map ( \. includedServices)
108+ . handleEvents ( receiveSubscription: { [ _discoverIncludedServices] _ in
109+ _discoverIncludedServices ( serviceUUIDS, service)
110+ } )
111+ . shareCurrentValue ( )
112+ . eraseToAnyPublisher ( )
69113 }
70114
71115 public func discoverCharacteristics( _ characteristicUUIDs: [ CBUUID ] ? , for service: CBService ) -> AnyPublisher < [ CBCharacteristic ] , Error > {
72- _discoverCharacteristics ( characteristicUUIDs, service)
116+ delegate
117+ . didDiscoverCharacteristics
118+ . filterFirstValueOrThrow ( where: { discoveredService in
119+ // ignore characteristics from services we're not interested in.
120+ guard discoveredService. uuid == service. uuid else { return false }
121+ // nil identifiers means we want to discover anything we can
122+ guard let identifiers = characteristicUUIDs else { return true }
123+ // Only progress if the discovered service contains all the characteristics we are looking for.
124+ let neededUUIDs = Set ( identifiers)
125+ let foundUUIDs = Set ( ( discoveredService. characteristics ?? [ ] ) . map ( \. uuid) )
126+ let allFound = foundUUIDs. isSuperset ( of: neededUUIDs)
127+ return allFound
128+ } )
129+ . map ( { $0. characteristics ?? [ ] } )
130+ . handleEvents ( receiveSubscription: { [ _discoverCharacteristics] _ in
131+ _discoverCharacteristics ( characteristicUUIDs, service)
132+ } )
133+ . shareCurrentValue ( )
134+ . eraseToAnyPublisher ( )
73135 }
74136
75137 public func readValue( for characteristic: CBCharacteristic ) -> AnyPublisher < Data ? , Error > {
76- _readValueForCharacteristic ( characteristic)
138+ delegate
139+ . didUpdateValueForCharacteristic
140+ . filterFirstValueOrThrow ( where: {
141+ $0. uuid == characteristic. uuid
142+ } )
143+ . map ( \. value)
144+ . handleEvents ( receiveSubscription: { [ _readValueForCharacteristic] _ in
145+ _readValueForCharacteristic ( characteristic)
146+ } )
147+ . shareCurrentValue ( )
148+ . eraseToAnyPublisher ( )
77149 }
78150
79151 public func maximumWriteValueLength( for writeType: CBCharacteristicWriteType ) -> Int {
80152 _maximumWriteValueLength ( writeType)
81153 }
82154
83155 public func writeValue( _ value: Data , for characteristic: CBCharacteristic , type writeType: CBCharacteristicWriteType ) -> AnyPublisher < Void , Error > {
84- _writeValueForCharacteristic ( value, characteristic, writeType)
156+ if writeType == . withoutResponse {
157+ return writeValueWithoutResponse ( value, for: characteristic)
158+ } else {
159+ return writeValueWithResponse ( value, for: characteristic)
160+ }
161+ }
162+
163+ private func writeValueWithoutResponse( _ value: Data , for characteristic: CBCharacteristic ) -> AnyPublisher < Void , Error > {
164+ if characteristic. properties. contains ( . writeWithoutResponse) {
165+ // Return an empty publisher here, since we never expect to receive a response.
166+ return Empty ( )
167+ . handleEvents ( receiveSubscription: { [ _writeValueForCharacteristic] _ in
168+ _writeValueForCharacteristic ( value, characteristic, . withoutResponse)
169+ } )
170+ . eraseToAnyPublisher ( )
171+ } else {
172+ // a response-less write against a characteristic that doesn't support it is silently ignored
173+ // by core bluetooth and never sends to the peripheral, so surface that case with an error here instead.
174+ return Fail (
175+ error: NSError (
176+ domain: CBATTErrorDomain,
177+ code: CBATTError . writeNotPermitted. rawValue,
178+ userInfo: [
179+ NSLocalizedDescriptionKey: " Writing without response is not permitted. "
180+ ]
181+ )
182+ )
183+ . eraseToAnyPublisher ( )
184+ }
185+ }
186+
187+ private func writeValueWithResponse( _ value: Data , for characteristic: CBCharacteristic ) -> AnyPublisher < Void , Error > {
188+ delegate
189+ . didWriteValueForCharacteristic
190+ . filterFirstValueOrThrow ( where: {
191+ $0. uuid == characteristic. uuid
192+ } )
193+ . map { _ in }
194+ . handleEvents ( receiveSubscription: { [ _writeValueForCharacteristic] _ in
195+ _writeValueForCharacteristic ( value, characteristic, . withResponse)
196+ } )
197+ . shareCurrentValue ( )
198+ . eraseToAnyPublisher ( )
85199 }
86200
87201 public func setNotifyValue( _ enabled: Bool , for characteristic: CBCharacteristic ) -> AnyPublisher < Void , Error > {
88- _setNotifyValue ( enabled, characteristic)
202+ delegate
203+ . didUpdateNotificationState
204+ . filterFirstValueOrThrow ( where: {
205+ $0. uuid == characteristic. uuid
206+ } )
207+ . map { _ in }
208+ . handleEvents ( receiveSubscription: { [ _setNotifyValue] _ in
209+ _setNotifyValue ( enabled, characteristic)
210+ } )
211+ . shareCurrentValue ( )
212+ . eraseToAnyPublisher ( )
89213 }
90214
91215 public func discoverDescriptors( for characteristic: CBCharacteristic ) -> AnyPublisher < [ CBDescriptor ] ? , Error > {
92- _discoverDescriptors ( characteristic)
216+ delegate
217+ . didDiscoverDescriptorsForCharacteristic
218+ . filterFirstValueOrThrow ( where: {
219+ $0. uuid == characteristic. uuid
220+ } )
221+ . map ( \. descriptors)
222+ . handleEvents ( receiveSubscription: { [ _discoverDescriptors] _ in
223+ _discoverDescriptors ( characteristic)
224+ } )
225+ . shareCurrentValue ( )
226+ . eraseToAnyPublisher ( )
93227 }
94228
95229 public func readValue( for descriptor: CBDescriptor ) -> AnyPublisher < Any ? , Error > {
96- _readValueForDescriptor ( descriptor)
230+ delegate
231+ . didUpdateValueForDescriptor
232+ . filterFirstValueOrThrow ( where: {
233+ $0. uuid == descriptor. uuid
234+ } )
235+ . map ( \. value)
236+ . handleEvents ( receiveSubscription: { [ _readValueForDescriptor] _ in
237+ _readValueForDescriptor ( descriptor)
238+ } )
239+ . shareCurrentValue ( )
240+ . eraseToAnyPublisher ( )
97241 }
98242
99243 public func writeValue( _ value: Data , for descriptor: CBDescriptor ) -> AnyPublisher < Void , Error > {
100- _writeValueForDescriptor ( value, descriptor)
244+ delegate
245+ . didWriteValueForDescriptor
246+ . filterFirstValueOrThrow ( where: {
247+ $0. uuid == descriptor. uuid
248+ } )
249+ . map { _ in }
250+ . handleEvents ( receiveSubscription: { [ _writeValueForDescriptor] _ in
251+ _writeValueForDescriptor ( value, descriptor)
252+ } )
253+ . shareCurrentValue ( )
254+ . eraseToAnyPublisher ( )
101255 }
102256
103257 public func openL2CAPChannel( _ psm: CBL2CAPPSM ) -> AnyPublisher < L2CAPChannel , Error > {
104- _openL2CAPChannel ( psm)
258+ delegate
259+ . didOpenChannel
260+ . filterFirstValueOrThrow ( where: { channel, error in
261+ return channel? . psm == psm || error != nil
262+ } )
263+ // we won't get here unless channel is not nil, so we can safely force-unwrap
264+ . map { $0! }
265+ . handleEvents ( receiveSubscription: { [ _openL2CAPChannel] _ in
266+ _openL2CAPChannel ( psm)
267+ } )
268+ . shareCurrentValue ( )
269+ . eraseToAnyPublisher ( )
105270 }
271+ }
106272
107- public func listenForUpdates( on characteristic: CBCharacteristic ) -> AnyPublisher < Data ? , Error > {
108- _listenForUpdatesToCharacteristic ( characteristic)
273+ extension Peripheral {
274+ public class Delegate : NSObject {
275+ let nameUpdates : PassthroughSubject < String ? , Never > = . init( )
276+ let didInvalidateServices : PassthroughSubject < [ CBService ] , Never > = . init( )
277+ let didReadRSSI : PassthroughSubject < Result < Double , Error > , Never > = . init( )
278+ let didDiscoverServices : PassthroughSubject < ( [ CBService ] , Error ? ) , Never > = . init( )
279+ let didDiscoverIncludedServices : PassthroughSubject < ( CBService , Error ? ) , Never > = . init( )
280+ let didDiscoverCharacteristics : PassthroughSubject < ( CBService , Error ? ) , Never > = . init( )
281+ let didUpdateValueForCharacteristic : PassthroughSubject < ( CBCharacteristic , Error ? ) , Never > = . init( )
282+ let didWriteValueForCharacteristic : PassthroughSubject < ( CBCharacteristic , Error ? ) , Never > = . init( )
283+ let didUpdateNotificationState : PassthroughSubject < ( CBCharacteristic , Error ? ) , Never > = . init( )
284+ let didDiscoverDescriptorsForCharacteristic : PassthroughSubject < ( CBCharacteristic , Error ? ) , Never > = . init( )
285+ let didUpdateValueForDescriptor : PassthroughSubject < ( CBDescriptor , Error ? ) , Never > = . init( )
286+ let didWriteValueForDescriptor : PassthroughSubject < ( CBDescriptor , Error ? ) , Never > = . init( )
287+ let isReadyToSendWriteWithoutResponse : PassthroughSubject < Void , Never > = . init( )
288+ let didOpenChannel : PassthroughSubject < ( L2CAPChannel ? , Error ? ) , Never > = . init( )
109289 }
110290}
111291
0 commit comments