Skip to content

Commit 3c8908b

Browse files
author
Jeremy Chiang
authored
v0.8.2 - Add initial support for did modify services (#193)
* Add initial support for did modify services delegation, #144 * Bump version to 0.8.2 * Update changelog for 0.8.2 * Update readme and jazzy doc for 0.8.2
1 parent 320dc60 commit 3c8908b

File tree

116 files changed

+1532
-136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+1532
-136
lines changed

Bluejay.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
Pod::Spec.new do |spec|
22
spec.name = 'Bluejay'
3-
spec.version = '0.8.1'
3+
spec.version = '0.8.2'
44
spec.license = { type: 'MIT', file: 'LICENSE' }
55
spec.homepage = 'https://github.com/steamclock/bluejay'
66
spec.authors = { 'Jeremy Chiang' => 'jeremy@steamclock.com' }
77
spec.summary = 'Bluejay is a simple Swift framework for building reliable Bluetooth apps.'
88
spec.homepage = 'https://github.com/steamclock/bluejay'
9-
spec.source = { git: 'https://github.com/steamclock/bluejay.git', tag: 'v0.8.1' }
9+
spec.source = { git: 'https://github.com/steamclock/bluejay.git', tag: 'v0.8.2' }
1010
spec.source_files = 'Bluejay/Bluejay/*.{h,swift}'
1111
spec.framework = 'SystemConfiguration'
1212
spec.platform = :ios, '10.0'

Bluejay/.jazzy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ output: ../docs
22
author: Steamclock Software
33
author_url: http://steamclock.com
44
module: Bluejay
5-
module_version: 0.8.1
5+
module_version: 0.8.2
66
readme: ../README.md
77
sdk: iphone
88
copyright: Copyright © 2017 Steamclock Software. All rights reserved.

Bluejay/Bluejay.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
B8383B7221C3239B00F07306 /* PeripheralDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8383B7121C3239B00F07306 /* PeripheralDelegate.swift */; };
2626
B83A67CE219F45C300076B9F /* BackgroundRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83A67CD219F45C300076B9F /* BackgroundRestorer.swift */; };
2727
B842AAF81E4CF74300BB32EE /* String+Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B842AAF71E4CF74300BB32EE /* String+Transferable.swift */; };
28+
B843657E221F73FC00990C83 /* ServiceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B843657D221F73FC00990C83 /* ServiceObserver.swift */; };
2829
B86586A9216D1105002E8E2D /* StartOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86586A8216D1105002E8E2D /* StartOptions.swift */; };
2930
B869A2971E721D50003C1278 /* Data+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B869A2961E721D50003C1278 /* Data+Sendable.swift */; };
3031
B87CBB4D21C05F8400B67B5B /* BackgroundRestoreConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87CBB4C21C05F8400B67B5B /* BackgroundRestoreConfig.swift */; };
@@ -152,6 +153,7 @@
152153
B8383B7121C3239B00F07306 /* PeripheralDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralDelegate.swift; sourceTree = "<group>"; };
153154
B83A67CD219F45C300076B9F /* BackgroundRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundRestorer.swift; sourceTree = "<group>"; };
154155
B842AAF71E4CF74300BB32EE /* String+Transferable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Transferable.swift"; sourceTree = "<group>"; };
156+
B843657D221F73FC00990C83 /* ServiceObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceObserver.swift; sourceTree = "<group>"; };
155157
B86586A8216D1105002E8E2D /* StartOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartOptions.swift; sourceTree = "<group>"; };
156158
B869A2961E721D50003C1278 /* Data+Sendable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Sendable.swift"; sourceTree = "<group>"; };
157159
B87CBB4C21C05F8400B67B5B /* BackgroundRestoreConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundRestoreConfig.swift; sourceTree = "<group>"; };
@@ -460,6 +462,7 @@
460462
children = (
461463
B8C70FB81E1C725C0006CF58 /* ConnectionObserver.swift */,
462464
B8FE9DF41E73597A00D361CE /* RSSIObserver.swift */,
465+
B843657D221F73FC00990C83 /* ServiceObserver.swift */,
463466
B8DC6C6D21DD7917004A8EA6 /* LogObserver.swift */,
464467
B80D299A2170062D001C3C9B /* DisconnectHandler.swift */,
465468
);
@@ -819,6 +822,7 @@
819822
B8383B7021C31B2D00F07306 /* ListenRestorer.swift in Sources */,
820823
B8022D9C1E1F052F00EA360B /* ListenAction.swift in Sources */,
821824
B8C70F9B1E1C5A910006CF58 /* CharacteristicIdentifier.swift in Sources */,
825+
B843657E221F73FC00990C83 /* ServiceObserver.swift in Sources */,
822826
B869A2971E721D50003C1278 /* Data+Sendable.swift in Sources */,
823827
B8D2078C1E26D446007E670A /* CBPeripheralState+ReturnString.swift in Sources */,
824828
B88F79951EB7DB420094D8D1 /* Operation.swift in Sources */,

