android - things - bluetooth le devices
Android BLE API: notificación del GATT no recibida (10)
Dispositivo utilizado para la prueba: Nexus 4, Android 4.3
La conexión funciona bien pero nunca se llama al método onCharacteristicChanged
de mi devolución de llamada. Sin embargo, me estoy registrando para setCharacteristicNotification(char, true)
notificaciones usando setCharacteristicNotification(char, true)
en onServicesDiscovered
y esa función incluso devuelve true.
Registro del dispositivo (en realidad, no hay mensajes cuando las notificaciones deberían aparecer / se envían a través del dispositivo Bluetooth):
07-28 18:15:06.936 16777-16809/de.ffuf.leica.sketch D/BluetoothGatt: setCharacteristicNotification() - uuid: 3ab10101-f831-4395-b29d-570977d5bf94 enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.GattService: registerForNotification() - address=C9:79:25:34:19:6C enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_reg_for_notification
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10101-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10102-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.976 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_upstreams_evt: Event 9
Las notificaciones GATT funcionan bien con iOS y la aplicación básicamente hace lo mismo que con Android (se registra para notificaciones, etc.).
¿Alguien más ha experimentado esto con una posible solución?
@ Boni2k - Tengo los mismos problemas. En mi caso, tengo 3 características de notificación y un puñado de características de lectura / escritura.
Lo que sí encontré es que existe cierta dependencia entre writeGattDescriptor
y readCharacteristic
. Todos los writeGattDescriptors deben estar primero y completos antes de emitir cualquier llamada de caracterización de lectura.
Aquí está mi solución usando Queues
. Ahora recibo notificaciones y todo lo demás funciona bien:
Crea dos colas como esta:
private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();
Luego escriba todos sus descriptores inmediatamente después del descubrimiento con este método:
public void writeGattDescriptor(BluetoothGattDescriptor d){
//put the descriptor into the write queue
descriptorWriteQueue.add(d);
//if there is only 1 item in the queue, then write it. If more than 1, we handle asynchronously in the callback above
if(descriptorWriteQueue.size() == 1){
mBluetoothGatt.writeDescriptor(d);
}
}
y esta devolución de llamada:
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Callback: Wrote GATT Descriptor successfully.");
}
else{
Log.d(TAG, "Callback: Error writing GATT Descriptor: "+ status);
}
descriptorWriteQueue.remove(); //pop the item that we just finishing writing
//if there is more to write, do it!
if(descriptorWriteQueue.size() > 0)
mBluetoothGatt.writeDescriptor(descriptorWriteQueue.element());
else if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readQueue.element());
};
El método para leer una característica normalmente se ve así:
public void readCharacteristic(String characteristicName) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
BluetoothGattService s = mBluetoothGatt.getService(UUID.fromString(kYourServiceUUIDString));
BluetoothGattCharacteristic c = s.getCharacteristic(UUID.fromString(characteristicName));
//put the characteristic into the read queue
readCharacteristicQueue.add(c);
//if there is only 1 item in the queue, then read it. If more than 1, we handle asynchronously in the callback above
//GIVE PRECEDENCE to descriptor writes. They must all finish first.
if((readCharacteristicQueue.size() == 1) && (descriptorWriteQueue.size() == 0))
mBluetoothGatt.readCharacteristic(c);
}
y mi devolución de llamada leída:
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
readCharacteristicQueue.remove();
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
else{
Log.d(TAG, "onCharacteristicRead error: " + status);
}
if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readCharacteristicQueue.element());
}
Al establecer el valor en el descriptor en lugar de poner descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
, ponga descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
. Las devoluciones de llamada para onCharacteristicChanged se llaman ahora.
Aquí hay una manera simple de hacerlo, pero avíseme si ve algún inconveniente.
Paso 1 Declarar variables booleanas
private boolean char_1_subscribed = false;
private boolean char_2_subscribed = false;
private boolean char_3_subscribed = false;
Paso 2 suscríbase a la primera característica en la devolución de llamada onServicesDescubierta:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_1)); char_1_subscribed = true;
}
Paso 3
Suscribirse a cualquier otro después de los incendios de devolución de llamada onCharacteristicChanged
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if(UUID_CHAR_1.equals(characteristic.getUuid()))
{
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_2)); char_2_subscribed = true;
}
if(UUID_CHAR_2.equals(characteristic.getUuid()))
{
if(!char_3_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_3)); char_3_subscribed = true;
}
}
Bueno, este nombre de la API seguramente genera algunas confusiones para el desarrollador de aplicaciones si él / ella no era el programador de fondo de Bluetooth.
Desde la perspectiva de la especificación del núcleo de Bluetooth, cita de la especificación central 4.2 Vol 3, Parte G, sección 3.3.3.3 "Configuración de características del cliente":
El valor del descriptor característico es un campo de bit. Cuando se establece un bit, esa acción se habilitará, de lo contrario no se usará.
y la sección 4.10
Las notificaciones se pueden configurar utilizando el descriptor de configuración de características del cliente (consulte la Sección 3.3.3.3).
que establece claramente que si el cliente desea recibir la notificación (o indicación, que necesita respuesta) del servidor, debe escribir el bit de "Notificación" a 1 (el bit "Indicación" también a 1 en caso contrario).
Sin embargo, el nombre "setCharacteristicNotification" nos da una pista es que si establecemos los parámetros de esta API como TURE, el cliente obtendría notificaciones; desafortunadamente, esta API solo establece el bit local para permitir que se envíe la notificación a las aplicaciones en caso de notificación remota. Ver el código de Bluedroid:
/*******************************************************************************
**
** Function BTA_GATTC_RegisterForNotifications
**
** Description This function is called to register for notification of a service.
**
** Parameters client_if - client interface.
** bda - target GATT server.
** p_char_id - pointer to GATT characteristic ID.
**
** Returns OK if registration succeed, otherwise failed.
**
*******************************************************************************/
tBTA_GATT_STATUS BTA_GATTC_RegisterForNotifications (tBTA_GATTC_IF client_if,
BD_ADDR bda,
tBTA_GATTC_CHAR_ID *p_char_id)
{
tBTA_GATTC_RCB *p_clreg;
tBTA_GATT_STATUS status = BTA_GATT_ILLEGAL_PARAMETER;
UINT8 i;
if (!p_char_id)
{
APPL_TRACE_ERROR("deregistration failed, unknow char id");
return status;
}
if ((p_clreg = bta_gattc_cl_get_regcb(client_if)) != NULL)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if ( p_clreg->notif_reg[i].in_use &&
!memcmp(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN) &&
bta_gattc_charid_compare(&p_clreg->notif_reg[i].char_id, p_char_id))
{
APPL_TRACE_WARNING("notification already registered");
status = BTA_GATT_OK;
break;
}
}
if (status != BTA_GATT_OK)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if (!p_clreg->notif_reg[i].in_use)
{
memset((void *)&p_clreg->notif_reg[i], 0, sizeof(tBTA_GATTC_NOTIF_REG));
p_clreg->notif_reg[i].in_use = TRUE;
memcpy(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN);
p_clreg->notif_reg[i].char_id.srvc_id.is_primary = p_char_id->srvc_id.is_primary;
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.srvc_id.id, &p_char_id->srvc_id.id);
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.char_id, &p_char_id->char_id);
status = BTA_GATT_OK;
break;
}
}
if (i == BTA_GATTC_NOTIF_REG_MAX)
{
status = BTA_GATT_NO_RESOURCES;
APPL_TRACE_ERROR("Max Notification Reached, registration failed.");
}
}
}
else
{
APPL_TRACE_ERROR("Client_if: %d Not Registered", client_if);
}
return status;
}''
Entonces, lo que importa es la acción de escritura del descriptor.
Este me funciona:
para notificar al dispositivo maestro que algunas características cambian, llame a esta función en su dispositivo periférico:
private BluetoothGattServer server;
//init....
//on BluetoothGattServerCallback...
//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);
en su dispositivo maestro: habilite setCharacteristicNotification después de descubrir el servicio:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
services = mGatt.getServices();
for(BluetoothGattService service : services){
if( service.getUuid().equals(SERVICE_UUID)) {
characteristicData = service.getCharacteristic(CHAR_UUID);
for (BluetoothGattDescriptor descriptor : characteristicData.getDescriptors()) {
descriptor.setValue( BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mGatt.writeDescriptor(descriptor);
}
gatt.setCharacteristicNotification(characteristicData, true);
}
}
if (dialog.isShowing()){
mHandler.post(new Runnable() {
@Override
public void run() {
dialog.hide();
}
});
}
}
ahora puede verificar que su valor característico sea el cambio, por ejemplo, en la función CharacterSheetRead (esto también funciona en la funciónCharacteristicChanged):
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
byte[] value=characteristic.getValue();
String v = new String(value);
Log.i("onCharacteristicRead", "Value: " + v);
}
Parece que olvidó escribir el Descriptor que le dice a su dispositivo BLE que vaya en este modo. Consulte las líneas de código que tratan sobre el descriptor en http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification
Sin configurar este descriptor, nunca recibirá actualizaciones de una característica. Llamar a setCharacteristicNotification
no es suficiente. Este es un error común.
código cortado
protected static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public boolean setCharacteristicNotification(BluetoothDevice device, UUID serviceUuid, UUID characteristicUuid,
boolean enable) {
if (IS_DEBUG)
Log.d(TAG, "setCharacteristicNotification(device=" + device.getName() + device.getAddress() + ", UUID="
+ characteristicUuid + ", enable=" + enable + " )");
BluetoothGatt gatt = mGattInstances.get(device.getAddress()); //I just hold the gatt instances I got from connect in this HashMap
BluetoothGattCharacteristic characteristic = gatt.getService(serviceUuid).getCharacteristic(characteristicUuid);
gatt.setCharacteristicNotification(characteristic, enable);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[] { 0x00, 0x00 });
return gatt.writeDescriptor(descriptor); //descriptor write operation successfully started?
}
Problemas experimentados en versiones anteriores de notificaciones de recepción de Android (una indicación que se registró) y siempre tuvo un extraño evento de desconexión después. Resultó que fue porque nos registramos para recibir notificaciones sobre cinco características.
El error descubierto en LogCat fue:
02-05 16:14:24.990 1271-1601/? E/bt-btif﹕ Max Notification Reached, registration failed.
¡Antes de 4.4.2, el número de registros fue limitado a 4! 4.4.2 aumentó este límite a 7.
Al reducir el número de registros en versiones anteriores, pudimos evitar esta limitación.
Supongo (no proporcionó su código fuente) que no lo implementó como Google quería :
(1)
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
y entonces
(2)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
Supongo que 2 falta. En ese caso, creo que se activará la notificación de bajo nivel, pero nunca se informará a la capa de aplicación.
También he tenido problemas con las notificaciones de BLE en Android. Sin embargo, hay una demostración completamente operativa que incluye un contenedor bluetooth alrededor de BluetoothAdapter
. El contenedor se llama BleWrapper
y se envía con la aplicación de demostración llamada BLEDemo contenida en el paquete Application Accelerator . Descargue aquí: https://developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx . Debe registrarse con su dirección de correo electrónico en la parte superior derecha antes de descargar. La licencia del proyecto permite el uso gratuito, la modificación del código y la publicación.
Para mi experiencia, la aplicación de demostración de Android maneja muy bien las suscripciones de notificación BLE. Todavía no he buceado demasiado en el código para ver cómo se ajusta el envoltorio.
Hay una aplicación de Android disponible en Play Store que es una personalización de la demo del acelerador de aplicaciones . Como la interfaz de usuario se ve casi igual, supongo que también usa BleWrapper
. Descargue la aplicación aquí: https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner
Tenía otra razón que me gustaría agregar, ya que me volvía loco todo el día:
En mi Samsung Note 3 no recibí notificaciones de valores modificados mientras el mismo código funcionaba en cualquier otro dispositivo con el que probé.
Reiniciar el dispositivo resolvió todos los problemas. Obvio, pero cuando estás en el problema, te olvidas de pensar en.