テストに使用したデバイス:Nexus 4、Android 4.3
接続は正常に機能していますが、コールバックのonCharacteristicChanged
Methodが呼び出されることはありません。ただし、onServicesDiscovered
内のsetCharacteristicNotification(char, true)
を使用して通知を登録していますが、その関数はtrueを返します。
デバイスログ(実際にはno通知があったときにメッセージがありますshouldが表示されます/ 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
GATT通知はiOSを使用して正常に機能し、アプリは基本的にAndroid(通知の登録など)と同じように機能します。
他の誰かが可能な解決策でこれを経験しましたか?
BLEデバイスにこのモードを使用するよう指示する記述子を記述するのを忘れたようです。 http://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#notification で記述子を処理するコード行を参照してください
この記述子を設定しないと、特性の更新を受け取ることはありません。 setCharacteristicNotification
を呼び出すだけでは不十分です。これはよくある間違いです。
コードを切り取った
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?
}
@ Boni2k-私は同じ問題を抱えています。私の場合、3つの通知特性と、いくつかの読み取り/書き込み特性があります。
私が見つけたのは、writeGattDescriptor
とreadCharacteristic
の間に依存関係があるということです。 すべて writeGattDescriptors 必須最初に来るおよび完了してからreadCharacteristic呼び出しを発行します。
Queues
を使用した私のソリューションです。今、私は通知を受け取っており、他のすべてが正常に動作します:
次のような2つのキューを作成します。
private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();
次に、このメソッドを使用して、検出後すぐにすべての記述子を記述します。
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);
}
}
そしてこのコールバック:
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());
};
通常、特性を読み取る方法は次のようになります。
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);
}
と私の読み取りコールバック:
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());
}
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
を置く代わりに値を記述子に設定するときは、descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
を置きます。 onCharacteristicChangedのコールバックが呼び出されるようになりました。
Androidの以前のバージョンで通知(登録された通知)を受信し、その後常に奇妙な切断イベントが発生するという問題が発生しました。結局のところ、これは5つの特性に関する通知に登録したためです。
LogCatで検出されたエラーは次のとおりです。
02-05 16:14:24.990 1271-1601/? E/bt-btif﹕ Max Notification Reached, registration failed.
4.4.2より前は、登録数の上限は4でした! 4.4.2はこの制限を7に増やしました。
以前のバージョンの登録数を減らすことで、この制限を回避することができました。
私はあなたがソースコードを提供しなかったので、あなたがそれを Googleが欲しかった として実装しなかったと仮定します。
(1)
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
その後
(2)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
2が欠落していると思います。その場合、低レベルの通知がトリガーされると思いますが、アプリケーション層に報告されることはありません。
まあ、このAPI名は、もし彼/彼女がBluetoothバックグラウンドプログラマーでなければ、アプリ開発者を混乱させるでしょう。
Bluetoothコア仕様の観点から、コア仕様4.2 Vol 3、パートGセクション3.3.3.3「クライアント特性構成」から引用:
特性記述子の値はビットフィールドです。ビットが設定されると、そのアクションが有効になります。そうでない場合は使用されません。
およびセクション4.10
通知は、クライアント特性構成記述子を使用して構成できます(セクション3.3.3.3を参照)。
これは、クライアントがサーバーから通知(または応答を必要とする指示)を受信する場合、「通知」ビットを1に書き込む必要があることを明確に示しています(それ以外の場合は「指示」ビットも1)。
ただし、「setCharacteristicNotification」という名前は、このAPIのパラメーターをTUREとして設定すると、クライアントが通知を受け取るというヒントを示しています。残念ながら、このAPIはローカルビットのみを設定して、リモート通知が来た場合にアプリに送信される通知を許可します。 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;
}'
重要なのは、記述子の書き込みアクションです。
これは私のために働いています:
いくつかの特性が変更されたことをマスターデバイスに通知するには、周辺機器でこの関数を呼び出します。
private BluetoothGattServer server;
//init....
//on BluetoothGattServerCallback...
//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);
マスターデバイスで:サービスを検出した後、setCharacteristicNotificationを有効にします。
@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();
}
});
}
}
onCharacteristicRead関数など、特性値が変更されていることを確認できるようになりました(これはonCharacteristicChanged関数でも機能します)。
@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);
}
これを行う簡単な方法を次に示しますが、欠点があれば教えてください。
ステップ1ブール変数の宣言
private boolean char_1_subscribed = false;
private boolean char_2_subscribed = false;
private boolean char_3_subscribed = false;
ステップ2 onServicesDiscoveredコールバックの最初の特性にサブスクライブします。
@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;
}
ステップ
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;
}
}
一日中夢中になったので、追加したい別の理由がありました。
Samsung Note 3では、テストした他のデバイスで同じコードが機能している間、値が変更されたという通知を受け取りませんでした。
デバイスを再起動すると、すべての問題が解決しました。明らかですが、問題に直面しているときは、考えることを忘れます。