I am trying to use android as a BLE peripheral (gatt server). I was able to get it advertising, and I can have the iOS app reeive notifications when certain characteristics are changed, but I am having trouble doing a simple writeValue.
On iOS I have:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (!error) {
for(int i = 0; i < service.characteristics.count; i++) { //Show every one
CBCharacteristic *c = [service.characteristics objectAtIndex:i];
if ([[c.UUID UUIDString] rangeOfString:@"43FF0001"].location!=NSNotFound){
self.shot_detected_characteristic = c;
[peripheral setNotifyValue:YES forCharacteristic:self.shot_detected_characteristic];
}else if ([[c.UUID UUIDString] rangeOfString:@"43FF0002"].location!=NSNotFound){
self.display_characteristic = c;
[self writeValue:@"ABC" forCharacteristic:c onPeripheral:peripheral];
}
NSLog(@"characteristic uuid: %@", [c.UUID UUIDString]);
}
} else {
NSLog(@"Characteristic discovery unsuccessful");
}
}
-(IBAction*) sendData {
CBPeripheral *p = [peripherals lastObject];
[self writeValue:@"ABC" forCharacteristic:self.display_characteristic onPeripheral:p];
}
-(void) writeValue:(NSString*)value forCharacteristic:(CBCharacteristic*)c onPeripheral:(CBPeripheral *)p {
NSData *d = [value dataUsingEncoding:NSUTF8StringEncoding];
if (c.properties & CBCharacteristicPropertyWrite) {
NSLog(@"Write with response"); //I am getting this message
[p writeValue:d forCharacteristic:c type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"Unable to write");
}
}
And on the Android side I have:
private BluetoothManager bluetoothManager;
private BluetoothGattServer gattServer;
private BluetoothGattCharacteristic displayCharacteristic;
private BluetoothDevice connected_device;
private BluetoothLeAdvertiser advertiser;
private void setupBLEService() {
bluetoothManager = (android.bluetooth.BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);
if( ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
}
gattServer = bluetoothManager.openGattServer(this, gattServerCallback);
String uuid_string = "92D9D153-9BE6-43FF-9672-3E2904628B9D";
BluetoothGattService service = new BluetoothGattService(UUID.fromString(uuid_string), BluetoothGattService.SERVICE_TYPE_PRIMARY);
shotDetectedCharacteristic = new BluetoothGattCharacteristic(
UUID.fromString("43FF0001-9BE6-43FF-9672-3E2904628B9D"),
BluetoothGattCharacteristic.PROPERTY_BROADCAST | BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
BluetoothGattDescriptor gD = new BluetoothGattDescriptor(UUID.fromString(NOTIFICATION_DESCRIPTOR), BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattDescriptor.PERMISSION_READ);
shotDetectedCharacteristic.addDescriptor(gD);
service.addCharacteristic(shotDetectedCharacteristic);
displayCharacteristic = new BluetoothGattCharacteristic(
UUID.fromString("43FF0002-2BE5-46FE-9162-1D2905728B9B"),
BluetoothGattCharacteristic.PROPERTY_BROADCAST | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(displayCharacteristic);
gattServer.addService(service);
}
private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
if (newState == 2){
connected_device = device;
advertiser.stopAdvertising(new AdvertiseCallback() {});
}
Log.i("chase", "Connection state changed: "+newState);
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
long millis = System.currentTimeMillis();
characteristic.setValue( ""+millis );
Log.i("chase", "reading: "+millis);
gattServer.sendResponse(device, requestId, 2, offset, characteristic.getValue());
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.i("chase", "characteristic write request");
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
characteristic.setValue(value);
gattServer.sendResponse(device, requestId, 2, offset, characteristic.getValue());
if (characteristic == displayCharacteristic){
//TODO: UPDATE DISPLAY
Log.i("chase", "display write request: "+value.toString());
}else
Log.i("chase", "char write request: "+value.toString());
}
...
}
The reason I think the problem is on the iOS side is that I've downloaded the nrf Connect app and am able to successfully write to the android characteristic using nrf connect. Or at least get to the log statements in BluetoothGattServerCallback#onCharacteristicWriteRequest
.
However, I have also written apps interfacing to BLE chips and I've never had trouble with just writing NSData with [p writeValue:d forCharacteristic:c type:CBCharacteristicWriteWithResponse];
Thanks in advance for any help or ideas of how to troubleshoot this.
Update:
When I say that it doesn't work. I don't get any error message at all. [p writeValue:d forCharacteristic:c type:CBCharacteristicWriteWithResponse];
is called, and the android side doesn't have any call to BluetoothGattServerCallback#onCharacteristicWriteRequest
and the ios side doesn't have any callback to -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
.
However, I did get something interesting. If I comment out the ios line:
//[peripheral setNotifyValue:YES forCharacteristic:self.shot_detected_characteristic];
Then I do get a call to android's onCharacteristicWriteRequest
and I do get a callback to didWriteValueForCharacteristic
(I get an error message, that it couldn't be written, but I think I can deal with that. At least I am getting something).
I am subscribing to notifications on a different characteristic, but on the same service and for some reason that is preventing me from writing to data on this other characteristic.