I have developed a bluetooth app, which runs fine on iOS 6, but when I run it on iOS 7 the application crashes in the -didDiscoverPeripheral call back. The crash info suggests that a release is called on the CBPeripheral object. I have used ARC for memory management, here is my declaration,initialisation of the CBPeripheral object and the call back code:
#interface BrLEDiscovery () <CBCentralManagerDelegate, CBPeripheralDelegate> {
CBCentralManager *centralManager;
CBPeripheral *currentperipheral;
BOOL pendingInit;
}
- (id) init
{
self = [super init];
if (self) {
pendingInit = YES;
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
currentperipheral=[[CBPeripheral alloc]init];
founddevice=FALSE;
foundPeripherals = [[NSMutableArray alloc] init];
connectedServices = [[NSMutableArray alloc] init];
}
return self;
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{ NSLog(#"Found Peripheral %#",[peripheral name]);
if ([[peripheral name] isEqualToString:peripheralname]) {
founddevice=TRUE;
currentperipheral=peripheral;
if (![foundPeripherals containsObject:peripheral]) {
[foundPeripherals addObject:peripheral];
[discoveryDelegate discoveryDidRefresh];
[discoveryDelegate DiscoveryDidFindDevice:peripheral];
}
[RecursiveScanTimer invalidate];
}
}
From your description, it's pretty likely to be crashing on this line:
currentperipheral=peripheral;
To me, it's kinda messy allocating a CBPeripheral object in your init and then assigning the peripheral at an unknown time later (especially with arc). If you want to keep reference to a discovered peripheral, just create a CBPeripheral object in your interface, retain the discovered peripheral, and assign it to your interface peripheral.
Just try something like this:
currentPeripheral = [discoveredPeripheral retain];//where discoveredPeripheral is the one given in the delegate callback
I personally don't use arc for any of my corebluetooth related applications..You need to be really careful with the corebluetooth framework and peripheral references though...it gets messyyy.
Note: If you do not always need to have direct contact with the peripheral, it's often handy to just keep a record of the CFUUIDRef (or identifier for iOS 7) and just retrieve the peripheral whenever you need it... Good luck!
Related
I have been trying for weeks to figure out how state preservation and restoration works for Core Bluetooth in iOS, but I am completely lost.
So far I have done the following:
Added a Restoration Identifier to my CBCentralManager
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{CBCentralManagerOptionRestoreIdentifierKey:#"myCentralManager"}];
Which should make my application able to call the delegate method:
centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)dict;
In here I am trying to reconnect to my peripheral, if I had a connection prior to the app being suspended, otherwise I will try to scan for my peripheral again, like so:
if (dict[CBCentralManagerRestoredStatePeripheralsKey]) {
for (CBPeripheral *currentPeripheral in peripherals) {
peripheral = currentPeripheral;
}
// Connect to peripheral
}
else{
// Scan for UUID
}
Am I doing this right, am I close, or is this completely wrong?
It works for me now.
I initialize my central manager, just like I did originally, and then in the willRestoreState method, I run the following code:
NSArray* peripherals = [central retrieveConnectedPeripheralsWithServices:[NSArray arrayWithObject:UUID]];
if ([peripherals count] > 0) {
_discoveredPeripheral = [peripherals objectAtIndex:0];
_discoveredPeripheral.delegate= self;
[central connectPeripheral:_discoveredPeripheral options:nil];
} else {
[self scan];
}
I have multiple BLE devices which have same services and characteristics. I can scan and connect multiple devices. After connections, when I try to distinguish each one by sending command it doesn't work. It works perfectly with a single device. Is it something like socket connection ? Like A server spawns child threads and each client can maintain a connection through threads.
Please provide some tips on how to scan each device when other device is reading the data from the device.
-(void) scanDevice {
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[centralManager scanForPeripheralsWithServices:nil options:0];
[AppDelegate app].cbCentral = centralManager;
}
-(void) stopScan {
[[AppDelegate app].cbCentral stopScan];
}
-(void)connectToDevice:(CBPeripheral *)peripheral{
[[AppDelegate app] cbCentral].delegate = self;
[[[AppDelegate app] cbCentral] connectPeripheral:peripheral options:nil];
}
-(void)calldiscoverServicesForPeripheral:(CBPeripheral *)peripheral{
[peripheral setDelegate:self];
[peripheral discoverServices:nil];
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(#"Connected PERIPHERAL %d",peripheral.state);
[delegate getConnectedPeripheral:peripheral];
NSLog(#"Connected peripheral %#",peripheral);
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(#"Discovered servicea: %#", peripheral.services);
for (CBService *service in peripheral.services) {
NSLog(#"Discovered service: %#", [service.UUID data]);
[peripheral discoverCharacteristics:nil forService:service];
}
}
I will explain in detail,
I have table view, that contains BLE devices. For the first time it is empty, so i will search for the devices by calling a class "Scan Devices".
This "Scan devices" class contains all the Corebluetooth methods like CBCentralManager allocation, CBperipheral delegate methods.
After search, I will display the device in table view and connect to the BLE device. I am getting some data from "Scan Device" class.
Now, I want to search more devices to connect and get the data. For this, I will call [[CBCentralManager alloc] initWithDelegate:self] in "ScanDevices" class. At this time, for previous device(connected and reading) is showing the warning "is not a valid peripheral" and new device is connecting and reading data from the device.
But I want read the data from both devices at a time
Please help me...
Thanks
You shouldn't keep creating a new CBCentral - doing so will cause your previous CBCentral to be deallocated and therefore invalidate the existing peripherals.
You should activate your scanning once, say in viewWillAppear and deactivate it in viewWillDisappear.
Once you have initiated scanning and set your delegate in your ScanDevices class, it will call [delegate getConnectedPeripheral:] each time a new peripheral is found and connected.
I am working on an app that reacts on disconnects of peripherals and I am now trying to adopt the ne state preservation and restoration introduced in iOS 7.
I did everything like the documentation says, means:
I added the background mode for centrals.
I always instantiate my central manager with the same unique
identifier.
I implemented the centralManager:willRestoreState: method.
When my App moves to background I kill it in the AppDelegate callback with an kill(getpid(), SIGKILL);. (Core Bluetooth State Preservation and Restoration Not Working, Can't relaunch app into background)
When I now disconnect a peripheral by removing the battery my app is being waked up as expected and launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey] contains the correct identifier BUT the centralManager:willRestoreState: was not called.
Only if I disconnect another peripheral this method gets called.
This is how I have it:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSArray *peripheralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothPeripheralsKey];
if (peripheralManagerIdentifiers) {
// We've restored, so create the _manager on the main queue
_manager = [[CBPeripheralManager alloc] initWithDelegate:self
queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
options:#{CBPeripheralManagerOptionRestoreIdentifierKey:#"YourUniqueIdentifier"}];
} else {
// Not restored so just create as normal
manager = [[CBPeripheralManager alloc] initWithDelegate:self
queue:nil
options:#{CBPeripheralManagerOptionRestoreIdentifierKey:#"YourUniqueIdentifier"}];
}
return YES;
}
And then:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
willRestoreState:(NSDictionary *)dict
{
// This is the advertisement data that was being advertised when the app was terminated by iOS
_advertisementData = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey];
NSArray *services = dict[CBPeripheralManagerRestoredStateServicesKey];
// Loop through the services, I only have one service but if you have more you'll need to check against the UUID strings of each
for (CBMutableService *service in services) {
_primaryService = service;
// Loop through the characteristics
for (CBMutableCharacteristic *characteristic in _primaryService.characteristics) {
if ([characteristic.UUID.UUIDString isEqualToString:CHARACTERISTIC_UUID]) {
_primaryCharacteristic = characteristic;
NSArray *subscribedCentrals = characteristic.subscribedCentrals;
// Loop through all centrals that were subscribed when the app was terminated by iOS
for (CBCentral *central in subscribedCentrals) {
// Add them to an array
[_centrals addObject:central];
}
}
}
}
}
As we know, iOS 6 support running devices (iPhone 4s and above, and new iPad) as a BLE peripheral. There is a demo in WWDC 2012 Session 705 called "advanced core bluetooth". I asked for the source code from Apple. They sent me a modified version of source code (BTLE_Transfer_Draft). Then I:
Run the app in iPhone 5 (iOS 6) in "Peripheral Mode" and start "Advertising"
Run the app in new iPad (iOS 5.1.1) in "Central Mode"
The problem is that the peripheral is never been discovered at all. So I use other testing applications including some downloaded from App Store. All failed to discover peripherals. I think the problem should be in BTLE_Transfer_Draft. Because I'm not sure whether I'm allowed to present the whole source code. So I just show the "peripheral mode" part here:
- (void)viewDidLoad {
[super viewDidLoad];
// Start up the CBPeripheralManager
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
// We're in CBPeripheralManagerStatePoweredOn state...
NSLog(#"self.peripheralManager powered on.");
// ... so build our service.
// Start with the CBMutableCharacteristic
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]
properties:CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
// Then the service
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]
primary:YES];
// Add the characteristic to the service
transferService.characteristics = #[self.transferCharacteristic];
// And add it to the peripheral manager
[self.peripheralManager addService:transferService];
}
/** Start advertising
*/
- (IBAction)switchChanged:(id)sender
{
if (self.advertisingSwitch.on) {
// All we advertise is our service's UUID
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
}
else {
[self.peripheralManager stopAdvertising];
}
}
The BLE is in powered on status and the startAdvertising is called. But the BLE central can never discover it.
Post updated:
According to mttrb's suggestion I added "CBAdvertisementDataLocalNameKey" when I startAdvertising. But my service is still can't be discovered by most of the apps including some apps from app store. The only one app can discover my service is an app from app store called "BLE scanner".
My question is: does this mean my application is working as a peripheral? But why my own code can't discover the service? How am I supposed to debug it ?
My code in Central Mode is like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Start up the CBCentralManager
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state != CBCentralManagerStatePoweredOn) {
return;
}
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
......
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(#"Error discovering services: %#", [error localizedDescription]);
return;
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
// Deal with errors (if any)
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Peripheral Disconnected");
self.discoveredPeripheral = nil;
}
The didDiscoverPeripheral and didDiscoverServices are never called. What could be wrong? Any idea? Thanks
There is also a high quality free app called LightBlue that you can use to test your code with. It should be able to pick up all devices advertising in peripheral mode and it can even turn itself into an advertising peripheral if you want to make sure your device is working properly.
I would try moving the startAdvertising: method call up and into the end of your peripheralManagerDidUpdateState: delegate method and see if that helps.
I would also add a CBAdvertisementDataLocalNameKey key-value pair to your startAdvertising: method call. I found things were unreliable when the advertisement didn't have a name.
Finally, I would invest in the BLExplr app available in the App Store to help with scanning for your peripheral. It removes the assumption that your central is working correctly.
This Git hub project also throws some light on the CBPeripheralManager API. Called PeripheralModeTest.
This line is particularly useful for setting the advertising data
NSDictionary *advertisingData = #{CBAdvertisementDataLocalNameKey : #"Device Name", CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]};
Though I can't see any official documentation in the Apple iOS Developer Library yet. More specifically anything about setting the repeat period for the advertising.
The BTLE Transfer example has this piece of (odd) code, which may cause some troubles:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
// Reject any where the value is above reasonable range
if (RSSI.integerValue > -15) {
return;
}
// Reject if the signal strength is too low to be close enough (Close is around -22dB)
if (RSSI.integerValue < -35) {
return;
}
Just remove those two if-statements as it makes no sense!
I have made a simplified version available here that can be used to test high volumes of messages being sent from peripheral to central.
Please note that the CBPeripheralManager class was first introduced in iOS 6.0.
I do not know witch version of BTLE Central Peripheral Transfer you did actually test but current version has iOS 6 as requirement.
So I would suggest to test linkage against iOS 5.1 to see what compatibility issues it shows.
I have multiple BLE devices which have same services and characteristics. I can scan and connect multiple devices. After connections, tried to distinguish each one by sending command but it does not work. It works perfectly with a single device. Is it something like socket connection ? Like A server spawns child threads and each client can maintain a connection through threads. Please provide some tips on how to identity each device.
CBCentralManager instance I created is singleton.
+ (id) sharedInstance
{
static BLEConnectionManager *this = nil;
if (!this) {
this = [[BLEConnectionManager alloc] init];
}
else {
[this scanDevice];
}
return this;
}
-(void) scanDevice {
[centralManager scanForPeripheralsWithServices:nil options:0];
}
- (id) init
{
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
peripherals = [[NSMutableArray alloc] init];
[centralManager scanForPeripheralsWithServices:nil options:0];
return self;
}
===
EDIT
Is this going to work? After establishing a connection per a device, I am going to create an instance handling service and characteristics in didConnectPeripheral delegation method like temperatureSensor example Apple provide. So each device maintains a connection separately.
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
service = [[[LeTemperatureAlarmService alloc] initWithPeripheral:peripheral controller:peripheralDelegate] autorelease];
[service start];
}