Skip to content

Commit cedd785

Browse files
authored
Merge pull request #327 from vladiulianbogdan/fix/discover_processes_when_disconnect
Fix for problem when service discovery process gets stuck
2 parents 7564521 + be52d6d commit cedd785

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed

Source/Peripheral.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ public class Peripheral {
6262
}
6363

6464
private func setupSubjects() {
65+
manager.delegateWrapper
66+
.didDisconnectPeripheral
67+
.filter { [weak self] peripheral, _ in
68+
peripheral.uuidIdentifier == self?.peripheral.uuidIdentifier
69+
}
70+
.subscribe(onNext: { [weak self] _ in
71+
self?.remainingServicesDiscoveryRequest.writeSync { value in
72+
value = 0
73+
}
74+
75+
self?.remainingIncludedServicesDiscoveryRequest.writeSync { array in
76+
array.removeAll()
77+
}
78+
79+
self?.remainingCharacteristicsDiscoveryRequest.writeSync { array in
80+
array.removeAll()
81+
}
82+
})
83+
.disposed(by: disposeBag)
84+
6585
delegateWrapper.peripheralDidDiscoverServices.subscribe { [weak self] event in
6686
self?.remainingServicesDiscoveryRequest.writeSync { value in
6787
if value > 0 {
@@ -843,7 +863,7 @@ public class Peripheral {
843863

844864
/// Function that merges given observable with error streams of invalid Central Manager states.
845865
/// - parameter observable: `Observable` to be transformed
846-
/// - returns: Source `Observable` which listens on state chnage errors as well
866+
/// - returns: Source `Observable` which listens on state change errors as well
847867
func ensureValidPeripheralState<T>(for observable: Observable<T>) -> Observable<T> {
848868
return Observable<T>.absorb(
849869
manager.ensurePeripheralIsConnected(self),

Tests/Autogenerated/_Peripheral.generated.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ class _Peripheral {
6363
}
6464

6565
private func setupSubjects() {
66+
manager.delegateWrapper
67+
.didDisconnectPeripheral
68+
.filter { [weak self] peripheral, _ in
69+
peripheral.uuidIdentifier == self?.peripheral.uuidIdentifier
70+
}
71+
.subscribe(onNext: { [weak self] _ in
72+
self?.remainingServicesDiscoveryRequest.writeSync { value in
73+
value = 0
74+
}
75+
76+
self?.remainingIncludedServicesDiscoveryRequest.writeSync { array in
77+
array.removeAll()
78+
}
79+
80+
self?.remainingCharacteristicsDiscoveryRequest.writeSync { array in
81+
array.removeAll()
82+
}
83+
})
84+
.disposed(by: disposeBag)
85+
6686
delegateWrapper.peripheralDidDiscoverServices.subscribe { [weak self] event in
6787
self?.remainingServicesDiscoveryRequest.writeSync { value in
6888
if value > 0 {
@@ -844,7 +864,7 @@ class _Peripheral {
844864

845865
/// Function that merges given observable with error streams of invalid Central Manager states.
846866
/// - parameter observable: `Observable` to be transformed
847-
/// - returns: Source `Observable` which listens on state chnage errors as well
867+
/// - returns: Source `Observable` which listens on state change errors as well
848868
func ensureValidPeripheralState<T>(for observable: Observable<T>) -> Observable<T> {
849869
return Observable<T>.absorb(
850870
manager.ensurePeripheralIsConnected(self),

Tests/PeripheralTest+CharacteristicsDiscover.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,44 @@ class PeripheralCharacteristicsDiscoverTest: BasePeripheralTest {
158158
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
159159
}
160160

161+
func testDiscoverCharacteristicsForConnectedPeripheralAfterADisconnection() {
162+
let mockCharacteristics = [
163+
createCharacteristic(uuid: "0x0000"),
164+
createCharacteristic(uuid: "0x0001")
165+
]
166+
167+
let obs: ScheduledObservable<[_Characteristic]> = testScheduler.scheduleObservable {
168+
self.peripheral.discoverCharacteristics(nil, for: self.service).asObservable()
169+
}
170+
171+
testScheduler.scheduleAt(subscribeTime + 100) {
172+
self.centralManagerMock.delegateWrapper.didDisconnectPeripheral.onNext((self.peripheral.peripheral, nil))
173+
}
174+
175+
testScheduler.advanceTo(subscribeTime + 100)
176+
177+
XCTAssertEqual(obs.events.count, 1, "should receive one event")
178+
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
179+
180+
// Check that after reconnection the service discovery still works
181+
let time = ObservableScheduleTimes(createTime: subscribeTime + 200, subscribeTime: subscribeTime + 300, disposeTime: subscribeTime + 1000)
182+
let obs1: ScheduledObservable<[_Characteristic]> = testScheduler.scheduleObservable(time: time) {
183+
self.peripheral.discoverCharacteristics(nil, for: self.service).asObservable()
184+
}
185+
186+
testScheduler.advanceTo(subscribeTime + 400)
187+
188+
testScheduler.scheduleAt(subscribeTime + 500) {
189+
self.service.service.characteristics = mockCharacteristics
190+
self.peripheral.delegateWrapper.peripheralDidDiscoverCharacteristicsForService.asObserver().onNext((self.service.service, nil))
191+
}
192+
193+
testScheduler.advanceTo(subscribeTime + 600)
194+
195+
XCTAssertEqual(peripheral.peripheral.discoverCharacteristicsParams.count, 2, "should call discover characteristics twice")
196+
XCTAssertCharacteristicsList(observable: obs1, uuids: mockCharacteristics.map { $0.uuid })
197+
}
198+
161199
func testDiscoverCharacteristicsForDisabledBluetooth() {
162200
let obs: ScheduledObservable<[_Characteristic]> = testScheduler.scheduleObservable {
163201
self.peripheral.discoverCharacteristics(nil, for: self.service).asObservable()

Tests/PeripheralTest+IncludedServices.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,43 @@ class PeripheralIncludedServicesTest: BasePeripheralTest {
156156
XCTAssertEqual(obs.events.count, 1, "should receive one event")
157157
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
158158
}
159+
160+
func testDiscoverIncludedServicesForConnectedPeripheralAfterADisconnection() {
161+
let mockServices = [
162+
createService(uuid: "0x0000")
163+
]
164+
165+
let obs: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable {
166+
self.peripheral.discoverIncludedServices(nil, for: self.service).asObservable()
167+
}
168+
169+
testScheduler.scheduleAt(subscribeTime + 100) {
170+
self.centralManagerMock.delegateWrapper.didDisconnectPeripheral.onNext((self.peripheral.peripheral, nil))
171+
}
172+
173+
testScheduler.advanceTo(subscribeTime + 100)
174+
175+
XCTAssertEqual(obs.events.count, 1, "should receive one event")
176+
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
177+
178+
// Check that after reconnection the service discovery still works
179+
let time = ObservableScheduleTimes(createTime: subscribeTime + 200, subscribeTime: subscribeTime + 300, disposeTime: subscribeTime + 1000)
180+
let obs1: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable(time: time) {
181+
self.peripheral.discoverIncludedServices(nil, for: self.service).asObservable()
182+
}
183+
184+
testScheduler.advanceTo(subscribeTime + 400)
185+
186+
testScheduler.scheduleAt(subscribeTime + 500) {
187+
self.service.service.includedServices = mockServices
188+
self.peripheral.delegateWrapper.peripheralDidDiscoverIncludedServicesForService.onNext((self.service.service, nil))
189+
}
190+
191+
testScheduler.advanceTo(subscribeTime + 600)
192+
193+
XCTAssertEqual(peripheral.peripheral.discoverIncludedServicesParams.count, 2, "should call discover services for the peripheral")
194+
XCTAssertServiceList(observable: obs1, uuids: mockServices.map { $0.uuid })
195+
}
159196

160197
func testDiscoverIncludedServicesForDisabledBluetooth() {
161198
let obs: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable {

Tests/PeripheralTest+Service.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,44 @@ class PeripheralServiceTest: BasePeripheralTest {
155155
XCTAssertEqual(obs.events.count, 1, "should receive one event")
156156
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
157157
}
158+
159+
func testDiscoverServicesForConnectedPeripheralAfterADisconnection() {
160+
let mockServices = [
161+
createService(uuid: "0x0000"),
162+
createService(uuid: "0x0001")
163+
]
164+
165+
let obs: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable {
166+
self.peripheral.discoverServices(nil).asObservable()
167+
}
168+
169+
testScheduler.scheduleAt(subscribeTime + 100) {
170+
self.centralManagerMock.delegateWrapper.didDisconnectPeripheral.onNext((self.peripheral.peripheral, nil))
171+
}
172+
173+
testScheduler.advanceTo(subscribeTime + 100)
174+
175+
XCTAssertEqual(obs.events.count, 1, "should receive one event")
176+
XCTAssertError(obs.events[0].value, _BluetoothError.peripheralDisconnected(peripheral, nil), "should receive peripheral disconnected error")
177+
178+
// Check that after reconnection the service discovery still works
179+
let time = ObservableScheduleTimes(createTime: subscribeTime + 200, subscribeTime: subscribeTime + 300, disposeTime: subscribeTime + 1000)
180+
let obs1: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable(time: time) {
181+
self.peripheral.discoverServices(nil).asObservable()
182+
}
183+
184+
testScheduler.advanceTo(subscribeTime + 400)
185+
186+
testScheduler.scheduleAt(subscribeTime + 500) {
187+
self.peripheral.peripheral.services = mockServices
188+
self.peripheral.delegateWrapper.peripheralDidDiscoverServices.asObserver().onNext((mockServices, nil))
189+
}
190+
191+
testScheduler.advanceTo(subscribeTime + 600)
192+
193+
XCTAssertEqual(peripheral.peripheral.discoverServicesParams.count, 2, "should call discover services for the peripheral")
194+
XCTAssertServiceList(observable: obs1, uuids: mockServices.map { $0.uuid })
195+
}
158196

159197
func testDiscoverServicesForDisabledBluetooth() {
160198
let obs: ScheduledObservable<[_Service]> = testScheduler.scheduleObservable {

0 commit comments

Comments
 (0)