问题
I am working on a Bluetooth LE application for iOS. I am using the Core Bluetooth framework within iOS to handle all communications.
Question & Description:
When I use a single tag, despite numerous connections and disconnections, the single tag connects seamlessly and the phone discovers it services.
Also, when multiple Bluetooth LE tags connect for the first time, they connect seamlessly and the phone discovers their services.
When the tags get disconnected and then reconnect to the phone, the tags connect fine. But one of the two tags (either one) does not seem to advertise its services. i.e when the app is open and the tag reconnects, the DiscoverServices method does not call the didDiscoverServices delegate.
Why is this happening only when connection with multiple devices takes place.
I have set the peripheral.delegate correctly. I have tried everything, including doing repeated re-connect, repeated DiscoverServices calls to the tag. Nothing seems to work.
How can I re-connect to multiple tags to the phone and still discover all services.
Please help
Thanks,
Manju
回答1:
I had the same problem but realized that I wasn't setting the delegate
to CBPeripheral
after didConnectPeripheral
is called.
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"Peripheral Connected: %@", peripheral.name);
peripheral.delegate = self;
if (peripheral.services) {
[self peripheral:peripheral didDiscoverServices:nil];
} else {
[peripheral discoverServices:@[[CBUUID UUIDWithString:CUSTOM_UUID]]];
}
}
回答2:
I was facing a similar issue with CoreBluetooth to connect to Bluetooth LE devices, in my case connecting to iOS devices (peripherals) from my Mac (central).
If I get you correctly, the pattern is quite consistent, the first time I run my Mac app for debuging, it always detected and connected to any bluetooth LE devices (peripherals), most importantly, it also discover their services/characteristics fine. The problem starts on the second run (for example, change some code, hit cmd-R to relaunch the debug). The central still detects peripherals and connects to them, but, it fails to discover any services/characteristics. In other words, the delegate peripheral:didDiscoverServices:
and peripheral:didDiscoverCharacteristicsForService:error:
never get called.
The solution after a lot of trial and errors, is surprisingly simple. It seems that CoreBluetooth caches services
and characteristics
for peripherals that are still connected, although locally it looks like it had been disconnected to the app, the peripheral still maintains a bluetooth connection to the system. For these type of connections, there is no need to (re)discover the services and characteristics, just access them directly from the peripheral object, check for nil
to know if you should discover them. Also, as mentioned, since the peripheral is in a state that is in between connections, it is best to call cancelPeripheralConnection:
right before attempting to connect. The gist of it as following, assuming we already discovered the peripheral to connects to:
-(void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
[central cancelPeripheralConnection:peripheral]; //IMPORTANT, to clear off any pending connections
[central connectPeripheral:peripheral options:nil];
}
-(void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
peripheral.delegate = self;
if(peripheral.services)
[self peripheral:peripheral didDiscoverServices:nil]; //already discovered services, DO NOT re-discover. Just pass along the peripheral.
else
[peripheral discoverServices:nil]; //yet to discover, normal path. Discover your services needed
}
-(void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
for(CBService* svc in peripheral.services)
{
if(svc.characteristics)
[self peripheral:peripheral didDiscoverCharacteristicsForService:svc error:nil]; //already discovered characteristic before, DO NOT do it again
else
[peripheral discoverCharacteristics:nil
forService:svc]; //need to discover characteristics
}
}
-(void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for(CBCharacteristic* c in service.characteristics)
{
//Do some work with the characteristic...
}
}
This works well for me for a CBCentralManager in Mac app. Never tested it in iOS, but I assume it should be quite similar.
回答3:
Turns out that there was command I was issuing to the device in the"didDiscovercharacteristicsForService" delegate method which was causing the connection instability.
If you are facing similar issues, I suggest you to let the delegate method complete without any intervention (of any kind) and pass the CBPeripheral to another function designed by you to pass any values / issue a command to the devices.
Thanks anyway Wilhemsen.
So the steps are as follows..
1> Search for Tag,
2> If in range, CONNECT to Tag
3> If Connected, call DISCOVER services method (do not interrupt)
4> IN DidDiscoverServices, call DISCOVER Characteristics Method
..
In DidDiscoverCharacteristics Method, wait until all Characteristics are discovered..
Then , at the end, call a function in your code that will do the necessary setup..
...
Code Sample
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
for (int i=0; i < peripheral.services.count; i++) {
CBService *s = [peripheral.services objectAtIndex:i];
printf("Fetching characteristics for service with UUID : %s\r\n",[self CBUUIDToString:s.UUID]);
[peripheral discoverCharacteristics:nil forService:s];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (!error)
{
CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)];
if([self compareCBUUID:service.UUID UUID2:s.UUID])
{
// This is when the **Tag** is completely connected to the Phone and is in a mode where it can accept Instructions from Device well.
printf("Finished discovering characteristics");
// Now Do the Setup of the Tag
[self setupPeripheralForUse:peripheral];
}
}
else
{
printf("Characteristic discorvery unsuccessfull !\r\n");
}
}
-(void) setupPeripheralForUse:(CBPeripheral *)peripheral
{
// DO all your setup in this function. Separate Perpiheral Setup from the process that synchronizes the tag and the phone.
}
回答4:
I've been having the same issue. it seems to occur about 1/3 the time in my case. I tried the solution provided by P.L. but I had no success on iOS. There are perhaps many moving pieces at work here which can contribute to this problem (bluetooth device firmware, CoreBluetooth, etc.) but I solved it by simply keeping track of the devices which are pending service/characteristic discovery in an NSMutableDictionary and using GCD to check if it had completed it's discovery in a reasonable amount of time and trying again if necessary. So something like this:
- (void)requestDiscoverServicesForPeripheral:(CBPeripheral *)peripheral
{
peripheral.delegate = self;
NSString *uuid =[self stringUUIDForUUID:peripheral.UUID];
NSLog(@"discovering %d services ", self.interestingServices.count);
[peripheral discoverServices:self.interestingServices];
[self.pendingConnectionDevices setObject:peripheral forKey:uuid];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if ([weakSelf.pendingConnectionDevices objectForKey:uuid]) {
//request discover services again
NSLog(@"services did not discover, requesting again!");
[weakSelf.btManager cancelPeripheralConnection:peripheral];
[weakSelf.btManager connectPeripheral:peripheral options:nil];
}
});
}
Then, when peripheral:didDiscoverServices:error
calls back I remove it from my pendingConnectionDevices dictionary. This seems to work pretty well. I've seen it try to discover services up to 3 times before succeeding. I hope this helps.
回答5:
Sometimes it's hardware issue. I just encountered a case that the hardware will enter a sleeping mode which is scannable, connectable, but not calling back at all for discoverServices.
Also there's a situation that happened to me when developing BLE all night and suddenly, the device become silence for discoverServices no matter what I did. Finally I found it will become normal if I reboot my iPhone5s.
And one day the bluetooth hardware engineer told me that the spec of BLE only says device must be connectable again within 30 seconds. So if I connect and disconnect over and over again, it may not working as expected... Which I still doubt it.
回答6:
I was trying to apply all above answers but it wasn't working for me because I was missing CBPeripheralDelegate
on class.
class BLEHandler : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate{
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
print("Connected: \(peripheral.state == .connected ? "YES" : "NO")")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service: CBService in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
print(service)
}
}
}
回答7:
I'm doing everything you say in your answer, but I still see this issue from time to time. My guess is Core Bluetooth gets into a weird state, probably due to a specific sequence of connection, subscription and disconnection.
The only way I've found to fix the problem is to restart the iOS device. This issue might be fixed in iOS 7.1 though.
回答8:
My headache was caused by lack of strong reference to peripheral
So I've add the reference and the problem is gone
private var activePeripheral: CBPeripheral?
func connect(_ peripheral: CBPeripheral) {
disconnect(peripheral)
activePeripheral = peripheral
self.central.connect(peripheral, options: nil)
}
来源:https://stackoverflow.com/questions/12004623/iphone-does-not-discover-services-on-a-bluetooth-le-tag-on-reconnection