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
Related
Am new to Objective C Am trying to read data from an arduino peripheral with a specific service and characteristic
there are two characteristics with for two different services
//arduino code
BLEService dataService("938548e6-c655-11ea-87d0-0242ac130003"); //Data reading
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); //LED control service
// Initialize dataService characteristics
BLECharacteristic dataArray("77539407-6493-4b89-985f-baaf4c0f8d86", BLERead | BLENotify, 114); // standard 16-bit characteristic UUID
// Initialize ledService characteristics
BLECharacteristic eventChar("19B10001-E8F2-537E-4F6C-D104768A1213", BLERead | BLEWrite, 1);
I was able to notify and got the read value for the first characteristics for when am trying to write for second characteristic am not receiving data from peripheral
for (CBCharacteristic *characteristic in service. characteristics) {
NSLog(#"Charecteristics %#", characteristic.UUID);
if ([characteristic.UUID.UUIDString containsString: #"77539407"]) {
NSLog(#"Specific Characteristics %#", characteristic.UUID);
//Working........ Characteristic
[peripheral setNotifyValue: YES forCharacteristic: characteristic];
}
if ([characteristic.UUID.UUIDString containsString: #"19B10001"]) {
NSLog(#"Specific Charecteristics %#", characteristic.UUID);
NSData *data = [#"01" dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"WRITING DATA");
NSLog(#"%# : dataaaaaa",data);
[peripheral writeValue: data forCharacteristic: characteristic
type: CBCharacteristicWriteWithResponse];
}
}
after writing the data it goes to the didWriteValueForCharacteristic where I get null for the writeCallBack can someone explain me what mistake am doing
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(#"didWrite");
NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
NSLog(#" writeCallback : %# ",key);
RCTResponseSenderBlock writeCallback = [writeCallbacks objectForKey:key];
NSLog(#" writeCallback : %# ",writeCallback);
if (writeCallback) {
if (error) {
NSLog(#"%#", error);
[writeCallbacks removeObjectForKey:key];
writeCallback(#[error.localizedDescription]);
} else {
if ([writeQueue count] == 0) {
NSLog(#"NO 1");
[writeCallbacks removeObjectForKey:key];
writeCallback(#[]);
}else{
// Remove and write the queud message
NSLog(#"NO 2");
NSData *message = [writeQueue objectAtIndex:0];
[writeQueue removeObjectAtIndex:0];
[peripheral writeValue:message forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
}
Below image is the log
OUTPUT LOG
Sending data through the iOS (writeValue: forCharacteristic: type :) method works fine without error, but the value does not change. Do you know why?
> 2017-06-27 14:53:54.846963+0900 BluetoothSample[12662:5477661] Did
> write characteristic <CBCharacteristic: 0x1700ad500, UUID =
> 2A588021-4FB2-40F5- 8204-85315DEF11C5, properties = 0xA, value =
> <1357>, notifying = NO>
uint8_t val = 123;
NSData * valData = [NSData dataWithBytes:(void*)&val length:sizeof(val)];
[self.discoveredPeripheral writeValue:valData forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
- (void) peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(#"didWriteValueForCharacteristic With error: %#", [error localizedDescription]);
self.receivePaketDataTextView.text = [[NSString alloc] initWithFormat:#"%#",[error localizedDescription]];
return ;
}
self.receivePaketDataTextView.text = [[NSString alloc] initWithFormat:#"%#",characteristic.value];
}
When I try to write, only the same logs are printed.
Try:
[self.discoveredPeripheral writeValue: valData
forCharacteristic:self.usedCharacteristic
type:CBCharacteristicWriteWithoutResponse];
Use type: CBCharacteristicWriteWithoutResponse
Also check if you have set the notifier
[self.discoveredPeripheral setNotifyValue:YES forCharacteristic:self.usedCharacteristic];
Before hand in your delegates.
On your iOS central the value of the characteristic in didWriteValueForCharacteristic does not reflect the change you just wrote.
You need to issue an explicit read to get the new value.
This is by design, since there is no guarantee that the value you have just written is the current value of the characteristic; only the peripheral can give you the current value.
I am developing for BLE in objective-C.
I define the UUID like the following code:
static NSString *const LEDStateCharacteristicUUID = #"ffffffff-7777-7uj7-a111-d631d00173f4";
I want to write characteristic to BLE device by following code , it need to pass 3 parameter:1.Data 2.Characteristic 3.type
CBCharacteristic *chara = ??? // how to set the characteristic via above UUID let it can pass to following function?
[peripheral writeValue:data forCharacteristic:chara type:CBCharacteristicWriteWithoutResponse];
How to set the characteristic via above UUID let it can pass to writeCharacteristic function?
Thanks in advance.
First you need to know what service UUID and what characteristic UUID from this service you want. When you have these UUIDs you can use this logic below to get the right characteristic instance:
- (CBCharacteristic *)characteristicWithUUID:(CBUUID *)characteristicUUID forServiceUUID:(CBUUID *)serviceUUID inPeripheral:(CBPeripheral *)peripheral {
CBCharacteristic *returnCharacteristic = nil;
for (CBService *service in peripheral.services) {
if ([service.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:characteristicUUID]) {
returnCharacteristic = characteristic;
}
}
}
}
return returnCharacteristic;
}
You need to set a delegate for the peripheral:
peripheral.delegate = self;
In didConnectToPeripheral you discover the peripheral's services. In the peripheral's didDiscoverServices callback you then discover characteristics. In didDiscoverCharacteristics you then loop through each characteristic and save them in a variable.
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", error.localizedDescription);
} else {
NSLog(#"Discovered characteristics for %#", peripheral);
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID.UUIDString isEqualToString: LEDStateCharacteristicUUID]) {
// Save a reference to it in a property for use later if you want
_LEDstateCharacteristic = characteristic;
[peripheral writeValue:data forCharacteristic: _LEDstateCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
}
}
}
So I'm working with two apps. One acts as the central, the other is the peripheral. There could be more than one devices that act as the peripheral. When the Central launches, it begins scanning for peripherals that are running a service I call UUIDService. This service has two characteristics: vendorUUID (read) and totalAmount (write).
When the Peripheral launches, it begins advertising UUIDService with it's -[UIDevice identifierForVendor] string as the value for vedorUUID and nil for totalAmount.
On Central, I have a singleton called BluetoothManager which holds a mutable array called connectedUsers to keep track of peripherals that are connected to the central. This is an array of BTUser objects which has two properties: uuid (NSString) and periph (CBPeripheral).
When Central discovers a peripheral, it creates the user object and fills in the reference to periph:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(#"Discovered periph advertising UUID Service");
BTUser* user = [[BTUser alloc] init];
user.periph = peripheral;
[_centralManager connectPeripheral:user.periph options:nil];
[_connectedUsers addObject:user];
NSLog(#"User periph: %#", user.periph.identifier);
}
This block of code works. _connectedUsers now contains one BTUser with a periph. Then it connects:
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(#"Connected periph: %#", peripheral.name);
BTUser* thisUser;
for (BTUser* user in _connectedUsers)
{
if ([user.periph.identifier isEqual: peripheral.identifier])
{
thisUser = user;
break;
}
}
thisUser.periph.delegate = self;
[thisUser.periph discoverServices:#[_uuidServiceUUID]];
}
I've confirmed this works, and the peripheral goes on to discover the service and and related characteristics. The issue I'm running into is when I go to use the reference to the peripheral to write to totalAmount.
-(void)initializePaymentWithUser:(BTUser*)user forAmount:(CGFloat)amount
{
_isListeningForUsers = NO;
[self stopScanningForUsers];
NSLog(#"disconnecting from all other users");
for (BTUser* u in _connectedUsers)
{
if (![u isEqual:user])
{
[u disconnectFromCentral:_centralManager];
}
}
CBService* uuidService = user.periph.services[0];
for (CBCharacteristic* cha in uuidService.characteristics)
{
if ([cha.UUID isEqual:_uuidServiceTotalAmountUUID])
{
NSLog(#"Writing total amount %.2f", amount);
NSData* data = [[NSString stringWithFormat:#"%.2f", amount] dataUsingEncoding:NSUTF8StringEncoding];
[user.periph writeValue:data
forCharacteristic:cha
type:CBCharacteristicWriteWithResponse];
break;
}
}
}
It makes it to the block that writes the value, and user.periph still has the same -[CBPeripheral identifier] as it did when it originally connected. But I'm getting the error:
CoreBluetooth[WARNING] <CBPeripheral: 0x14ed28c0 identifier = B4CB35CC-2C78-7543-B083-E0B373EF66D0, Name = "iPhone", state = connected> is not a valid peripheral
And the peripheral never calls -peripheral:didWriteValueForCharacteristic:error.
For completion to this questions, this is the block of code from the Peripheral responsible for advertising the UUIDService:
-(void)advertiseVendorUUID:(NSString*)uuid
paymentInitiatedBlock:(DOBluetoothPeripheralBlock)completion
{
_uuidService = [[CBMutableService alloc] initWithType: _uuidServiceUUID
primary:YES];
NSLog(#"Adding vendor id to characteristic: %#", uuid);
NSData* vendorData = [uuid dataUsingEncoding:NSUTF8StringEncoding];
_uuidServiceVendorUUIDChar = [[CBMutableCharacteristic alloc] initWithType:_uuidServiceVendorUUIDUUID
properties:CBCharacteristicPropertyRead
value:vendorData
permissions:CBAttributePermissionsReadable];
_uuidServiceTotalAmountChar = [[CBMutableCharacteristic alloc] initWithType:_uuidServiceTotalAmountUUID
properties:CBCharacteristicPropertyWrite
value:nil
permissions:CBAttributePermissionsWriteable];
_uuidService.characteristics = #[_uuidServiceVendorUUIDChar, _uuidServiceTotalAmountChar];
[_peripheralManager addService:_uuidService];
[_peripheralManager startAdvertising:#{CBAdvertisementDataServiceUUIDsKey: #[_uuidService.UUID],
CBAdvertisementDataLocalNameKey: #"UUID"}];
}
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];
}
}
}