Skip to content

Commit ac18350

Browse files
authored
Merge pull request #257 from Polidea/feature/peripheral_manager
Support for CBPeripheralManager
2 parents b6720a9 + 5854363 commit ac18350

39 files changed

+2411
-295
lines changed

.swiftlint.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ excluded:
1515
- Carthage
1616
- ExampleApp
1717
- Tests
18+
- .build
1819

1920

20-
line_length: 120
21+
line_length:
22+
warning: 120
23+
ignores_comments: true
2124
# they can set both implicitly with an array
2225
type_body_length:
2326
- 300 # warning

HOW_TO_RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- add new change log to `Changelog.md`
55
- change `RxBluetoothKit.podspec` with new library version
66
- generate new doc by running script `./scripts/generate-docs.sh x.x.x`
7-
- push new commit with that changes
7+
- create new Pull Request with that changes and merge it
88
- create new release on github
99
- create archive by running `carthage build --no-skip-current && carthage archive RxBluetoothKit`
1010
- add those archive to github release that was created before

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ Follow [Polidea's Blog](https://www.polidea.com/blog/RxBluetoothKit_The_most_sim
2323

2424
## Features
2525
- [x] CBCentralManger RxSwift support
26+
- [x] CBPeripheralManger RxSwift support
2627
- [x] CBPeripheral RxSwift support
2728
- [x] Scan sharing
2829
- [x] Scan queueing
2930
- [x] Bluetooth error bubbling
3031

3132
## Sample
32-
In Example folder you can find application we've provided to you. It's a great place to dig in, once you want to see everything in action. App provides most of the common usages of RxBluetoothKit.
33+
In ExampleApp folder you can find application we've provided to you. It's a great place to dig in, once you want to see everything in action. App provides most of the common usages of RxBluetoothKit.
3334

3435
## Installation
3536

@@ -60,6 +61,7 @@ Library is built on top of Apple's CoreBluetooth.
6061
It has multiple components, that should be familiar to you:
6162

6263
- CentralManager
64+
- PeripheralManager
6365
- ScannedPeripheral
6466
- Peripheral
6567
- Service

RxBluetoothKit.xcodeproj/project.pbxproj

Lines changed: 156 additions & 4 deletions
Large diffs are not rendered by default.

Source/BluetoothError.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public enum BluetoothError: Error {
99
case destroyed
1010
// Emitted when `CentralManager.scanForPeripherals` called and there is already ongoing scan
1111
case scanInProgress
12+
// Emitted when `PeripheralManager.startAdvertising` called and there is already ongoing advertisement
13+
case advertisingInProgress
14+
case advertisingStartFailed(Error)
1215
// States
1316
case bluetoothUnsupported
1417
case bluetoothUnauthorized
@@ -25,6 +28,7 @@ public enum BluetoothError: Error {
2528
// Services
2629
case servicesDiscoveryFailed(Peripheral, Error?)
2730
case includedServicesDiscoveryFailed(Peripheral, Error?)
31+
case addingServiceFailed(CBService, Error?)
2832
// Characteristics
2933
case characteristicsDiscoveryFailed(Service, Error?)
3034
case characteristicWriteFailed(Characteristic, Error?)
@@ -35,8 +39,9 @@ public enum BluetoothError: Error {
3539
case descriptorsDiscoveryFailed(Characteristic, Error?)
3640
case descriptorWriteFailed(Descriptor, Error?)
3741
case descriptorReadFailed(Descriptor, Error?)
38-
//L2CAP
42+
// L2CAP
3943
case openingL2CAPChannelFailed(Peripheral, Error?)
44+
case publishingL2CAPChannelFailed(CBL2CAPPSM, Error?)
4045
}
4146

4247
extension BluetoothError: CustomStringConvertible {
@@ -54,6 +59,13 @@ extension BluetoothError: CustomStringConvertible {
5459
Tried to scan for peripheral when there is already ongoing scan.
5560
You can have only 1 ongoing scanning, please check documentation of CentralManager for more details
5661
"""
62+
case .advertisingInProgress:
63+
return """
64+
Tried to advertise when there is already advertising ongoing.
65+
You can have only 1 ongoing advertising, please check documentation of PeripheralManager for more details
66+
"""
67+
case let .advertisingStartFailed(err):
68+
return "Start advertising error occured: \(err.localizedDescription)"
5769
case .bluetoothUnsupported:
5870
return "Bluetooth is unsupported"
5971
case .bluetoothUnauthorized:
@@ -82,6 +94,8 @@ extension BluetoothError: CustomStringConvertible {
8294
return "Services discovery error has occured: \(err?.localizedDescription ?? "-")"
8395
case let .includedServicesDiscoveryFailed(_, err):
8496
return "Included services discovery error has occured: \(err?.localizedDescription ?? "-")"
97+
case let .addingServiceFailed(_, err):
98+
return "Adding PeripheralManager service error has occured: \(err?.localizedDescription ?? "-")"
8599
// Characteristics
86100
case let .characteristicsDiscoveryFailed(_, err):
87101
return "Characteristics discovery error has occured: \(err?.localizedDescription ?? "-")"
@@ -102,6 +116,8 @@ extension BluetoothError: CustomStringConvertible {
102116
return "Descriptor read error has occured: \(err?.localizedDescription ?? "-")"
103117
case let .openingL2CAPChannelFailed(_, err):
104118
return "Opening L2CAP channel error has occured: \(err?.localizedDescription ?? "-")"
119+
case let .publishingL2CAPChannelFailed(_, err):
120+
return "Publishing L2CAP channel error has occured: \(err?.localizedDescription ?? "-")"
105121
}
106122
}
107123
}
@@ -132,6 +148,8 @@ extension BluetoothError: Equatable {}
132148
public func == (lhs: BluetoothError, rhs: BluetoothError) -> Bool {
133149
switch (lhs, rhs) {
134150
case (.scanInProgress, .scanInProgress): return true
151+
case (.advertisingInProgress, .advertisingInProgress): return true
152+
case (.advertisingStartFailed, .advertisingStartFailed): return true
135153
// States
136154
case (.bluetoothUnsupported, .bluetoothUnsupported): return true
137155
case (.bluetoothUnauthorized, .bluetoothUnauthorized): return true
@@ -141,6 +159,7 @@ public func == (lhs: BluetoothError, rhs: BluetoothError) -> Bool {
141159
// Services
142160
case let (.servicesDiscoveryFailed(l, _), .servicesDiscoveryFailed(r, _)): return l == r
143161
case let (.includedServicesDiscoveryFailed(l, _), .includedServicesDiscoveryFailed(r, _)): return l == r
162+
case let (.addingServiceFailed(l, _), .addingServiceFailed(r, _)): return l == r
144163
// Peripherals
145164
case let (.peripheralIsAlreadyObservingConnection(l), .peripheralIsAlreadyObservingConnection(r)): return l == r
146165
case let (.peripheralIsConnectingOrAlreadyConnected(l), .peripheralIsConnectingOrAlreadyConnected(r)): return l == r
@@ -161,6 +180,7 @@ public func == (lhs: BluetoothError, rhs: BluetoothError) -> Bool {
161180
case let (.descriptorReadFailed(l, _), .descriptorReadFailed(r, _)): return l == r
162181
// L2CAP
163182
case let (.openingL2CAPChannelFailed(l, _), .openingL2CAPChannelFailed(r, _)): return l == r
183+
case let (.publishingL2CAPChannelFailed(l, _), .publishingL2CAPChannelFailed(r, _)): return l == r
164184
default: return false
165185
}
166186
}

Source/CBCentral+Uuid.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
import CoreBluetooth
3+
4+
extension CBCentral {
5+
/// There is still no identifier property for macOS, that's why we need to retrieve it by value method
6+
var uuidIdentifier: UUID {
7+
return value(forKey: "identifier") as! NSUUID as UUID
8+
}
9+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
import CoreBluetooth
3+
import RxSwift
4+
5+
class CBPeripheralManagerDelegateWrapper: NSObject, CBPeripheralManagerDelegate {
6+
7+
let didUpdateState = PublishSubject<BluetoothState>()
8+
let isReady = PublishSubject<Void>()
9+
let didStartAdvertising = PublishSubject<Error?>()
10+
let didReceiveRead = PublishSubject<CBATTRequest>()
11+
let willRestoreState = ReplaySubject<[String: Any]>.create(bufferSize: 1)
12+
let didAddService = PublishSubject<(CBService, Error?)>()
13+
let didReceiveWrite = PublishSubject<[CBATTRequest]>()
14+
let didSubscribeTo = PublishSubject<(CBCentral, CBCharacteristic)>()
15+
let didUnsubscribeFrom = PublishSubject<(CBCentral, CBCharacteristic)>()
16+
let didPublishL2CAPChannel = PublishSubject<(CBL2CAPPSM, Error?)>()
17+
let didUnpublishL2CAPChannel = PublishSubject<(CBL2CAPPSM, Error?)>()
18+
private var _didOpenChannel: Any?
19+
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
20+
var didOpenChannel: PublishSubject<(CBL2CAPChannel?, Error?)> {
21+
if _didOpenChannel == nil {
22+
_didOpenChannel = PublishSubject<(CBL2CAPChannel?, Error?)>()
23+
}
24+
return _didOpenChannel as! PublishSubject<(CBL2CAPChannel?, Error?)>
25+
}
26+
27+
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
28+
guard let bleState = BluetoothState(rawValue: peripheral.state.rawValue) else { return }
29+
RxBluetoothKitLog.d("\(peripheral.logDescription) didUpdateState(state: \(bleState.logDescription))")
30+
didUpdateState.onNext(bleState)
31+
}
32+
33+
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
34+
RxBluetoothKitLog.d("\(peripheral.logDescription) isReady()")
35+
isReady.onNext(())
36+
}
37+
38+
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
39+
RxBluetoothKitLog.d("\(peripheral.logDescription) didStartAdvertising(error: \(String(describing: error)))")
40+
didStartAdvertising.onNext(error)
41+
}
42+
43+
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
44+
RxBluetoothKitLog.d("\(peripheral.logDescription) didReceiveRead(request: \(request.logDescription))")
45+
didReceiveRead.onNext(request)
46+
}
47+
48+
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String: Any]) {
49+
RxBluetoothKitLog.d("\(peripheral.logDescription) willRestoreState(dict: \(dict))")
50+
willRestoreState.onNext(dict)
51+
}
52+
53+
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
54+
RxBluetoothKitLog.d("""
55+
\(peripheral.logDescription)
56+
didAdd(service: \(service.logDescription), error: \(String(describing: error))
57+
""")
58+
didAddService.onNext((service, error))
59+
}
60+
61+
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
62+
let requestsLog = requests.reduce("[", { $0 + $1.logDescription + "," }).dropLast().appending("]")
63+
RxBluetoothKitLog.d("\(peripheral.logDescription) didReceiveWrite(requests: \(requestsLog))")
64+
didReceiveWrite.onNext(requests)
65+
}
66+
67+
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral,
68+
didSubscribeTo characteristic: CBCharacteristic) {
69+
RxBluetoothKitLog.d("""
70+
\(peripheral.logDescription)
71+
didSubscribeTo(central: \(central.logDescription), characteristic: \(characteristic.logDescription))
72+
""")
73+
didSubscribeTo.onNext((central, characteristic))
74+
}
75+
76+
func peripheralManager(_ peripheral: CBPeripheralManager,
77+
central: CBCentral,
78+
didUnsubscribeFrom characteristic: CBCharacteristic) {
79+
RxBluetoothKitLog.d("""
80+
\(peripheral.logDescription)
81+
didUnsubscribeFrom(central: \(central.logDescription), characteristic: \(characteristic.logDescription))
82+
""")
83+
didUnsubscribeFrom.onNext((central, characteristic))
84+
}
85+
86+
func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {
87+
RxBluetoothKitLog.d("""
88+
\(peripheral.logDescription) didPublishL2CAPChannel(PSM: \(PSM), error: \(String(describing: error))
89+
""")
90+
didPublishL2CAPChannel.onNext((PSM, error))
91+
}
92+
93+
func peripheralManager(_ peripheral: CBPeripheralManager, didUnpublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {
94+
RxBluetoothKitLog.d("""
95+
\(peripheral.logDescription) didUnpublishL2CAPChannel(PSM: \(PSM), error: \(String(describing: error))
96+
""")
97+
didUnpublishL2CAPChannel.onNext((PSM, error))
98+
}
99+
100+
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)
101+
func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) {
102+
RxBluetoothKitLog.d("""
103+
\(peripheral.logDescription)
104+
didOpen(channel: \(channel?.logDescription ?? "nil"), error: \(String(describing: error))
105+
""")
106+
didOpenChannel.onNext((channel, error))
107+
}
108+
}

Source/CentralManager+RestoredState.swift

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@ import Foundation
22
import RxSwift
33
import CoreBluetooth
44

5-
/// Closure that receives `RestoredState` as a parameter
5+
@available(*, deprecated: 5.1.0, renamed: "OnWillRestoreCentralManagerState")
66
public typealias OnWillRestoreState = (RestoredState) -> Void
7+
/// Closure that receives `RestoredState` as a parameter
8+
public typealias OnWillRestoreCentralManagerState = (CentralManagerRestoredState) -> Void
79

810
extension CentralManager {
911

1012
// MARK: State restoration
1113

12-
// swiftlint:disable line_length
14+
/// Deprecated, use CentralManager.init(queue:options:onWillRestoreCentralManagerState:) instead
15+
@available(*, deprecated: 5.1.0, renamed: "CentralManager.init(queue:options:onWillRestoreCentralManagerState:)")
16+
public convenience init(queue: DispatchQueue = .main,
17+
options: [String: AnyObject]? = nil,
18+
onWillRestoreState: OnWillRestoreState? = nil) {
19+
self.init(queue: queue, options: options)
20+
if let onWillRestoreState = onWillRestoreState {
21+
listenOnWillRestoreState(onWillRestoreState)
22+
}
23+
}
1324

1425
/// Creates new `CentralManager` instance, which supports bluetooth state restoration.
1526
/// - warning: If you pass background queue to the method make sure to observe results on main thread
@@ -18,20 +29,18 @@ extension CentralManager {
1829
/// and all operations and events are executed and received on main thread.
1930
/// - parameter options: An optional dictionary containing initialization options for a central manager.
2031
/// For more info about it please refer to [Central Manager initialization options](https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CBCentralManager_Class/index.html)
21-
/// - parameter onWillRestoreState: Closure called when state has been restored.
32+
/// - parameter onWillRestoreCentralManagerState: Closure called when state has been restored.
2233
///
23-
/// - seealso: `RestoredState`
34+
/// - seealso: `OnWillRestoreCentralManagerState`
2435
public convenience init(queue: DispatchQueue = .main,
2536
options: [String: AnyObject]? = nil,
26-
onWillRestoreState: OnWillRestoreState? = nil) {
37+
onWillRestoreCentralManagerState: OnWillRestoreCentralManagerState? = nil) {
2738
self.init(queue: queue, options: options)
28-
if let onWillRestoreState = onWillRestoreState {
29-
listenOnWillRestoreState(onWillRestoreState)
39+
if let onWillRestoreCentralManagerState = onWillRestoreCentralManagerState {
40+
listenOnWillRestoreState(onWillRestoreCentralManagerState)
3041
}
3142
}
3243

33-
// swiftlint:enable line_length
34-
3544
/// Creates new `CentralManager`
3645
/// - parameter centralManager: Central instance which is used to perform all of the necessary operations
3746
/// - parameter delegateWrapper: Wrapper on CoreBluetooth's central manager callbacks.
@@ -43,28 +52,38 @@ extension CentralManager {
4352
delegateWrapper: CBCentralManagerDelegateWrapper,
4453
peripheralProvider: PeripheralProvider,
4554
connector: Connector,
46-
onWillRestoreState: @escaping OnWillRestoreState
47-
) {
55+
onWillRestoreCentralManagerState: @escaping OnWillRestoreCentralManagerState
56+
) {
4857
self.init(
4958
centralManager: centralManager,
5059
delegateWrapper: delegateWrapper,
5160
peripheralProvider: peripheralProvider,
5261
connector: connector
5362
)
54-
listenOnWillRestoreState(onWillRestoreState)
63+
listenOnWillRestoreState(onWillRestoreCentralManagerState)
64+
}
65+
66+
func listenOnWillRestoreState(_ handler: @escaping OnWillRestoreState) {
67+
_ = restoreStateObservable
68+
.map { RestoredState(centralManagerRestoredState: $0) }
69+
.subscribe(onNext: { handler($0) })
5570
}
5671

5772
/// Emits `RestoredState` instance, when state of `CentralManager` has been restored,
5873
/// Should only be called once in the lifetime of the app
5974
/// - returns: Observable which emits next events state has been restored
60-
func listenOnWillRestoreState(_ handler: @escaping OnWillRestoreState) {
61-
_ = delegateWrapper
75+
func listenOnWillRestoreState(_ handler: @escaping OnWillRestoreCentralManagerState) {
76+
_ = restoreStateObservable
77+
.subscribe(onNext: { handler($0) })
78+
}
79+
80+
var restoreStateObservable: Observable<CentralManagerRestoredState> {
81+
return delegateWrapper
6282
.willRestoreState
6383
.take(1)
64-
.flatMap { [weak self] dict -> Observable<RestoredState> in
84+
.flatMap { [weak self] dict -> Observable<CentralManagerRestoredState> in
6585
guard let strongSelf = self else { throw BluetoothError.destroyed }
66-
return .just(RestoredState(restoredStateDictionary: dict, centralManager: strongSelf))
86+
return .just(CentralManagerRestoredState(restoredStateDictionary: dict, centralManager: strongSelf))
6787
}
68-
.subscribe(onNext: { handler($0) })
6988
}
7089
}

0 commit comments

Comments
 (0)