Inhalt
Teil 3 (Lesen / Schreiben), Sie sind hier
Im vorherigen Artikel haben wir ausführlich über das Anschließen / Trennen von BLE-Geräten gesprochen. In diesem Artikel geht es um das Lesen und Schreiben von Eigenschaften sowie um das Ein- und Ausschalten von Benachrichtigungen .
Lese- und Schreibeigenschaften
Viele Entwickler, die mit BLE unter Android arbeiten, haben Probleme beim Lesen / Schreiben von BLE-Eigenschaften. Auf Stackoverflow voller Leute, die nur Verzögerung anbieten ... Die meisten dieser Tipps sind falsch.
Es gibt zwei Hauptgründe für die Probleme:
Lese- / Schreibvorgänge sind asynchron . Dies bedeutet, dass der Methodenaufruf sofort zurückgegeben wird, Sie das Aufrufergebnis jedoch etwas später erhalten - in den entsprechenden Rückrufen. Zum Beispiel
onCharacteristicRead()
oderonCharacteristicWrite()
.
. , , .
BluetoothGatt
, . Google … (. :mDeviceBusy
mDeviceBusyLock
).
, , , BLE. , , , . .
BluetoothGatt.java
mDeviceBusy
, :
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
return false;
}
if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
if (mService == null || mClientIf == 0) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized (mDeviceBusy) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG, "", e);
mDeviceBusy = false;
return false;
}
return true;
}
/, mDeviceBusy
false :
public void onCharacteristicRead(String address,
int status,
int handle,
byte[] value) {
if (VDBG) {
Log.d(TAG, "onCharacteristicRead() - Device=" + address
+ " handle=" + handle + " Status=" + status);
}
if (!address.equals(mDevice.getAddress())) {
return;
}
synchronized (mDeviceBusy) {
mDeviceBusy = false;
}
....
/ , . - . BLE , , . ! – . , , «» , . , , . BLE. iOS CoreBluetooth
(. : , Bluetooth Android).
BluetoothGatt
. , Android BluetoothGatt
, (. : , ). , Queue
Runnable
commandQueueBusy
:
private Queue<Runnable> commandQueue;
private boolean commandQueueBusy;
Runnable
. (readCharacteristic):
public boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) {
if(bluetoothGatt == null) {
Log.e(TAG, "ERROR: Gatt is 'null', ignoring read request");
return false;
}
// Check if characteristic is valid
if(characteristic == null) {
Log.e(TAG, "ERROR: Characteristic is 'null', ignoring read request");
return false;
}
// Check if this characteristic actually has READ property
if((characteristic.getProperties() & PROPERTY_READ) == 0 ) {
Log.e(TAG, "ERROR: Characteristic cannot be read");
return false;
}
// Enqueue the read command now that all checks have been passed
boolean result = commandQueue.add(new Runnable() {
@Override
public void run() {
if(!bluetoothGatt.readCharacteristic(characteristic)) {
Log.e(TAG, String.format("ERROR: readCharacteristic failed for characteristic: %s", characteristic.getUuid()));
completedCommand();
} else {
Log.d(TAG, String.format("reading characteristic <%s>", characteristic.getUuid()));
nrTries++;
}
}
});
if(result) {
nextCommand();
} else {
Log.e(TAG, "ERROR: Could not enqueue read characteristic command");
}
return result;
}
, ( ) , . Runnable
, readCharacteristic()
, . , (. : , ). false
, , «» , . nextCommand()
, :
private void nextCommand() {
// If there is still a command being executed then bail out
if(commandQueueBusy) {
return;
}
// Check if we still have a valid gatt object
if (bluetoothGatt == null) {
Log.e(TAG, String.format("ERROR: GATT is 'null' for peripheral '%s', clearing command queue", getAddress()));
commandQueue.clear();
commandQueueBusy = false;
return;
}
// Execute the next command in the queue
if (commandQueue.size() > 0) {
final Runnable bluetoothCommand = commandQueue.peek();
commandQueueBusy = true;
nrTries = 0;
bleHandler.post(new Runnable() {
@Override
public void run() {
try {
bluetoothCommand.run();
} catch (Exception ex) {
Log.e(TAG, String.format("ERROR: Command exception for device '%s'", getName()), ex);
}
}
});
}
}
, peek()
Runnable
, . .
:
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic,
int status) {
// Perform some checks on the status field
if (status != GATT_SUCCESS) {
Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Read failed for characteristic: %s, status %d", characteristic.getUuid(), status));
completedCommand();
return;
}
// Characteristic has been read so processes it
...
// We done, complete the command
completedCommand();
}
completedCommand()
. .
, Runnable
poll()
:
private void completedCommand() {
commandQueueBusy = false;
isRetrying = false;
commandQueue.poll();
nextCommand();
}
(, ), . , Runnable
completedCommand()
. – :
private void retryCommand() {
commandQueueBusy = false;
Runnable currentCommand = commandQueue.peek();
if(currentCommand != null) {
if (nrTries >= MAX_TRIES) {
// Max retries reached, give up on this one and proceed
Log.v(TAG, "Max number of tries reached");
commandQueue.poll();
} else {
isRetrying = true;
}
}
nextCommand();
}
, . , . , :
WRITE_TYPE_DEFAULT
( , , );
WRITE_TYPE_NO_RESPONSE
( ).
( , ).
Android , . Android, :
...
if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) {
mWriteType = WRITE_TYPE_NO_RESPONSE;
} else {
mWriteType = WRITE_TYPE_DEFAULT;
}
...
, , . , WRITE_TYPE_NO_RESPONSE
. !
, :
// Check if this characteristic actually supports this writeType
int writeProperty;
switch (writeType) {
case WRITE_TYPE_DEFAULT: writeProperty = PROPERTY_WRITE; break;
case WRITE_TYPE_NO_RESPONSE : writeProperty = PROPERTY_WRITE_NO_RESPONSE; break;
case WRITE_TYPE_SIGNED : writeProperty = PROPERTY_SIGNED_WRITE; break;
default: writeProperty = 0; break;
}
if((characteristic.getProperties() & writeProperty) == 0 ) {
Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Characteristic <%s> does not support writeType '%s'", characteristic.getUuid(), writeTypeToString(writeType)));
return false;
}
Android!
, bytesToWrite
:
characteristic.setValue(bytesToWrite);
characteristic.setWriteType(writeType);
if (!bluetoothGatt.writeCharacteristic(characteristic)) {
Log.e(TAG, String.format("ERROR: writeCharacteristic failed for characteristic: %s", characteristic.getUuid()));
completedCommand();
} else {
Log.d(TAG, String.format("writing <%s> to characteristic <%s>", bytes2String(bytesToWrite), characteristic.getUuid()));
nrTries++;
}
/
, . , .
Android:
setCharacteristicNotification
. Bluetooth .
1 2
unsigned int16
(Client Characteristic Configuration, - ). CCC UUID 2902.
1 2? « » Bluetooth . Bluetooth, – . , , , , . Android : Bluetooth , . , 1 , 2 – . , 0. , CCC.
iOS setNotify()
. , Android, , , :
private final String CCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";
public boolean setNotify(BluetoothGattCharacteristic characteristic,
final boolean enable) {
// Check if characteristic is valid
if(characteristic == null) {
Log.e(TAG, "ERROR: Characteristic is 'null', ignoring setNotify request");
return false;
}
// Get the CCC Descriptor for the characteristic
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(CCC_DESCRIPTOR_UUID));
if(descriptor == null) {
Log.e(TAG, String.format("ERROR: Could not get CCC descriptor for characteristic %s", characteristic.getUuid()));
return false;
}
// Check if characteristic has NOTIFY or INDICATE properties and set the correct byte value to be written
byte[] value;
int properties = characteristic.getProperties();
if ((properties & PROPERTY_NOTIFY) > 0) {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else if ((properties & PROPERTY_INDICATE) > 0) {
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
} else {
Log.e(TAG, String.format("ERROR: Characteristic %s does not have notify or indicate property", characteristic.getUuid()));
return false;
}
final byte[] finalValue = enable ? value : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
// Queue Runnable to turn on/off the notification now that all checks have been passed
boolean result = commandQueue.add(new Runnable() {
@Override
public void run() {
// First set notification for Gatt object if(!bluetoothGatt.setCharacteristicNotification(descriptor.getCharacteristic(), enable)) {
Log.e(TAG, String.format("ERROR: setCharacteristicNotification failed for descriptor: %s", descriptor.getUuid()));
}
// Then write to descriptor
descriptor.setValue(finalValue);
boolean result;
result = bluetoothGatt.writeDescriptor(descriptor);
if(!result) {
Log.e(TAG, String.format("ERROR: writeDescriptor failed for descriptor: %s", descriptor.getUuid()));
completedCommand();
} else {
nrTries++;
}
}
});
if(result) {
nextCommand();
} else {
Log.e(TAG, "ERROR: Could not enqueue write command");
}
return result;
}
CCC onDescriptorWrite
. CCC . , , .
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
final BluetoothGattDescriptor descriptor,
final int status) {
// Do some checks first
final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
if(status!= GATT_SUCCESS) {
Log.e(TAG, String.format("ERROR: Write descriptor failed value <%s>, device: %s, characteristic: %s", bytes2String(currentWriteBytes), getAddress(), parentCharacteristic.getUuid()));
}
// Check if this was the Client Configuration Descriptor if(descriptor.getUuid().equals(UUID.fromString(CCC_DESCRIPTOR_UUID))) {
if(status==GATT_SUCCESS) {
// Check if we were turning notify on or off
byte[] value = descriptor.getValue();
if (value != null) {
if (value[0] != 0) {
// Notify set to on, add it to the set of notifying characteristics notifyingCharacteristics.add(parentCharacteristic.getUuid());
}
} else {
// Notify was turned off, so remove it from the set of notifying characteristics notifyingCharacteristics.remove(parentCharacteristic.getUuid());
}
}
}
// This was a setNotify operation
....
} else {
// This was a normal descriptor write....
...
});
}
completedCommand();
}
– isNotifying()
:
public boolean isNotifying(BluetoothGattCharacteristic characteristic) {
return notifyingCharacteristics.contains(characteristic.getUuid());
}
, , . Android-5 15. 7 4. 15 . , , .
, / , / , . , BLE :
. , , Bluetooth Health Thermometer . , . , ;
. , . BLE, BLE, , . , : , , , ..
, – . , , (30Hz ).
:
BluetoothGattCharacteristic
, .
:
, Android ( ). , Android ;
Android BluetoothGattCharacteristic . (services discovering) . , Android
BluetoothGattCharacteristic
. (race condition) .
, . , .
, :
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic) {
// Copy the byte array so we have a threadsafe copy
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(
characteristic.getValue(),
0, value, 0,
characteristic.getValue().length);
// Characteristic has new value so pass it on for processing
bleHandler.post(new Runnable() {
@Override
public void run() {
myProcessor.onCharacteristicUpdate(BluetoothPeripheral.this, value, characteristic);
}
});
}
BLE Android. BLE , - .
Android :
(
main
);
BluetoothGattCallback
(Binder
);
main . Binder . Binder, Android , Binder . , sleep()
- . , BluetoothGatt
, Binder, .
:
BluetoothGattCallback
, (. : main - , , UI, );
Binder
;
– Handler
. , Handler
onCharacteristicUpdate
.
:
Handler bleHandler = new Handler();
Handler
main
:
Handler bleHandler = new Handler(Looper.getMainLooper());
Scrollen Sie zurück und sehen Sie sich unsere Methode an nextCommand()
. Jede Methode Runnable
wird in ihrer eigenen ausgeführt Handler
. Daher stellen wir sicher, dass alle Befehle außerhalb des Threads ausgeführt werden Binder
.
Weiter: Kleben
In diesem Artikel haben wir herausgefunden, wie man Merkmale liest und schreibt, Benachrichtigungen / Benachrichtigungen ein- und ausschaltet. Im nächsten Artikel werden wir den Prozess der Konjugation mit einem Gerät (Bindung) im Detail untersuchen.
Sie können es kaum erwarten, mit BLE zu arbeiten? Probieren Sie meine Blessed for Android-Bibliothek aus . Es verwendet alle Ansätze in dieser Artikelserie und erleichtert die Arbeit mit BLE in Ihrer Anwendung.