I'm building a bluetooth app using CoreBluetooth on iOS. Every time an app is launch user receives an ID, which is saved in Peripheral's LocalNameKey, begin to advertise with it and start to search for other users using CentralManager. Every user is identified by his local name and that's work fine.
Using CentralManager every user can write a value to another's user Peripheral's characteristics, and notify them about the change. That is also working.
Problem occurs here, after the connection is done and Peripheral executed didRecieveWriteRequests method, both CBPeripheralManager and CBCentralMange are being reseted and reinitialised. After that Peripheral's LocalNameKey is no longer my specific ID, but an iPhone's Given Name or (null). It ruins the whole idea of the app, but I'm pretty sure it can be done.
It works again, when I turn off and on bluetooth.
This how I cleanup Central after connection:
- (void)cleanup
{
// See if we are subscribed to a characteristic on the peripheral
if (self.peripheral.services != nil) {
for (CBService *service in self.peripheral.services) {
if (service.characteristics != nil) {
for (CBCharacteristic *characteristic in service.characteristics) {
if (characteristic.isNotifying) {
// It is notifying, so unsubscribe
[self.peripheral setNotifyValue:NO forCharacteristic:characteristic];
// And we're done.
return;
}
}
}
}
}
// If we've got this far, we're connected, but we're not subscribed, so we just disconnect
[self.manager cancelPeripheralConnection:self.peripheral];
}
This is how I reinitialise bluetooth's central and peripheral:
self.bluetoothPeripheral = nil;
self.bluetoothCentral = nil;
self.bluetoothPeripheral = [[BluetoothPeripheral alloc] initWithID:idNumber];
[self.bluetoothPeripheral loadManager];
self.bluetoothCentral = [[BluetoothCentral alloc] init];
Related
I'm trying to get my central (iOS device) to communicate with two peripherals (one iOS device, one not). Individually they work fine but I'm finding that once I get both peripherals involved, only the peripheral that was connected to the most recently is able to receive data from the central device. Is there a way to send data from the central to each peripheral without disconnecting and reconnecting the peripheral?
This is my code for writing to peripheral:
- (void) peripheral: (CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBChatacteristic *)characteristic error:(NSError *)error{
NSString *newValue = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Received: %# from %#", newValue, peripheral.name);
CBPeripheral *sender = peripheral;
if([newValue isEqualToString:#"ready"]){
NSData *messageValue = [#"challenge dataUsingNSUTF8StringEncoding];
[sender writeValue:messageValue forCharacteristic:_writeCharacteristic type:CBCharacteristicWriteWithResponse];
NSLog(#"Challenge sent to %#", sender.name);
}
Breakpoints indicate the code is being executed and the log shows "challenge sent" to the correct peripheral, it's just that peripheral never receives it.
Code for peripheral receiving:
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *) requests{
for(int i=0; i<requests.count; i++){
CBATTRequest *request = requests[i];
if([request.characteristic.UUID isEqual:_writeCharacteristic.UUID]){
NSString *stringValue = [[NSString alloc] initWithData:request.value encoding:NSASCIIStringEncoding];
NSLog(#"Write Request: %#", stringValue);
}
}
To answer your question: Yes it is possible that you can have multiple connections to different peripherals and read/write to them. iOS devices can handle up to 8 simultaneous connections.
For the implementation, have a look at the Core Bluetooth Programming Guide from Apple. All the things you need are explained there.
Just as a suggestion: If multiple devices (let's call them B and C) should receive data from 1 device (called A), I would use the peripheral role on device A that needs to send the data to the others. Because then devices B and C can scan, connect and subscribe to a characteristic and receive updates without having to read again.
I have a 4s device with iOS 7.1. I'm trying to implement some code that helps my device to behave like a beacon device but I'm getting the error "can only accept this command while in the powered on state".
I am implementing this piece of code:
#implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
beaconPeripheralData=[[NSDictionary alloc]init];
peripheralManager.delegate=self;
_locationManager.delegate=self;
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[self initWithBeacons];
}
-(void)initWithBeacons
{
NSNumber * power = [NSNumber numberWithInt:-63];
NSUUID *uuid=[[NSUUID alloc]initWithUUIDString:#"F24BDBE3-EB98-4A04-A621-91C088DC32D2"];
CLBeaconRegion *beaconReason=[[CLBeaconRegion alloc]initWithProximityUUID:uuid major:1 identifier:#"blackbean.com"];
beaconPeripheralData=[beaconReason peripheralDataWithMeasuredPower:power];
peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
[peripheralManager startAdvertising:beaconPeripheralData];
if ([peripheralManager isAdvertising])
{
NSLog(#"peripeheralMAnager is advertising");
}
else
{
NSLog(#"peripeheralMAnager is not advertising");
}
}
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOn:
NSLog(#"Powered on");
[peripheralManager startAdvertising:beaconPeripheralData];
break;
case CBPeripheralManagerStatePoweredOff:
NSLog(#"Powered Off");
[peripheralManager stopAdvertising];
break;
case CBPeripheralManagerStateUnsupported:
NSLog(#"Device not supported");
break;
default:
break;
}
}
#end
From the CBPeripheralManager documentation
Before you call CBPeripheralManager methods, the state of the
peripheral manager object must be powered on, as indicated by the
CBPeripheralManagerStatePoweredOn. This state indicates that the
peripheral device (your iPhone or iPad, for instance) supports
Bluetooth low energy and that its Bluetooth is on and available to
use.
In order to determine when the peripheral manager is ready, you need to implement the didUpdateState peripheral manager delegate method and start advertising once you get the powered on state, which you have done, but you also have a call to startAdvertising straight after you have allocated the CBPeripheralManager, which gives you the error message, because it won't yet be in the powered on state
I was following Apple's official Turning an iOS Device into an iBeacon article, and it states to use the following code:
func advertiseDevice(region : CLBeaconRegion) {
let peripheral = CBPeripheralManager(delegate: self, queue: nil)
let peripheralData = region.peripheralData(withMeasuredPower: nil)
peripheral.startAdvertising(((peripheralData as NSDictionary) as! [String : Any]))
}
However, the last line causes the "can only accept this command while in the powered on state" error.
To fix it, I had to:
Store a strong reference to the CBPeripheralManager so that it doesn't get deallocated.
Listen for the didUpdateState method, and only when it is poweredOn call the startAdvertising method.
Use a real iPhone to advertise. If you attempt to use the simulator, you'll get CBManagerState unsupported.
Here's an example of what the advertising code needs to look like to avoid this error instead of Apple's example:
class Foo: NSObject {
var manager: CBPeripheralManager?
func advertiseDevice() {
self.manager = CBPeripheralManager(delegate: self, queue: nil)
}
}
extension Foo: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
notifyOfStatusChange("iBeacon advertiser state change: \(peripheral.state.rawValue)")
if peripheral.state == .poweredOn {
let region = createBeaconRegion()!
let peripheralData = region.peripheralData(withMeasuredPower: nil)
peripheral.startAdvertising(((peripheralData as NSDictionary) as! [String : Any]))
}
}
}
I've searched SO for help on this and haven't found anything that will answer, address or get me pointed in the right direction so I've decided to post my issue.
I have a BT Central app running on an Apple TV. I have a peripheral app running on an iPhone and iPad.
The central app is able to connect to both peripheral devices just fine. I'm able to transfer all kinds of data to the central app and have control over all of the phases of the session (didDiscoverPeripheral, didDiscoverServices, didDiscoverChracteristics, etc.) All the delegate methods on both central and peripheral sides are behaving exactly as they should.
When the central app connects to a peripheral and it discovers the "writable" characteristic it sends (writes) an NSString to the peripheral with something like "Hi iPad, you've connected to central" or "Hi iPhone you've connected to central". In doing this I know that everyone is connected, discovered, processed and a reference to the peripherals is saved. None of this is an issue and behaves exactly as is documented by Apple.
On the central app I have a UIButton that performs a write to all of the connected peripherals. I attempt to loop through the connected peripherals and write something to each one inside the loop. Unfortunately only the last connected peripheral receives the written data.
I have a nice NSDictionary of all of the peripheral information and object that I enumerate through. I've even based the loop on the
retrieveConnectedPeripheralsWithServices method. My peripherals all use a custom class for their delegate so I know I'm not crossing the same delegate with multiple peripherals.
Inside the loop I can see that the peripheral is connected, the characteristic I'm targeting has writeWithResponse properties and write permissions. No reference to the peripheral has been lost or released. Everything maps out and looks great.
I just can't write to all the connected peripherals from inside a loop. I've tried queuing up the writes in an NSOperation and or dispatch_async in case it's a timing thing but nothing is working.
If the iPad is the last connect peripheral it gets the write. If the iPhone connect last then it gets the write. The last connected peripheral is the only clue I've got to go on but I'm just not seeing the problem.
At this point I'm out of sticks and carrots and several days of googling and SO searching. Sorry for the long post but I wanted to explain and also show that I'm not just asking out of laziness but have sincerely tried everything I know.
Thanks for any help.
Added relevant code:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *) advertisementData RSSI:(NSNumber *)RSSI {
//it's in range - have we already seen it?
if([self findPeripheralMatching:peripheral.identifier] == nil) {
//hack if the advertisingData local name or the peripheral GATT name is NULL
NSDictionary *dict = [self cleanupAdvertisementData:peripheral advertisementData:advertisementData];
if(dict == nil) {
[self stop];
[self start];
return;
}
//save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
XCBluetoothPeripheralDictionary *obj = [[XCBluetoothPeripheralDictionary alloc] init];
obj.peripheral = peripheral;
obj.advertisementData = dict;
[self.peripheralDictionary setObject:obj forKey:[peripheral.identifier UUIDString]];
//and connect is not connected...
if(peripheral.state == CBPeripheralStateDisconnected) {
[self.centralManager connectPeripheral:peripheral options:nil];
}
} //findPeripheralMatching
} //didDiscoverPeripheral
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
//make sure we get the discovery callbacks
XCBluetoothPeripheral *pd = [[XCBluetoothPeripheral alloc] initWithDelegate:self];
pd.subscriptionDictionary = self.subscriptionDictionary;
peripheral.delegate = pd;
//save a copy of the XCPeripheral object
[self.peripheralDictionary objectForKey:[peripheral.identifier UUIDString]].delegate = pd;
//discover and search only for services that match our service UUID
[peripheral discoverServices:#[[CBUUID UUIDWithString:self.serviceUUID]]];
//notify the delegate we connected
XC_SelectorAssert(#selector(bluetoothCentralDidConnect:), self.delegate)
if (self.delegate && [self.delegate respondsToSelector:#selector(bluetoothCentralDidConnect:)]) {
XCBluetoothPeripheralDictionary *dict = [self.peripheralDictionary objectForKey:[peripheral.identifier UUIDString]];
[self.delegate bluetoothCentralDidConnect:dict];
} else {
NSAssert(NO, XCMissingSelectorForProtocol);
}
} //didConnectPeripheral
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if(error) {
[self callbackError:error];
return;
}
//again, we loop through the array, and if the characteristic matches
//whats in the subscriptionDictionary then we subscribe to it
for (CBCharacteristic *characteristic in service.characteristics) {
if([self isDesiredCharachteristic:characteristic.UUID]) {
[peripheral discoverDescriptorsForCharacteristic:characteristic];
if(characteristic.properties & CBCharacteristicPropertyNotify) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
if(characteristic.properties & CBCharacteristicPropertyRead) {
}
if(characteristic.properties & CBCharacteristicPropertyWrite) {
NSLog(#"Writing value to %# - %#", peripheral.identifier, peripheral.name);
NSString *string = [NSString stringWithFormat:#"%# connected to %#",
peripheral.name,
[XCUtilities deviceName]];
[peripheral writeValue:[string dataUsingEncoding:NSUTF8StringEncoding]
forCharacteristic:characteristic
type:CBCharacteristicWriteWithResponse];
}
if(characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
}
[self.subscriptionDictionary objectForKey:(NSString *)characteristic.UUID].characteristic = characteristic;
} //if isMatching
} //for CBCharacteristic
} //didDiscoverCharacteristicsForService
The following method is called from an IBAction
-(void)writeValueToCharacteristic:(CBUUID *)cbuuid value:(NSString *)string {
//get a reference to the characteristic we specified
CBCharacteristic *chr = [self findCharacteristicMatching:cbuuid];
XC_CBCharacteristicAssert(chr)
//enumerate through the discovered peripherals
[self.peripheralDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, XCBluetoothPeripheralDictionary *obj, BOOL *stop){
XC_CBPeripheralAssert(obj.peripheral)
if(obj.peripheral.state == CBPeripheralStateConnected) {
//check the properties
if(chr.properties & CBCharacteristicPropertyWriteWithoutResponse ||
chr.properties & CBCharacteristicPropertyWrite) {
NSLog(#"Writing value to:\n%#\n%#\n%#\n%#\n%#",
key,
obj.advertisementData,
obj.peripheral.name,
obj.peripheral.delegate,
obj.peripheral);
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
[obj.peripheral writeValue:data forCharacteristic:chr
type:CBCharacteristicWriteWithResponse];
} else {
[self localError:XC_BTErrorPermissionWritable description:XCErrorWritingCharacteristic];
}
} //is connected
}];
} //writeValueToCharacteristic
I would think that if something were wrong with the way I'm saving the peripherals or my custom dictionary or the way I'm using this stuff then my writes would fail for all peripherals and not just one of the two I'm testing with from inside a loop. And I know I'm connected and discovered and all is well because when central initially processes these peripherals it writes to each one as sort of a confirmation that they are indeed ready to go.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests {
[peripheral respondToRequest:[requests objectAtIndex:0] withResult:CBATTErrorSuccess];
CBATTRequest *aRequest = requests[0];
NSData *aData = aRequest.value;
NSString *string = [[NSString alloc] initWithData:aData encoding:NSUTF8StringEncoding];
[self logToDelegate:string];
}
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if(error) {
[self callbackError:error];
return;
}
[self logToDelegate:#"didWriteValueForCharacteristic"];
} //didWriteValueForCharacteristic
When receiving multiple packets via BLE notifications, iOS is only giving me access to the final packet sent. I am using YMSCoreBluetooth to connect to a BLE peripheral with multiple services, each of which has multiple characteristics. I connect to the peripheral, discover the services and discover the characteristics of those services without a problem. My goal is to subscribe to a certain characteristic's notifications and receive via the notifications a series of data packets. My subscription is successful and I can see through use of NSLogs within my code that I am receiving the notifications containing the data. The issue is that when I go to access the data from each notification as it comes in, every notification gives me only the data contained in the last packet sent.
My code for receiving notifications is as follows:
- (void)notifyCharacteristicHandler:(YMSCBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(#"Error: Error in handling notification.\n%#", error);
}
else if ([characteristic.name isEqualToString:#"InterestingChar"]) {
if (self.firstNotify) {
self.mutableData = [[NSMutableData alloc] init];
self.firstNotify = NO;
}
NSData *data = [[NSData alloc] init];
data = characteristic.cbCharacteristic.value;
[self.mutableData appendData:data];
self.notifyCounter++;
NSLog(#"Notify received! Count: %ld \nData =%#",(long)self.notifyCounter,self.mutableData);
}
else NSLog(#"Other notification received");
}
For instance, if I receive 5 notifications with the following data:
1 ababababab
2 bcbcbcbcbc
3 cdcdcdcdcd
4 dedededede
5 efefefefef
My NSLog would print out efefefefef for the first notify data, efefefefef efefefefef for the second, and so on appending the last data value for each subsequent notify.
I am trying to send the notifications as quickly as possible from the peripheral using BLE. The connection interval is between 20ms and 40ms (iOS demands a range of at least 20ms) and three packets are being sent per connection interval.
EDIT:
Paulw11's suggestion worked beautifully. I fixed the issue by amending the YMSCB 'didUpdateValueForCharacteristic' method to obtain the value of the characteristic and pass it along with the pointer to the characteristic itself onto the 'notifyCharacteristicHandler' method. The amended method now looks as follows:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
__weak YMSCBPeripheral *this = self;
NSData *value = characteristic.value;
_YMS_PERFORM_ON_MAIN_THREAD(^{
YMSCBService *btService = [this findService:characteristic.service];
YMSCBCharacteristic *yc = [btService findCharacteristic:characteristic];
if (yc.cbCharacteristic.isNotifying) {
[btService notifyCharacteristicHandler:yc value:value error:error];
} else {
if ([yc.readCallbacks count] > 0) {
[yc executeReadCallback:characteristic.value error:error];
}
}
if ([this.delegate respondsToSelector:#selector(peripheral:didUpdateValueForCharacteristic:error:)]) {
[this.delegate peripheral:peripheral didUpdateValueForCharacteristic:characteristic error:error];
}
});
}
You obviously also need to amend the 'notifyCharacteristicHandler' method to accept the new argument.
Looking at the internal didUpdateValueForCharacteristic delegate method of the YMSCoreBluetooth library, it sends the data to your method using a "perform on main thread" and it doesn't capture the data - it just sends a reference to the characteristic. Also, it performs a "findCharacteristic" on the characteristic by executing a linear search through the array on the main thread even though this could have been done immediately on entering the delegate method on the current thread. Granted this isn't going to be a very big array but it seems that this library hasn't been created with performance in mind.
I suspect that you have a timing problem - by the time your method executes the data in the characteristic has been over written. If you have control over your peripheral, slow it right down for a test to see if the problem goes away.
If it is timing related then you could try a straight Core-Bluetooth implementation, or try a modification to YMSCoreBluetooth so that it captures the data earlier - perhaps if it created a copy of the peripheral at the start of didUpdateValueForCharacteristic and sent that to your method it would work.
I have my app running on two iOS device (both have 6.1.4) where one device acts as the Central and one acts as a Peripheral.
I have been successful in getting notifications (characteristics set up to notify) from the Peripheral over to the Central just fine.
However, I was wanting to write a value from the Central to the Peripheral to a specific writable characteristic but that always fails.
The peripheral:didWriteValueForCharacteristic:error: delegate's method is immediately called where the error description says:
"One or more parameters were invalid"
I have been searching the net for any clues but have yet to come up with what is wrong.
This is how I setup the characteristic on the peripheral side:
[[CBMutableCharacteristic alloc] initWithType:<My CBUUID> properties:CBCharacteristicPropertyWrite value:nil permissions:0];
I see that when I discover characteristics on the Central side that my writeable characteristic is there. I do store away a reference at that point that I later try to use when writing my value. The value that I am writing is just a text string that I convert to a NSData like so:
NSString *payloadMessage = #"Hello";
NSData *payload = [payloadMessage dataUsingEncoding:NSUTF8StringEncoding];
[peripheral writeValue:payload forCharacteristic:<myCharacteristic> type:CBCharacteristicWriteWithResponse];
The only odd thing I can see is that my characteristic's properties is set to 138. It looks like it is both writeable as well as having extended properties. Not sure why that is automatically added by the CoreBluetooth framework. Don't know if it matters.
It seems like something is wrong with the characteristic or the data that I am trying to send but for the life of me I can not figure it out. If anyone out there has any suggestions or ideas I would appreciate the help
I did receive an answer in the Bluetooh-dev list that solved my problem.
I didn't create my characteristic with the right permissions.
This is what I originally had:
[[CBMutableCharacteristic alloc] initWithType:<CBUUID> properties:CBCharacteristicPropertyWrite value:nil permissions:0];
This is what it should be:
[[CBMutableCharacteristic alloc] initWithType:<CBUUID> properties:CBCharacteristicPropertyWrite value:nil permissions: **CBAttributePermissionsWriteable**];
I got the same error when I accidentally provided an array of strings instead of CBUUIDs in discoverCharacteristics:forService:.
Incorrect:
[peripheral discoverCharacteristics:#[NOTIFY_CHARACTERISTIC_UUID, WRITE_WO_RESPONSE_CHARACTERISTIC_UUID]
forService:service];
Correct:
[peripheral discoverCharacteristics:#[[CBUUID UUIDWithString:NOTIFY_CHARACTERISTIC_UUID],
[CBUUID UUIDWithString:WRITE_WO_RESPONSE_CHARACTERISTIC_UUID]]
forService:service];
Create characteristic
self.writeableCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:CBUUIDWriteableCharacteristicCodeString] properties:CBCharacteristicPropertyWrite value:nil permissions:CBAttributePermissionsWriteable];
Add it to service and to PeripheralManager
CBMutableService *gameTransferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:CBUUIDServiceCodeString] primary:YES];
gameTransferService.characteristics = #[self.notifierCharacteristic, self.writeableCharacteristic];
[self.peripheralRoleManager addService:gameTransferService];
Make sure u are connected to another device
[self.service connectToPerephiral:self.dataSource[indexPath.row]];
Found u characteristic in connected peripheral and write data
for(CBService *service in self.discoveredPeripheral.services) {
if([service.UUID isEqual:[CBUUID UUIDWithString:<CBUUIDServiceCodeString>]]) {
for(CBCharacteristic *charac in service.characteristics) {
if([charac.UUID isEqual:[CBUUID UUIDWithString:<CBUUIDWriteableCharacteristicCodeString>]]) {
[self.discoveredPeripheral writeValue:data forCharacteristic:charac type:CBCharacteristicWriteWithResponse];
}
}
}
}
Add delegate method - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests and found u'r characteristic like
for (CBATTRequest *request in requests) {
if ([request.characteristic.UUID isEqual:[CBUUID UUIDWithString:CBUUIDNotifierCharacteristicCodeString]]) {
#ifdef DEBUG
NSString *dataString = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
NSLog(#"Received from manager - %#", dataString);
#endif
if (self.delegate && [self.delegate respondsToSelector:#selector(BLEServiceDidReceiveData:peripheral:service:)]) {
[self.delegate BLEServiceDidReceiveData:request.value peripheral:nil service:self];
}
}
}