[bluetooth] Changed characteristic read/write to use CompletableFutures (#8970)

Signed-off-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
This commit is contained in:
Connor Petty
2021-04-09 13:23:28 -07:00
committed by GitHub
parent 1822f77b07
commit 89d735bb0f
24 changed files with 1207 additions and 1562 deletions

View File

@@ -15,10 +15,10 @@ package org.openhab.binding.bluetooth.bluez.internal;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.bluez.exceptions.BluezFailedException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.freedesktop.dbus.errors.NoReply;
@@ -28,7 +28,6 @@ import org.freedesktop.dbus.types.UInt16;
import org.openhab.binding.bluetooth.BaseBluetoothDevice;
import org.openhab.binding.bluetooth.BluetoothAddress;
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
import org.openhab.binding.bluetooth.BluetoothDescriptor;
import org.openhab.binding.bluetooth.BluetoothService;
import org.openhab.binding.bluetooth.bluez.internal.events.BlueZEvent;
@@ -42,6 +41,8 @@ import org.openhab.binding.bluetooth.bluez.internal.events.ServicesResolvedEvent
import org.openhab.binding.bluetooth.bluez.internal.events.TXPowerEvent;
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
import org.openhab.binding.bluetooth.util.RetryException;
import org.openhab.binding.bluetooth.util.RetryFuture;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
@@ -257,57 +258,65 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
}
@Override
public boolean enableNotifications(BluetoothCharacteristic characteristic) {
ensureConnected();
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
if (c != null) {
try {
c.startNotify();
} catch (DBusException e) {
if (e.getMessage().contains("Already notifying")) {
return false;
} else if (e.getMessage().contains("In Progress")) {
// let's retry in 10 seconds
scheduler.schedule(() -> enableNotifications(characteristic), 10, TimeUnit.SECONDS);
} else {
logger.warn("Exception occurred while activating notifications on '{}'", address, e);
}
}
return true;
} else {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return false;
public CompletableFuture<@Nullable Void> enableNotifications(BluetoothCharacteristic characteristic) {
BluetoothDevice dev = device;
if (dev == null || !dev.isConnected()) {
return CompletableFuture
.failedFuture(new IllegalStateException("DBusBlueZ device is not set or not connected"));
}
}
@Override
public boolean writeCharacteristic(BluetoothCharacteristic characteristic) {
logger.debug("writeCharacteristic()");
ensureConnected();
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
if (c == null) {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return false;
return CompletableFuture.failedFuture(
new IllegalStateException("Characteristic " + characteristic.getUuid() + " is missing on device"));
}
scheduler.submit(() -> {
return RetryFuture.callWithRetry(() -> {
try {
c.writeValue(characteristic.getByteValue(), null);
notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic,
BluetoothCompletionStatus.SUCCESS);
c.startNotify();
} catch (DBusException e) {
if (e.getMessage().contains("Already notifying")) {
return null;
} else if (e.getMessage().contains("In Progress")) {
// let's retry in half a second
throw new RetryException(500, TimeUnit.MILLISECONDS);
} else {
logger.warn("Exception occurred while activating notifications on '{}'", address, e);
throw e;
}
}
return null;
}, scheduler);
}
@Override
public CompletableFuture<@Nullable Void> writeCharacteristic(BluetoothCharacteristic characteristic, byte[] value) {
logger.debug("writeCharacteristic()");
BluetoothDevice dev = device;
if (dev == null || !dev.isConnected()) {
return CompletableFuture
.failedFuture(new IllegalStateException("DBusBlueZ device is not set or not connected"));
}
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
if (c == null) {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return CompletableFuture.failedFuture(
new IllegalStateException("Characteristic " + characteristic.getUuid() + " is missing on device"));
}
return RetryFuture.callWithRetry(() -> {
try {
c.writeValue(value, null);
return null;
} catch (DBusException e) {
logger.debug("Exception occurred when trying to write characteristic '{}': {}",
characteristic.getUuid(), e.getMessage());
notifyListeners(BluetoothEventType.CHARACTERISTIC_WRITE_COMPLETE, characteristic,
BluetoothCompletionStatus.ERROR);
throw e;
}
});
return true;
}, scheduler);
}
@Override
@@ -364,10 +373,7 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
}
BluetoothCharacteristic c = getCharacteristic(UUID.fromString(characteristic.getUuid()));
if (c != null) {
synchronized (c) {
c.setValue(event.getData());
notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, c, BluetoothCompletionStatus.SUCCESS);
}
notifyListeners(BluetoothEventType.CHARACTERISTIC_UPDATED, c, event.getData());
}
}
@@ -453,28 +459,30 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
}
@Override
public boolean readCharacteristic(BluetoothCharacteristic characteristic) {
public CompletableFuture<byte[]> readCharacteristic(BluetoothCharacteristic characteristic) {
BluetoothDevice dev = device;
if (dev == null || !dev.isConnected()) {
return CompletableFuture
.failedFuture(new IllegalStateException("DBusBlueZ device is not set or not connected"));
}
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
if (c == null) {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return false;
return CompletableFuture.failedFuture(
new IllegalStateException("Characteristic " + characteristic.getUuid() + " is missing on device"));
}
scheduler.submit(() -> {
return RetryFuture.callWithRetry(() -> {
try {
byte[] value = c.readValue(null);
characteristic.setValue(value);
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
BluetoothCompletionStatus.SUCCESS);
return c.readValue(null);
} catch (DBusException | DBusExecutionException e) {
// DBusExecutionException is thrown if the value cannot be read
logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(),
e.getMessage());
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
BluetoothCompletionStatus.ERROR);
throw e;
}
});
return true;
}, scheduler);
}
@Override
@@ -490,24 +498,35 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
}
@Override
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
public CompletableFuture<@Nullable Void> disableNotifications(BluetoothCharacteristic characteristic) {
BluetoothDevice dev = device;
if (dev == null || !dev.isConnected()) {
return CompletableFuture
.failedFuture(new IllegalStateException("DBusBlueZ device is not set or not connected"));
}
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
if (c != null) {
if (c == null) {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return CompletableFuture.failedFuture(
new IllegalStateException("Characteristic " + characteristic.getUuid() + " is missing on device"));
}
return RetryFuture.callWithRetry(() -> {
try {
c.stopNotify();
} catch (BluezFailedException e) {
if (e.getMessage().contains("In Progress")) {
// let's retry in 10 seconds
scheduler.schedule(() -> disableNotifications(characteristic), 10, TimeUnit.SECONDS);
} catch (DBusException e) {
if (e.getMessage().contains("Already notifying")) {
return null;
} else if (e.getMessage().contains("In Progress")) {
// let's retry in half a second
throw new RetryException(500, TimeUnit.MILLISECONDS);
} else {
logger.warn("Exception occurred while activating notifications on '{}'", address, e);
logger.warn("Exception occurred while deactivating notifications on '{}'", address, e);
throw e;
}
}
return true;
} else {
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
return false;
}
return null;
}, scheduler);
}
@Override