Android Bluetooth Low Energy (BLE) - Richtig kochen, Teil 3 (Lesen / Schreiben)

Inhalt

Teil 1 (Scannen)





Teil 2 (Verbinden / Trennen)





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()



     oder  onCharacteristicWrite()



    .





  • . , , .  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:





  1.  setCharacteristicNotification



    . Bluetooth .





  2.  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.












All Articles