rightpoint / rzbluetooth Goto Github PK
View Code? Open in Web Editor NEWCore Bluetooth helper library
License: Other
Core Bluetooth helper library
License: Other
Hi there
I just updated my version of RZBluetooth from 0.1 to 1.0.2. There were some pretty big changes, but the migration generally wasn't tough.
One thing I have lost now, that I'm not sure how to replicate, is that I was using the methods in the "Dynamic" interface extension to RZBMockPeripheral in my tests. In particular, I had implemented rzb_writeData... for my test to post a notification containing the packet being written, so that I could catch the packets sent in a flow and check they were correct at a later stage.
Is there any way I can achieve something similar now?
RZBluetooth
offers powerful tools for simulating Bluetooth device interactions. At this time, they mostly serve in-app testing use cases. We would benefit from the ability to test production apps, and companion apps on other platforms, using the same mock peripheral. This issue proposes adding support for simulated Bluetooth devices. Ideally, a mock peripheral could emit real Bluetooth events for other Bluetooth consumers rather than invoke CoreBluetooth
callbacks on the hosting app.
Feedback welcome.
Hey! Love this library, great work =)
I had the need to show a warning when bluetooth is disabled, CBCentralManager happens to have a handy option that does just that. It would be nice to be able to hint to RZBluetooth to including it during initialization!
Enabling and disabling RZBluetooth mocking is not working as I expect.
Steps to reproduce:
RZBEnableMock(true)
)RZBEnableMock(false)
)RZBEnableMock(true)
)CBPeripheralManager
Expected: CBPeripheralManager.mock
is not nil.
Actual: CBPeripheralManager.mock
is nil.
I've attached and example project. It is a mixed Objective-C/Swift framework project since that is how my current project is set up. You can demonstrate the issue by running the tests.
RZBDisableMockExample.zip
Hi there
I recently upgraded a project I'm working on from 1.0.2 to 1.2.2, and tried to run my test suite on my BT logic. One of my tests (which simulates a connection timeout) crashes in RZBSimulatedCallback.m at line 118.
Here's my test to reproduce in an RZBSimulatedTestCase:
-(void)testRZBFail
{
[self.device addHopGateService];
[self.centralManager scanForPeripheralsWithServices:@[[CBUUID hopspotsServiceUUID]] options:nil onDiscoveredPeripheral:^(RZBScanInfo * _Nullable scanInfo, NSError * _Nullable error) {
}];
XCTestExpectation *expect = [self expectationWithDescription:@"Complete"];
[[BFTask taskWithDelay:5] continueWithBlock:^id _Nullable(BFTask * _Nonnull t) {
[self.centralManager stopScan];
[expect fulfill];
return nil;
}];
[self waitForExpectations:@[expect] timeout:10];
}
If you're not familiar with Bolts / BFTask, it's an implementation of Futures / Promises. What I'm doing here is waiting 5 seconds after we start scanning to call stopScan; if I call it instantly we don't crash.
The addHopGateService method is in a category on RZBSimulatedDevice. It looks like this:
-(void)addHopGateService
{
CBMutableService *hopGateService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:hopspotsServiceUUIDString] primary:YES];
CBMutableCharacteristic *hopSpotEventNotifyCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:hopspotsReceiveCharacteristicUUIDString]
properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyIndicate | CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
CBMutableCharacteristic *hopSpotControlCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:hopspotsTransmitCharacteristicUUIDString]
properties:CBCharacteristicPropertyWrite
value:nil
permissions:CBAttributePermissionsWriteable];
hopGateService.characteristics = @[hopSpotEventNotifyCharacteristic, hopSpotControlCharacteristic];
[self addService:hopGateService];
}
This should be enough to reproduce the error. Let me know if I can help.
@cpatterson-lilly @KingOfBrian I am having a small problem... I have a peripheral that always automatically reconnects if I manually call cancelConnectionWithCompletion:
. I have maintainConnection
false for this peripheral right now. Since maintainConnection
is false the connectWithCompletion:
method is not called from within connectionEvent:error:
I see the following in the logs
RZBLogLevelCommand - cancelPeripheralConnection: <PERIPHERAL UUID>
RZBLogLevelDelegate - centralManager:didDisconnectPeripheral:error: - <CBCentralManager: 0x17007dd80> <PERIPHERAL UUID> (null)
RZBLogLevelCommand - connectPeripheral:<PERIPHERAL UUID> options:(null)
RZBLogLevelDelegate - centralManager:didConnectPeripheral: - <CBCentralManager: 0x17007dd80> <PERIPHERAL UUID>
I must mention however that I have another peripheral that I do not see the same behavior on. The same peripheral behaves normally using CoreBluetooth directly.
It appears that after calling disconnect executeCommandWithContext:error:
is still called on an RZBConnectCommand
instance
there is no issue, but, do you have an example of restorationHandler use?
sorry, thanks in advance
I'd really love to add Core Bluetooth support to the simulator. It should be pretty straight forward to do by relaying the commands and callbacks over an HTTP socket to a mac process running locally. The API's are identical even if the performance and error conditions are a bit different.
From an implementation PoV, this is basically a mock central manager that talks to a mac app instead of the in-memory simulation.
Just a question here related to using the mock objects for unit testing.
I see that the method RZBSimulatedConnection -mockPeripheral:readValueForCharacteristic:
will never call through to any readHandlers
set up on the RZBSimulatedDevice
object once a characteristic value has been set (i.e. once if (characteristic.value == nil)
is false).
Is this how a real Core Bluetooth connection works?
I know that when you use a nil
characteristic value when initializing a CBMutableCharacteristic
, the Apple docs say:
If nil, the value will be dynamic and requested on-demand.
...but it doesn't specify what happens the second time a central reads its value.
I'd like my mock read handlers to be called every time the characteristic is read during unit testing.
Right now RZBluetooth
is great for use in Objective-C but when you use it in Swift you see a lot of [Any]!
, [AnyHashable: Any]
and String!
and also class func
instead of var
.
To improve this what needs to be done is
nullable
and nonnull
to properties and method params where applicable. (nonatomic, nullable, copy)
NSArray
and NSDictionary
properties, e.g. NSArray <NSString *>*
+ (CBUUID *)serviceUUID;
to @property (nonatomic, strong, readonly, nonnull, class) CBUUID *serviceUUID;
Doing all this will make the library look and feel native to Swift when used while it would primarily remain unaffected to Objective-C. I know it is a massive change and will require a major version bump and migration notes.
@KingOfBrian do you think this is a good change and should be done?
After connection at some point I am calling rzb_fetchDeviceInformationKeys
to read device information. However, the first call to this always returns nil values as it doesn't actually read the values
I see the following logs
RZBLogLevelCommand - <DEVICE UUID> discoverServices:@[Device Information]
RZBLogLevelDelegate - peripheral:didDiscoverServices: - <DEVICE UUID> (null)
RZBLogLevelDelegateValue - Services=@[180A]
RZBLogLevelCommand - <DEVICE UUID> discoverCharacteristics:@[System ID, Model Number String, Serial Number String, Software Revision String, Firmware Revision String, Hardware Revision String, Manufacturer Name String, PnP ID] forService:180A
RZBLogLevelDelegate - peripheral:didDiscoverCharacteristicsForService:error: - <DEVICE UUID> (null)
RZBLogLevelDelegateValue - Characteristics=@[2A29, 2A24, 2A25]
However, if I call it again once the characteristics are discovered it returns the values and I see logs like
RZBLogLevelCommand - <DEVICE UUID> readValueForCharacteristic:Model Number String
RZBLogLevelCommand - <DEVICE UUID> readValueForCharacteristic:Serial Number String
RZBLogLevelCommand - <DEVICE UUID> readValueForCharacteristic:Manufacturer Name String
RZBLogLevelCommand - <DEVICE UUID> discoverCharacteristics:@[System ID, Software Revision String, Firmware Revision String, Hardware Revision String, PnP ID] forService:180A
RZBLogLevelDelegate - peripheral:didUpdateValueForCharacteristic:error: - <DEVICE UUID> (null)
RZBLogLevelDelegateValue - Value=<some value>
RZBLogLevelDelegate - peripheral:didUpdateValueForCharacteristic:error: - <DEVICE UUID> (null)
RZBLogLevelDelegateValue - Value=<some value>
RZBLogLevelDelegate - peripheral:didUpdateValueForCharacteristic:error: - <DEVICE UUID> (null)
RZBLogLevelDelegateValue - Value=<some value>
RZBLogLevelDelegate - peripheral:didDiscoverCharacteristicsForService:error: - <DEVICE UUID> 180A (null)
RZBLogLevelDelegateValue - Characteristics=@[2A29, 2A24, 2A25]
My understanding was that RZBluetooth
handles the discovery of services and characteristics internally and I do not need to call discoverService
and discoverCharacteristics
methods...
In the completion block of -[RZBPeripheral connectWithCompletion:]
, call -[RZBPeripheral cancelConnectionWithCompletion:]
. It'll create an infinite loop and eventually crash with an EXC_BAD_ACCESS.
For example:
- (void)testCancelConnectionInConnectCompletion {
RZBPeripheral *peripheral = [self.centralManager peripheralForUUID:self.connection.identifier];
[peripheral connectWithCompletion:^(NSError * _Nullable error) {
[peripheral cancelConnectionWithCompletion:nil];
}];
[self waitForQueueFlush];
}
There is no way for us to retrieve the currently connected peripherals. So for instance, if I am making two apps that use the same peripheral at on the same device I can use CBCentralManager
's retrieveConnectedPeripheralsWithServices:
method but there is no support for that on RZBluetooth
yet
Hi there
One thing that isn't obvious to me from the documentation is how to simulate a scan followed by a connection.
I'm using a RZBSimulatedTestCase subclass. I'm calling my service layer, which initiates a scan. I have added services to my simulated device and called startAdvertising before the scan starts. But the scan never finds a device.
What steps am I missing?
In our app, once the user "pairs" to a peripheral, we use a connectionDelegate
to re-issue a characteristic write as soon as it disconnects, which works great. Later, in the UI the user can also manually forget a peripheral, and at this point I was calling RZBPeripheral.cancelConnection
.
However, I noticed that if I had that pending write command for that peripheral, the library would pro-actively retry the connection command, which would leave a dangling connection request for a device that the app has no interest in anymore, and that was causing some issues. I searched for a way to cancel all outstanding commands for a peripheral but didn't find anything, so I ended up hacking that feature into RZBPeripheral
with an extension:
@implementation RZBPeripheral (Cancellation)
- (RZBCommandDispatch *)hs_dispatch {
SEL selector = NSSelectorFromString(@"dispatch");
IMP imp = [self methodForSelector:selector];
RZBCommandDispatch* (*func)(id, SEL) = (void *)imp;
return func(self, selector);
}
- (void)hs_cancelAllCommands {
NSArray *commands = [self.hs_dispatch commandsOfClass:nil
matchingUUIDPath:RZBUUIDP(self.identifier)
isExecuted:YES];
NSError *error = [NSError errorWithDomain:[NSString stringWithFormat:@"%@-hs", RZBluetoothErrorDomain]
code:1
userInfo:@{NSLocalizedDescriptionKey: @"The operation has been manually cancelled by the user"}];
for (RZBCommand *command in commands) {
[self.hs_dispatch completeCommand:command withObject:nil error:error];
}
}
@end
I feel like this is a useful feature to add to the library, do you agree? If so, what do you think of this solution? If you think this approach is fine I can submit a PR with this change.
Hey @drdaz,
Thanks for asking the questions! Let's keep tracking things here just to keep things in the open. You can also email me at [email protected] if you have any questions that you don't want tracked publicly.
Your question WRT advertisedServices
is a good catch. You must set it to an array of CBUUID's that you want to be in the advertisement packet. This usually includes the primary service UUIDs, but not less interesting ones (IE: The battery).
I'm working on a documentation update for this, but I don't have a big window of time, so any help would be great.
If an invalid characteristicUUID parameter is passed to the above method, the app will crash because the underlying method -setNotifyBlock:forCharacteristicUUID: is passed a nil UUID which it tries to use as a key in the notifyBlocks dictionary.
This can happen if an app tries to clear notify blocks (ie. unregister notification/indication observers) immediately in response to a peripheral disconnection.
Pull request coming soon...
I believe we can remove advertisedServices and check for isPrimary on the services array instead.
I've been struggling for a while to integrate the testing library into a unit test target, but I haven't found a solution that works yet.
Given a pod file like the following :
platform :ios, '10.0'
use_frameworks!
target 'tacx' do
pod 'RZBluetooth/Mock'
target 'tacxTests' do
inherit! :search_paths
pod 'RZBluetooth/Test'
end
end
A unit testing file:
@import RZBluetooth;
@interface tacxTests : RZBSimulatedTestCase
@end
@implementation tacxTests
@end
Everything compiles just fine, but when running the test, it prints a message stating :
2016-11-20 19:05:59.112 tacx[95628:5961223] Failed to load test bundle from file:///Users/xxxxxxx/Library/Developer/Xcode/DerivedData/tacx-ghqgcpxbcphxnmhievkvngfndhzq/Build/Intermediates/CodeCoverage/Products/Debug-iphonesimulator/tacx.app/PlugIns/tacxTests.xctest/: Error Domain=NSCocoaErrorDomain Code=3588 "dlopen(/Users/xxxxxxx/Library/Developer/Xcode/DerivedData/tacx-ghqgcpxbcphxnmhievkvngfndhzq/Build/Intermediates/CodeCoverage/Products/Debug-iphonesimulator/tacx.app/PlugIns/tacxTests.xctest/tacxTests, 265): Symbol not found: OBJC_CLASS$_RZBSimulatedTestCase
Referenced from: /Users/xxxxxxx/Library/Developer/Xcode/DerivedData/tacx-ghqgcpxbcphxnmhievkvngfndhzq/Build/Intermediates/CodeCoverage/Products/Debug-iphonesimulator/tacx.app/PlugIns/tacxTests.xctest/tacxTests
Expected in: /Users/xxxxxxx/Library/Developer/CoreSimulator/Devices/D4A4BA5A-3023-4B18-B912-EE1FCDA1835D/data/Containers/Bundle/Application/2060D194-B119-4FEA-A8B1-917686855AB4/tacx.app/Frameworks/RZBluetooth.framework/RZBluetooth
in
As you can see by the Referenced from
and Expected in
lines, Xcode sees a symbol from the RZBluetooth
framework (_OBJC_CLASS_$_RZBSimulatedTestCase
) and expects it to be located in /tacx.app/Frameworks/RZBluetooth.framework/RZBluetooth
, which is the app's version of RZBluetooth
(created by pod 'RZBluetooth/Mock'
in the podfile). This causes the app to crash because it can not find a the symbols for RZBSimulatedTestCase
.
Are you aware of any way to explicitly importing the unit test's version of the library? (that is the version of the library that contains the RZBSimulatedTestCase
class?
The error message in the title is generated by Core Bluetooth when a central app using RZBluetooth is communicating with a peripheral device that changes its services database while connected.
This is most easily seen in the log output of a central app, when an app using CBPeripheralManager to simulate the peripheral is quit while the central app is still connected to it.
RZBCentralManager
needs to add the delegate method in question, if only to log what happened. Ideally it would also invalidate the peripheral's services and characteristics so they would be re-discovered on the next command.
2017-03-10 14:26:29.769412 AppName[722:459894] [CoreBluetooth] WARNING: The delegate for <CBPeripheral: 0x1700f7480, identifier = 5099F44B-85BE-83AC-8387-C1D6E4E7A57D, name = DeviceName, state = connected> does not implement -[peripheral:didModifyServices:]
I am invoking RZBEnableMock(YES) from "load" method in Objective C. This category is included in a Swift project, but "RZBEnableMock" is not working. It is not able to Swizzle alloc method of CBCentralManager.
The clearNotifyBlocks
method in RZBPeripheral
crashes when there's at least one key/value pair in the self.notifyBlockByUUIDs
NSMutableDictionary
.
The reason is that clearNotifyBlocks
iterates over the self.notifyBlockByUUIDs
dictionary, calling clearNotifyBlockForKey:
. In clearNotifyBlockForKey:
, the key/value pair is removed from self.notifyBlockByUUIDs
. This crashes since we're removing an object from the dictionary we're currently iterating over. Here's the error message:
Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSDictionaryM: 0x7f9f31a84440> was mutated while being enumerated.'
The fix is straightforward and just requires a small change to clearNotifyBlocks
:
- (void)clearNotifyBlocks {
NSArray *keys = self.notifyBlockByUUIDs.allKeys;
for (NSString* key in keys) {
[self clearNotifyBlockForKey:key];
}
}
I have this fixed locally and may be able to create a PR soon, but I wanted to document the problem and solution just in case I don't get to it right away.
As mentioned in #57, i'm trying out your framework with the heartrate example. I'm quite new to iOS so it's difficult for me to understand everything.
What I have found is that once I am connected to a device, if I move back from the service list view, the sensor stays connected. I would have expected it to disconnect at that point. When I go back to the "scan" view the device is not visible as it's still connected and therefore not advertising. I don't know if what I am seeing is a consequence of the fixes i put in place for #57? Could you advise please?
What I would like to evaluate, is making my app robust to the device disappearing (temporarily out of range) or my app being closed by iOS due to memory pressure etc. I essentially want my app to be always running unless I intentionally kill it. Can you advise on what I need to do to achieve that with your framework? I think some of the key elements are already there?
Thanks
You can't tell what is in the array without looking at the implementation. So things like:
scanForPeripheralsWithServices:
it's not clear what should go into the array (CBUUID
or NSUUID
).
If we just add <CBUUID>
it should clear this up. We should also do this for optionals
In the iOS 10 SDK, they've combined CBCentralManagerState and CBPeripheralManagerState into one type called CBManagerState.
Currently, building RZBluetooth in Xcode 8/iOS 10 SDK throws warnings about the type conversion:
warning: implicit conversion from enumeration type 'CBManagerState' (aka 'enum CBManagerState') to different enumeration type 'CBCentralManagerState' (aka 'enum CBCentralManagerState') [-Wenum-conversion]
It'd be nice to have OSX support especially for the Mock framework so we can have the a simulator and a command line peripheral/central at the same time.
I've noticed that if I connect to a peripheral that was found in a scan operation, and if the peripheral disconnects from us while the scan is still running, the scan will fail with the disconnection error being passed to the scan block. There are ways to work around this in my code, but this behavior seems weird to me.
The disconnection error is logged as follows:
centralManager:didDisconnectPeripheral:error: - <CBCentralManager: 0x174265200> 0D789391-1F42-41AB-82D8-3A431C753018 Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us." UserInfo={NSLocalizedDescription=The specified device has disconnected from us.}
I'm getting this assertion failure in my tests; it seems to be timing related. In tests that execute and finish very quickly it appears. In longer running tests, less so.
Hey there
Is there some way of manually making characteristic discovery happen? I have a write characteristic that I'd like to have discovered before I write to it...
And a hacky solution is fine, because I'm writing a hack.
If the Bluetooth radio resets during a connection, I am unable to reconnect to the peripheral. The only solution is to hard reset the phone.
[V][2017/05/24 11:55:16:268][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] peripheral:didUpdateValueForCharacteristic:error: - 4A571E85-188E-44CF-A03F-4DDDD152BA7E 914214C5-825C-4D8F-BD37-570F6BD532CD (null)
[V][2017/05/24 11:55:16:750][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] 4A571E85-188E-44CF-A03F-4DDDD152BA7E readValueForCharacteristic:914214C5-825C-4D8F-BD37-570F6BD532CD
[V][2017/05/24 11:55:17:774][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] centralManagerDidUpdateState: - <CBCentralManager: 0x170275640>
[V][2017/05/24 11:55:17:775][ViewController -[ViewController stateChange]][Line 352]
[V][2017/05/24 11:55:17:775][ViewController -[ViewController stateChange]][Line 353] centralManager.state = 1
[V][2017/05/24 11:55:17:775][ViewController -[ViewController stateChange]][Line 361] ERROR reset pause
[V][2017/05/24 11:55:17:797][ViewController -[ViewController stateChange]][Line 372] ERROR CBManagerStateResetting: The Bluetooth radio reset unexpectedly. btConnected = 1 scanning = 0
[V][2017/05/24 11:55:17:836][ViewController -[ViewController stateChange]_block_invoke][Line 384] cancelConnectionWithCompletion
[V][2017/05/24 11:55:21:251][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] centralManagerDidUpdateState: - <CBCentralManager: 0x170275640>
[V][2017/05/24 11:55:21:252][ViewController -[ViewController stateChange]][Line 352]
[V][2017/05/24 11:55:21:252][ViewController -[ViewController stateChange]][Line 353] centralManager.state = 5
[V][2017/05/24 11:55:21:267][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] connectPeripheral:4A571E85-188E-44CF-A03F-4DDDD152BA7E options:(null)
[V][2017/05/24 11:59:15:627][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] centralManager:didDiscoverPeripheral:advertisementData:RSSI: - <CBCentralManager: 0x170275640> 4A571E85-188E-44CF-A03F-4DDDD152BA7E
[V][2017/05/24 11:59:15:627][ViewController -[ViewController poweredOn]_block_invoke_2][Line 436] discovered Plug
[V][2017/05/24 11:59:15:628][ViewController -[ViewController poweredOn]_block_invoke_2][Line 456] peripheral = 4A571E85-188E-44CF-A03F-4DDDD152BA7E
[V][2017/05/24 11:59:15:628][ViewController -[ViewController poweredOn]_block_invoke_2][Line 457] services = (
)
[V][2017/05/24 11:59:15:628][ViewController -[ViewController poweredOn]_block_invoke_2][Line 458] readCharacteristicUUID:
[V][2017/05/24 11:59:15:628][ViewController -[ViewController readPowerReset]][Line 1326]
[V][2017/05/24 11:59:15:629][AppDelegate -[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke][Line 57] connectPeripheral:4A571E85-188E-44CF-A03F-4DDDD152BA7E options:(null)
...
[V][2017/05/24 11:59:46:491][ViewController -[ViewController poweredOn]_block_invoke_4][Line 464] Error Domain=com.raizlabs.bluetooth Code=7 "(null)"
[V][2017/05/24 11:59:46:491][ViewController -[ViewController readPowerReset]_block_invoke_2][Line 1332] Error Domain=com.raizlabs.bluetooth Code=7 "(null)"
[V][2017/05/24 11:59:46:492][ViewController -[ViewController readPowerReset]_block_invoke_2][Line 1332] Error Domain=com.raizlabs.bluetooth Code=7 "(null)"
[V][2017/05/24 11:59:46:492][ViewController -[ViewController poweredOn]_block_invoke_5][Line 473] cancelConnectionWithCompletion
Hello. I may be misunderstanding something but I couldn't get your heartrate example to work without some modifications.
In ScanListViewController.m, when I was looking for my heartrate sensor, I would get the following error in viewWillAppear:
2017-04-07 17:01:28.751428 RZBluetoothExample[286:10774] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 0 from section 0 which only contains 0 rows before the update'
The first issue (i think) was that scannedDevices wasn't getting populated with anything from scanInfo, so I added the line:
[self.scannedDevices addObject:scanInfo];
just after
[self.centralManager scanForPeripheralsWithServices:@[self.scanUUID] options:@{} onDiscoveredPeripheral:^(RZBScanInfo *scanInfo, NSError *error) {
if (error) {
NSLog(@"Error scanning: %@", error);
return;
}
This was then correctly populating devices into scannedDevices but they weren't being shown (still the same error above) until I rewrote the populating of the list like this:
NSIndexPath *durPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *paths = [NSArray arrayWithObject:durPath];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationNone];
instead of:
[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:existingIndex inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
I know that this isn't as elegant, but it worked and I now get heartrate data. The full method is this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.centralManager scanForPeripheralsWithServices:@[self.scanUUID] options:@{} onDiscoveredPeripheral:^(RZBScanInfo *scanInfo, NSError *error) {
if (error) {
NSLog(@"Error scanning: %@", error);
return;
}
[self.scannedDevices addObject:scanInfo];
__block NSUInteger existingIndex = 0;
[self.scannedDevices enumerateObjectsUsingBlock:^(RZBScanInfo * info, NSUInteger idx, BOOL * _Nonnull stop) {
if ([info.peripheral.identifier isEqual:scanInfo.peripheral.identifier]) {
info.advInfo = scanInfo.advInfo;
info.RSSI = scanInfo.RSSI;
existingIndex = idx;
}
}];
NSLog(@"%@ - %@", [scanInfo.peripheral.identifier UUIDString], scanInfo.advInfo);
if (existingIndex == NSNotFound) {
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.scannedDevices.count - 1 inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
else {
NSLog(@"numberOfRowsInSection: %ld", (long)[self tableView:self.tableView numberOfRowsInSection:0]);
NSIndexPath *durPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *paths = [NSArray arrayWithObject:durPath];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationNone];
//[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:existingIndex inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}];
}
However, did I do something wrong with your code, or is there a more elegant way of achieving what I "fixed"?
I have a follow-on question which I will post in a second thread.
Thanks
I suspect this isn't an issue with your library per se; I'm seeing the same behaviour with similar libraries. But I've been banging my head against this for a little while and I'd like to share the problem.
When the completion handler for cancelConnectionFromPeripheralUUID fires, I expect (wrongly) the peripheral to have actually disconnected. If I attempt to reconnect at this point, things get generally messy.
Is there any way of being notified when we actually have finished disconnecting? KVO on CBPeripheral's state variable perhaps?
The podspec lists the iOS deployment target at 9.0, but the Xcode project lists the iOS deployment target at 8.4.
Is there any reason for this? Can the podspec's deployment target be dropped down to 8.4, or can they potentially be both dropped even further to 8.0?
Thanks!
There's an identifier passed in that is not being used, it should be passed into the options dictionary of the central
Sometimes when turning off Bluetooth while connected to a peripheral the app crashes in executeCommandWithContext:error:
on RZBConnectCommand
because
CBPeripheral *peripheral = [context corePeripheralForUUID:self.peripheralUUID];
This peripheral is nil. This does not happen every time but does happen sometimes.
It seems that RZBEnableMock()
doesn't work when used in pure Swift code.
This came up while we were writing some peripheral simulator unit tests.
Try pasting the following code into the file SwiftTestCase.swift
:
class SwiftTestCase2: XCTestCase {
func testMocking() {
var centralManager = CBCentralManager()
XCTAssertNil(centralManager.mock)
var peripheralManager = CBPeripheralManager()
XCTAssertNil(peripheralManager.mock)
// Enable RZBluetooth mocking
RZBEnableMock(true)
centralManager = CBCentralManager()
XCTAssertNotNil(centralManager.mock) // Fails
peripheralManager = CBPeripheralManager()
XCTAssertNotNil(peripheralManager.mock) // Fails
// Restore normal operation
RZBEnableMock(false)
centralManager = CBCentralManager()
XCTAssertNil(centralManager.mock)
peripheralManager = CBPeripheralManager()
XCTAssertNil(peripheralManager.mock)
}
}
Apparently, Swift's static typing isn't fooled by swizzling the Obj-C alloc
method...
I just updated to Xcode 8, and I'm now seeing the following warning:
"API MISUSE: has no restore identifier but the delegate implements the centralManager:willRestoreState: method. Restoring will not be supported"
I'm not sure how dangerous this is; I'm guessing the issue was there the whole time but I wasn't seeing the warning. Thought you should know about it though.
Just migrated my code to 1.0, and ran into this issue.
The declaration of the RZBPeripheralConnectionDelegate
protocol method:
- (void)peripheral:(RZBPeripheral *)peripheral connectionEvent:(RZBPeripheralStateEvent)event error:(NSError *)error;
...needs the error parameter to be __nullable
to work properly with Swift. Currently, since the error parameter is not Optional
in Swift, it will cause a crash if the error is null.
For now, I'm working around the issue by declaring my implementation of the delegate method with an NSError?
parameter, but that generates a build warning.
New issue with the 10/10 commit:
I'm using RZBSimulatedTestCase in my unit tests, and it appears that CocoaPods does not pull down the new file RZBTestDefines.h when I include the "RZBluetooth/Test" pod in my test target in my Podfile. Thus my test target build fails.
I'm guessing that line 43 of the podspec should change from:
test.public_header_files = "RZBluetoothTests/RZBSimulatedTestCase.h"
to:
test.public_header_files = "RZBluetoothTests/RZBSimulatedTestCase.h",
"RZBluetoothTests/RZBTestDefines.h"
Here's my scenario:
RZBPeripheral
instance from RZBCentralManager
and enable maintainConnection
enableNotify
and observe that updated values are received correctlyI can work around this by setting a connectionDelegate
and having it call enableNotify
again once the connection succeeds, but should this be needed at all? From the docs I was expecting that the framework would handle this.
10:40:28.438 🔵DEBUG BluetoothController.init():55 - centralManagerDidUpdateState: - <CBCentralManager: 0x1720616c0>
10:40:28.439 🔵DEBUG BluetoothController.init():55 - State=5
10:40:28.439 🔵DEBUG BluetoothController.init():55 - connectPeripheral:89CF787A-F177-45F6-BE49-0F3DD92D7E20 options:(null)
10:40:28.757 🔵DEBUG BluetoothController.init():55 - centralManager:didConnectPeripheral: - <CBCentralManager: 0x1720616c0> 89CF787A-F177-45F6-BE49-0F3DD92D7E20
10:40:28.758 🔵DEBUG BluetoothController.init():55 - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 discoverServices:@[181D]
10:40:28.831 🔵DEBUG BluetoothController.init():55 - peripheral:didDiscoverServices: - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 (null)
10:40:28.831 🔵DEBUG BluetoothController.init():55 - Services=@[181D]
10:40:28.832 🔵DEBUG BluetoothController.init():55 - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 discoverCharacteristics:@[2A9D] forService:181D
10:40:28.832 🔵DEBUG BluetoothController.init():55 - peripheral:didDiscoverCharacteristicsForService:error: - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 181D (null)
10:40:28.832 🔵DEBUG BluetoothController.init():55 - Characteristics=@[2A9D]
10:40:28.833 🔵DEBUG BluetoothController.init():55 - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 setNotifyValue:YES forCharacteristic:2A9D
10:40:28.861 🔵DEBUG BluetoothController.init():55 - peripheral:didUpdateNotificationStateForCharacteristic:error: - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 2A9D (null)
10:40:28.862 🔵DEBUG BluetoothController.init():55 - Notify=YES
10:40:40.758 🔵DEBUG BluetoothController.init():55 - peripheral:didUpdateValueForCharacteristic:error: - 89CF787A-F177-45F6-BE49-0F3DD92D7E20 2A9D (null)
10:40:40.759 🔵DEBUG BluetoothController.init():55 - Value=<205050>
10:40:45.509 🔵DEBUG BluetoothController.init():55 - centralManager:didDisconnectPeripheral:error: - <CBCentralManager: 0x1720616c0> 89CF787A-F177-45F6-BE49-0F3DD92D7E20 Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us." UserInfo={NSLocalizedDescription=The specified device has disconnected from us.}
10:40:45.512 🔵DEBUG BluetoothController.init():55 - connectPeripheral:89CF787A-F177-45F6-BE49-0F3DD92D7E20 options:(null)
10:40:51.601 🔵DEBUG BluetoothController.init():55 - centralManager:didConnectPeripheral: - <CBCentralManager: 0x1720616c0> 89CF787A-F177-45F6-BE49-0F3DD92D7E20
Whenever I access state it always reports it as unknown. If I launch the app with Bluetooth powered off, the system provided alert to turn on Bluetooth does not turn on.
I am initializing the central manager as follows
manager = RZBCentralManager(identifier: "myrestorationidentifier", peripheralClass: Device.self, queue: DispatchQueue.main)
Maybe I'm missing something...
EDIT:
If I set the centralStateHandler
I get the correct state from the state
property.
I am trying to distinguish two cases:
Previously, using another library (RKBluetooth), the peripheral connection had a callback block for completion of connection and for disconnection. The disconnection block would only be called when the device disconnected by itself.
I am missing this feature on RZBluetooth, maybe I just didn't discover it yet. I don't understand the relationship between the callback block of the connection or cancel connection methods of the RZPeripheral, and the connectionEvent delegate callback.
Both are executed, while the method callback blocks are executed before the delegate method.
I wonder what the preferred way of handling this distinction is? I don't want to store a flag when the method callback block is called so that I can prevent the connectionEvent to execute, which is the only workaround I can think of right now.
1.2 Unit tests that passed successfully in the past (at least as of June 2017, Xcode 8.3.3) are now crashing consistently when run under Xcode 9.2 and macOS Sierra 10.12.6.
The crash is occurring in RZBSimulatedCallback.m
, line 118, in the -cancel
method.
Test Suite 'SwiftTestCase' passed at 2018-04-06 16:47:43.958.
Executed 2 tests, with 0 failures (0 unexpected) in 0.318 (0.320) seconds
Test Suite 'RZBluetoothTests.xctest' failed at 2018-04-06 16:47:43.959.
Executed 66 tests, with 3 failures (0 unexpected) in 1.825 (1.836) seconds
Test Suite 'Selected tests' failed at 2018-04-06 16:47:43.960.
Executed 66 tests, with 3 failures (0 unexpected) in 1.825 (1.838) seconds
Failing tests:
-[RZBSimulatedTests testConnectionAndCancelWhileNotConnectable]
-[RZBSimulatedTests testStateBounce]
-[RZBSimulationObjectTests testSimulatedCallbackPaused]
** TEST FAILED **
Process: xctest [67675]
Path: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest
Identifier: xctest
Version: 13764
Code Type: X86-64 (Native)
Parent Process: launchd_sim [53442]
Responsible: xctest [67675]
User ID: 501
Date/Time: 2018-04-06 16:47:22.602 -0400
OS Version: Mac OS X 10.12.6 (16G1314)
Report Version: 12
Anonymous UUID: AA94F0D2-7CE4-3EC1-8A8F-AE5F930B5E34
Sleep/Wake UUID: F138C980-C2C9-49DB-9B98-4110F6016067
Time Awake Since Boot: 150000 seconds
Time Since Wake: 5300 seconds
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes: 0x0000000000000001, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Illegal instruction: 4
Termination Reason: Namespace SIGNAL, Code 0x4
Terminating Process: exc handler [0]
Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH: Release of an inactive object
CoreSimulator 494.33 - Device: iPhone 7 - Runtime: iOS 11.2 (15C107) - DeviceType: iPhone 7
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libdispatch.dylib 0x0000000115def80a _dispatch_queue_xref_dispose + 73
1 libdispatch.dylib 0x0000000115dece2a -[OS_dispatch_source _xref_dispose] + 17
2 com.apple.CoreFoundation 0x000000010f31361d -[__NSArrayM removeAllObjects] + 237
3 com.raizlabs.RZBluetoothTests 0x0000000124875828 -[RZBSimulatedCallback cancel] + 696 (RZBSimulatedCallback.m:118)
4 com.raizlabs.RZBluetoothTests 0x0000000124877911 -[RZBSimulatedConnection reset] + 369 (RZBSimulatedConnection.m:101)
5 com.raizlabs.RZBluetoothTests 0x000000012485c41a -[RZBSimulatedTests testStateBounce] + 4282 (RZBSimulatedTests.m:222)
6 com.apple.CoreFoundation 0x000000010f2f136c __invoking___ + 140
7 com.apple.CoreFoundation 0x000000010f2f1240 -[NSInvocation invoke] + 320
8 com.apple.dt.XCTest 0x000000010e8d5e30 __24-[XCTestCase invokeTest]_block_invoke + 591
9 com.apple.dt.XCTest 0x000000010e91f17e -[XCUITestContext performInScope:] + 183
10 com.apple.dt.XCTest 0x000000010e8d5bd6 -[XCTestCase invokeTest] + 141
11 com.apple.dt.XCTest 0x000000010e8d6b97 __26-[XCTestCase performTest:]_block_invoke.369 + 42
12 com.apple.dt.XCTest 0x000000010e923f25 +[XCTContext runInContextForTestCase:block:] + 163
13 com.apple.dt.XCTest 0x000000010e8d6533 -[XCTestCase performTest:] + 608
14 com.apple.dt.XCTest 0x000000010e8d2539 __27-[XCTestSuite performTest:]_block_invoke + 363
15 com.apple.dt.XCTest 0x000000010e8d1ea0 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
16 com.apple.dt.XCTest 0x000000010e8d209d -[XCTestSuite performTest:] + 239
17 com.apple.dt.XCTest 0x000000010e8d2539 __27-[XCTestSuite performTest:]_block_invoke + 363
18 com.apple.dt.XCTest 0x000000010e8d1ea0 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
19 com.apple.dt.XCTest 0x000000010e8d209d -[XCTestSuite performTest:] + 239
20 com.apple.dt.XCTest 0x000000010e8d2539 __27-[XCTestSuite performTest:]_block_invoke + 363
21 com.apple.dt.XCTest 0x000000010e8d1ea0 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
22 com.apple.dt.XCTest 0x000000010e8d209d -[XCTestSuite performTest:] + 239
23 com.apple.dt.XCTest 0x000000010e92b64f __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 40
24 com.apple.dt.XCTest 0x000000010e8e571a -[XCTestObservationCenter _observeTestExecutionForBlock:] + 475
25 com.apple.dt.XCTest 0x000000010e92b4ee -[XCTTestRunSession runTestsAndReturnError:] + 281
26 com.apple.dt.XCTest 0x000000010e8c1af1 -[XCTestDriver runTestsAndReturnError:] + 314
27 com.apple.dt.XCTest 0x000000010e923190 _XCTestMain + 619
28 xctest 0x000000010dfcc591 0x10dfca000 + 9617
29 libdyld.dylib 0x0000000115e61d81 start + 1
Carthage is a better and non invasive way to manage dependencies, it should be added.
RZBBluetoothRepresentable was an approach to simplify serialization, but I don't think it helps very much and it adds service to the API that makes it more complex. The Device Information profile API will need to be changed to accommodate this.
The RZBSimulatedDevice
class cannot handle the case where a device implements 2 or more services that share a common characteristic UUID. If you try to add a read/write/subscribe handler for the duplicate characteristic UUID, obviously, it overwrites the previous one added.
The internal readHandlers
, writeHandlers
, and subscribeHandlers
dictionaries use only the characteristic UUID as the key; they should use a combination of characteristic UUID and service UUID (or an RZBUUIDPath
).
Similarly, the corresponding {add|remove}{Read|Write|Subscribe}CallbackForCharacteristicUUID:handler:
methods should be modified to take an additional service UUID parameter (or replace them with an RZBUUIDPath
parameter).
This is probably not a common use case, but it can happen, for instance, if a device implements a Record Access Control Point for 2 different kinds of data records on separate services.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.