Bluejay/Bluejay/Bluejay.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class Bluejay: NSObject { //swiftlint:disable:this type_body_length
3131
/// List of weak references to objects interested in receiving notifications on RSSI reads.
3232
private var rssiObservers: [WeakRSSIObserver] = []
3333

34+
/// List of weak references to objects interested in receiving notifications on services changes.
35+
private var serviceObservers: [WeakServiceObserver] = []
36+
3437
/// List of weak references to objects interested in receiving notifications on log file changes.
3538
private var logObservers: [WeakLogObserver] = []
3639

@@ -496,6 +499,25 @@ public class Bluejay: NSObject { //swiftlint:disable:this type_body_length
496499
rssiObservers = rssiObservers.filter { $0.weakReference != nil && $0.weakReference !== rssiObserver }
497500
}
498501

502+
/**
503+
Register for notifications when a connected peripheral's services change. Unregistering is not required, Bluejay will unregister for you if the observer is no longer in memory.
504+
505+
- Parameter serviceObserver: object interested in receiving the connected peripheral's did modify services event.
506+
*/
507+
public func register(serviceObserver: ServiceObserver) {
508+
serviceObservers = serviceObservers.filter { $0.weakReference != nil && $0.weakReference !== serviceObserver }
509+
serviceObservers.append(WeakServiceObserver(weakReference: serviceObserver))
510+
}
511+
512+
/**
513+
Unregister for notifications when a connected peripheral's services change. Unregistering is not required, Bluejay will unregister for you if the observer is no longer in memory.
514+
515+
- Parameter serviceObserver: object no longer interested in receiving the connected peripheral's did modify services event.
516+
*/
517+
public func unregister(serviceObserver: ServiceObserver) {
518+
serviceObservers = serviceObservers.filter { $0.weakReference != nil && $0.weakReference !== serviceObserver }
519+
}
520+
499521
/**
500522
Register for notifications when the log file is updated. Unregistering is not required, Bluejay will unregister for you if the observer is no longer in memory.
501523

@@ -1535,6 +1557,15 @@ extension Bluejay: PeripheralDelegate {
15351557
observer.weakReference?.didReadRSSI(from: peripheral.identifier, RSSI: RSSI, error: error)
15361558
}
15371559
}
1560+
1561+
func didModifyServices(from peripheral: Peripheral, invalidatedServices: [ServiceIdentifier]) {
1562+
for observer in serviceObservers {
1563+
observer.weakReference?.didModifyServices(
1564+
from: peripheral.identifier,
1565+
invalidatedServices: invalidatedServices
1566+
)
1567+
}
1568+
}
15381569
}
15391570

15401571
let logger = XCGLogger(identifier: "Bluejay", includeDefaultDestinations: true)

Bluejay/Bluejay/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>0.8.0</string>
18+
<string>0.8.2</string>
1919
<key>CFBundleVersion</key>
2020
<string>$(CURRENT_PROJECT_VERSION)</string>
2121
<key>NSPrincipalClass</key>

Bluejay/Bluejay/Peripheral.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,14 @@ extension Peripheral: CBPeripheralDelegate {
333333
delegate.didReadRSSI(from: self, RSSI: RSSI, error: error)
334334
}
335335

336+
/// Called when the peripheral removed or added services.
337+
public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
338+
delegate.didModifyServices(
339+
from: self,
340+
invalidatedServices: invalidatedServices.map {
341+
ServiceIdentifier(uuid: $0.uuid)
342+
}
343+
)
344+
}
345+
336346
}

Bluejay/Bluejay/PeripheralDelegate.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ protocol PeripheralDelegate: class {
3030

3131
/// The peripheral has received a RSSI value and notifies Bluejay.
3232
func didReadRSSI(from peripheral: Peripheral, RSSI: NSNumber, error: Error?)
33+
34+
/// The peripheral's list of available services has changed.
35+
func didModifyServices(from peripheral: Peripheral, invalidatedServices: [ServiceIdentifier])
3336
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// ServiceObserver.swift
3+
// Bluejay
4+
//
5+
// Created by Jeremy Chiang on 2019-02-21.
6+
// Copyright © 2019 Steamclock Software. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
A protocol allowing conforming objects to monitor the services changes of a connected peripheral.
13+
*/
14+
public protocol ServiceObserver: class {
15+
/**
16+
* Called whenever a peripheral's services change.
17+
*
18+
* - Parameters:
19+
* - from: the peripheral that changed services.
20+
* - invalidatedServices: the services invalidated.
21+
*/
22+
func didModifyServices(from peripheral: PeripheralIdentifier, invalidatedServices: [ServiceIdentifier])
23+
}
24+
25+
/// Allows creating weak references to ServiceObserver objects, so that Bluejay does not keep strong references to observers and prevent them from being released in memory.
26+
struct WeakServiceObserver {
27+
weak var weakReference: ServiceObserver?
28+
}

Bluejay/BluejayHeartSensorDemo/SensorViewController.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class SensorViewController: UITableViewController {
2727
override func viewDidLoad() {
2828
super.viewDidLoad()
2929
bluejay.register(connectionObserver: self)
30+
bluejay.register(serviceObserver: self)
3031
}
3132

3233
override func willMove(toParent parent: UIViewController?) {
@@ -228,3 +229,17 @@ extension SensorViewController: ConnectionObserver {
228229
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
229230
}
230231
}
232+
233+
extension SensorViewController: ServiceObserver {
234+
func didModifyServices(from peripheral: PeripheralIdentifier, invalidatedServices: [ServiceIdentifier]) {
235+
bluejay.log("SensorViewController - Invalidated services: \(invalidatedServices.debugDescription)")
236+
237+
if invalidatedServices.contains(where: { invalidatedServiceIdentifier -> Bool in
238+
invalidatedServiceIdentifier == chirpCharacteristic.service
239+
}) {
240+
endListen(to: chirpCharacteristic)
241+
} else if invalidatedServices.isEmpty {
242+
listen(to: chirpCharacteristic)
243+
}
244+
}
245+
}

Bluejay/DittojayHeartSensorDemo/DittojayViewController.swift

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class DittojayViewController: UITableViewController {
2222
var addedServices: [CBService] = []
2323

2424
var heartRate: UInt8 = 0
25+
var timer: Timer?
2526

2627
override func viewDidLoad() {
2728
super.viewDidLoad()
@@ -48,6 +49,12 @@ class DittojayViewController: UITableViewController {
4849
let wakeAppServiceUUID = CBUUID(string: "CED261B7-F120-41C8-9A92-A41DE69CF2A8")
4950
let wakeAppCharacteristicUUID = CBUUID(string: "83B4A431-A6F1-4540-B3EE-3C14AEF71A04")
5051

52+
if addedServices.contains(where: { addedService -> Bool in
53+
addedService.uuid == wakeAppServiceUUID
54+
}) {
55+
return
56+
}
57+
5158
wakeAppCharacteristic = CBMutableCharacteristic(
5259
type: wakeAppCharacteristicUUID,
5360
properties: .notify,
@@ -57,15 +64,35 @@ class DittojayViewController: UITableViewController {
5764
wakeAppService = CBMutableService(type: wakeAppServiceUUID, primary: true)
5865
wakeAppService.characteristics = [wakeAppCharacteristic]
5966

67+
debugPrint("Will add wake app service...")
68+
6069
manager.add(wakeAppService)
6170
}
6271

72+
private func removeWakeAppService() {
73+
addedServices.removeAll { addedService -> Bool in
74+
addedService.uuid == wakeAppService.uuid
75+
}
76+
77+
debugPrint("Will remove wake app service...")
78+
79+
manager.remove(wakeAppService)
80+
81+
if manager.isAdvertising {
82+
debugPrint("Will stop advertising...")
83+
manager.stopAdvertising()
84+
}
85+
86+
debugPrint("Will start advertising...")
87+
advertiseServices([heartRateService.uuid])
88+
}
89+
6390
private func advertiseServices(_ services: [CBUUID]) {
6491
manager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: services])
6592
}
6693

6794
private func startHeartRateSensor() {
68-
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
95+
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
6996
guard let weakSelf = self else {
7097
return
7198
}
@@ -93,7 +120,7 @@ class DittojayViewController: UITableViewController {
93120
}
94121

95122
override func numberOfSections(in tableView: UITableView) -> Int {
96-
return 1
123+
return 2
97124
}
98125

99126
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@@ -103,32 +130,54 @@ class DittojayViewController: UITableViewController {
103130
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
104131
let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath)
105132

106-
if indexPath.row == 0 {
107-
cell.textLabel?.text = "Generated Heart Rate"
108-
cell.detailTextLabel?.text = "\(heartRate)"
109-
110-
DispatchQueue.main.async {
111-
UIView.animate(withDuration: 0.25, animations: {
112-
cell.detailTextLabel?.transform = cell.detailTextLabel!.transform.scaledBy(x: 1.5, y: 1.5)
113-
}, completion: { completed in
114-
if completed {
115-
UIView.animate(withDuration: 0.25) {
116-
cell.detailTextLabel?.transform = CGAffineTransform.identity
133+
if indexPath.section == 0 {
134+
if indexPath.row == 0 {
135+
cell.textLabel?.text = "Generated Heart Rate"
136+
cell.detailTextLabel?.text = "\(heartRate)"
137+
cell.selectionStyle = .none
138+
139+
DispatchQueue.main.async {
140+
UIView.animate(withDuration: 0.25, animations: {
141+
cell.detailTextLabel?.transform = cell.detailTextLabel!.transform.scaledBy(x: 1.5, y: 1.5)
142+
}, completion: { completed in
143+
if completed {
144+
UIView.animate(withDuration: 0.25) {
145+
cell.detailTextLabel?.transform = CGAffineTransform.identity
146+
}
117147
}
118-
}
119-
})
148+
})
149+
}
150+
} else {
151+
cell.textLabel?.text = "Chirp"
152+
cell.detailTextLabel?.text = ""
120153
}
121154
} else {
122-
cell.textLabel?.text = "Chirp"
123-
cell.detailTextLabel?.text = ""
155+
if indexPath.row == 0 {
156+
cell.textLabel?.text = "Add Chirp Service"
157+
cell.detailTextLabel?.text = ""
158+
} else {
159+
cell.textLabel?.text = "Remove Chirp Service"
160+
cell.detailTextLabel?.text = ""
161+
}
124162
}
125163

126164
return cell
127165
}
128166

129167
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
130168
tableView.deselectRow(at: indexPath, animated: true)
131-
chirp()
169+
170+
if indexPath.section == 0 {
171+
if indexPath.row == 1 {
172+
chirp()
173+
}
174+
} else {
175+
if indexPath.row == 0 {
176+
addWakeAppService()
177+
} else {
178+
removeWakeAppService()
179+
}
180+
}
132181
}
133182
}
134183

@@ -146,13 +195,26 @@ extension DittojayViewController: CBPeripheralManagerDelegate {
146195
if let error = error {
147196
debugPrint("Failed to add service \(service.uuid.uuidString) with error: \(error.localizedDescription)")
148197
} else {
149-
debugPrint("Added service \(service.uuid.uuidString)")
150-
151-
addedServices.append(service)
198+
if !addedServices.contains { addedService -> Bool in
199+
addedService.uuid == service.uuid
200+
} {
201+
debugPrint("Added service \(service.uuid.uuidString)")
202+
addedServices.append(service)
203+
}
152204

153205
if addedServices.contains(heartRateService) && addedServices.contains(wakeAppService) {
206+
if manager.isAdvertising {
207+
debugPrint("Will stop advertising...")
208+
manager.stopAdvertising()
209+
}
210+
211+
debugPrint("Will start advertising...")
154212
advertiseServices([heartRateService.uuid, wakeAppService.uuid])
155-
startHeartRateSensor()
213+
214+
if timer == nil {
215+
debugPrint("Will start heart rate sensor...")
216+
startHeartRateSensor()
217+
}
156218
}
157219
}
158220
}

0 commit comments

Comments
 (0)