[bluetooth.generic] Enable BLE notification for linked channels (#10122)
* [bluetooth] Add BluetoothDevice.isNotifying() * [bluetooth] Improve Characteristic properties support * [bluez] Improve Characteristic properties support * [bluetooth] Add BluetoothDevice.canNotify() * [bluez] Also catch DBusExecutionException on read value * [bluetooth.generic] Activate notifications for linked channels where characteristics are able to notify * [bluez] Adjust javadoc * [bluegiga] Add BluetoothDevice.isNotifying() support * [bluegiga] Fix notification enabled check * [bluetooth] move canNotify() to Characteristic * [bluegiga] rename notificationEnabled to notifying * [bluetooth.generic] use handlerToChannels to subscribe to notifications * [bluetooth.generic] implement TODOs of canRead()/canWrite() * [bluetooth.generic] optimize ChannelUID * [bluetooth.generic] use channelUids for link check Signed-off-by: Peter Rosenberg <prosenb.dev@gmail.com>
This commit is contained in:
parent
596b261d47
commit
7abeb97396
|
@ -27,7 +27,7 @@ import org.openhab.binding.bluetooth.BluetoothCharacteristic;
|
||||||
*/
|
*/
|
||||||
public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic {
|
public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic {
|
||||||
|
|
||||||
private boolean notificationEnabled;
|
private boolean notifying;
|
||||||
|
|
||||||
public BlueGigaBluetoothCharacteristic(int handle) {
|
public BlueGigaBluetoothCharacteristic(int handle) {
|
||||||
super(null, handle);
|
super(null, handle);
|
||||||
|
@ -45,11 +45,11 @@ public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNotificationEnabled() {
|
public boolean isNotifying() {
|
||||||
return notificationEnabled;
|
return notifying;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNotificationEnabled(boolean enable) {
|
public void setNotifying(boolean enable) {
|
||||||
this.notificationEnabled = enable;
|
this.notifying = enable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
}
|
}
|
||||||
|
|
||||||
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
||||||
if (ch.isNotificationEnabled()) {
|
if (ch.isNotifying()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,12 +241,12 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
@Override
|
@Override
|
||||||
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
if (connection == -1) {
|
if (connection == -1) {
|
||||||
logger.debug("Cannot enable notifications, device not connected {}", this);
|
logger.debug("Cannot disable notifications, device not connected {}", this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
||||||
if (ch.isNotificationEnabled()) {
|
if (!ch.isNotifying()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +288,12 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNotifying(BluetoothCharacteristic characteristic) {
|
||||||
|
BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic;
|
||||||
|
return ch.isNotifying();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean enableNotifications(BluetoothDescriptor descriptor) {
|
public boolean enableNotifications(BluetoothDescriptor descriptor) {
|
||||||
// TODO will be implemented in a followup PR
|
// TODO will be implemented in a followup PR
|
||||||
|
@ -613,7 +619,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger.debug("write to descriptor failed");
|
logger.debug("write to descriptor failed");
|
||||||
}
|
}
|
||||||
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(success);
|
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(success);
|
||||||
procedureProgress = BlueGigaProcedure.NONE;
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
procedureCharacteristic = null;
|
procedureCharacteristic = null;
|
||||||
break;
|
break;
|
||||||
|
@ -622,7 +628,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger.debug("write to descriptor failed");
|
logger.debug("write to descriptor failed");
|
||||||
}
|
}
|
||||||
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(!success);
|
((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(!success);
|
||||||
procedureProgress = BlueGigaProcedure.NONE;
|
procedureProgress = BlueGigaProcedure.NONE;
|
||||||
procedureCharacteristic = null;
|
procedureCharacteristic = null;
|
||||||
break;
|
break;
|
||||||
|
@ -656,7 +662,7 @@ public class BlueGigaBluetoothDevice extends BaseBluetoothDevice implements Blue
|
||||||
}
|
}
|
||||||
|
|
||||||
for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) {
|
for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) {
|
||||||
ch.setNotificationEnabled(false);
|
ch.setNotifying(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelTimer(procedureTimer);
|
cancelTimer(procedureTimer);
|
||||||
|
|
|
@ -57,6 +57,7 @@ import com.github.hypfvieh.bluetooth.wrapper.BluetoothGattService;
|
||||||
*
|
*
|
||||||
* @author Kai Kreuzer - Initial contribution and API
|
* @author Kai Kreuzer - Initial contribution and API
|
||||||
* @author Benjamin Lafois - Replaced tinyB with bluezDbus
|
* @author Benjamin Lafois - Replaced tinyB with bluezDbus
|
||||||
|
* @author Peter Rosenberg - Improve notifications and properties support
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@ -399,6 +400,7 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
|
||||||
for (BluetoothGattCharacteristic dBusBlueZCharacteristic : dBusBlueZService.getGattCharacteristics()) {
|
for (BluetoothGattCharacteristic dBusBlueZCharacteristic : dBusBlueZService.getGattCharacteristics()) {
|
||||||
BluetoothCharacteristic characteristic = new BluetoothCharacteristic(
|
BluetoothCharacteristic characteristic = new BluetoothCharacteristic(
|
||||||
UUID.fromString(dBusBlueZCharacteristic.getUuid()), 0);
|
UUID.fromString(dBusBlueZCharacteristic.getUuid()), 0);
|
||||||
|
convertCharacteristicProperties(dBusBlueZCharacteristic, characteristic);
|
||||||
|
|
||||||
for (BluetoothGattDescriptor dBusBlueZDescriptor : dBusBlueZCharacteristic.getGattDescriptors()) {
|
for (BluetoothGattDescriptor dBusBlueZDescriptor : dBusBlueZCharacteristic.getGattDescriptors()) {
|
||||||
BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic,
|
BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic,
|
||||||
|
@ -414,6 +416,42 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the flags of BluetoothGattCharacteristic to the int bitset used by BluetoothCharacteristic.
|
||||||
|
*
|
||||||
|
* @param dBusBlueZCharacteristic source characteristic to read the flags from
|
||||||
|
* @param characteristic destination characteristic to write to properties to
|
||||||
|
*/
|
||||||
|
private void convertCharacteristicProperties(BluetoothGattCharacteristic dBusBlueZCharacteristic,
|
||||||
|
BluetoothCharacteristic characteristic) {
|
||||||
|
int properties = 0;
|
||||||
|
|
||||||
|
for (String property : dBusBlueZCharacteristic.getFlags()) {
|
||||||
|
switch (property) {
|
||||||
|
case "broadcast":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_BROADCAST;
|
||||||
|
break;
|
||||||
|
case "read":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_READ;
|
||||||
|
break;
|
||||||
|
case "write-without-response":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
|
||||||
|
break;
|
||||||
|
case "write":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_WRITE;
|
||||||
|
break;
|
||||||
|
case "notify":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_NOTIFY;
|
||||||
|
break;
|
||||||
|
case "indicate":
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_INDICATE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
characteristic.setProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readCharacteristic(BluetoothCharacteristic characteristic) {
|
public boolean readCharacteristic(BluetoothCharacteristic characteristic) {
|
||||||
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
|
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
|
||||||
|
@ -428,7 +466,8 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
|
||||||
characteristic.setValue(value);
|
characteristic.setValue(value);
|
||||||
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
||||||
BluetoothCompletionStatus.SUCCESS);
|
BluetoothCompletionStatus.SUCCESS);
|
||||||
} catch (DBusException e) {
|
} catch (DBusException | DBusExecutionException e) {
|
||||||
|
// DBusExecutionException is thrown if the value cannot be read
|
||||||
logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(),
|
logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(),
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic,
|
||||||
|
@ -438,6 +477,18 @@ public class BlueZBluetoothDevice extends BaseBluetoothDevice implements BlueZEv
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNotifying(BluetoothCharacteristic characteristic) {
|
||||||
|
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
|
||||||
|
if (c != null) {
|
||||||
|
Boolean isNotifying = c.isNotifying();
|
||||||
|
return Objects.requireNonNullElse(isNotifying, false);
|
||||||
|
} else {
|
||||||
|
logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
|
BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString());
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.sputnikdev.bluetooth.gattparser.spec.Field;
|
||||||
* channels based off of a bluetooth device's GATT characteristics.
|
* channels based off of a bluetooth device's GATT characteristics.
|
||||||
*
|
*
|
||||||
* @author Connor Petty - Initial contribution
|
* @author Connor Petty - Initial contribution
|
||||||
|
* @author Peter Rosenberg - Use notifications
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@ -68,6 +69,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
private final Map<ChannelUID, CharacteristicHandler> channelHandlers = new ConcurrentHashMap<>();
|
private final Map<ChannelUID, CharacteristicHandler> channelHandlers = new ConcurrentHashMap<>();
|
||||||
private final BluetoothGattParser gattParser = BluetoothGattParserFactory.getDefault();
|
private final BluetoothGattParser gattParser = BluetoothGattParserFactory.getDefault();
|
||||||
private final CharacteristicChannelTypeProvider channelTypeProvider;
|
private final CharacteristicChannelTypeProvider channelTypeProvider;
|
||||||
|
private final Map<CharacteristicHandler, List<ChannelUID>> handlerToChannels = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> readCharacteristicJob = null;
|
private @Nullable ScheduledFuture<?> readCharacteristicJob = null;
|
||||||
|
|
||||||
|
@ -84,12 +86,14 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
readCharacteristicJob = scheduler.scheduleWithFixedDelay(() -> {
|
readCharacteristicJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
if (device.getConnectionState() == ConnectionState.CONNECTED) {
|
if (device.getConnectionState() == ConnectionState.CONNECTED) {
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
for (CharacteristicHandler charHandler : charHandlers.values()) {
|
handlerToChannels.forEach((charHandler, channelUids) -> {
|
||||||
if (charHandler.canRead()) {
|
// Only read the value manually if notification is not on.
|
||||||
|
// Also read it the first time before we activate notifications below.
|
||||||
|
if (!device.isNotifying(charHandler.characteristic) && charHandler.canRead()) {
|
||||||
device.readCharacteristic(charHandler.characteristic);
|
device.readCharacteristic(charHandler.characteristic);
|
||||||
try {
|
try {
|
||||||
// TODO the ideal solution would be to use locks/conditions and timeouts
|
// TODO the ideal solution would be to use locks/conditions and timeouts
|
||||||
// between this code and `onCharacteristicReadComplete` but
|
// Kbetween this code and `onCharacteristicReadComplete` but
|
||||||
// that would overcomplicate the code a bit and I plan
|
// that would overcomplicate the code a bit and I plan
|
||||||
// on implementing a better more generalized solution later
|
// on implementing a better more generalized solution later
|
||||||
Thread.sleep(50);
|
Thread.sleep(50);
|
||||||
|
@ -97,7 +101,20 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (charHandler.characteristic.canNotify()) {
|
||||||
|
// Enabled/Disable notifications dependent on if the channel is linked.
|
||||||
|
// TODO check why isLinked() is true for not linked channels
|
||||||
|
if (channelUids.stream().anyMatch(this::isLinked)) {
|
||||||
|
if (!device.isNotifying(charHandler.characteristic)) {
|
||||||
|
device.enableNotifications(charHandler.characteristic);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (device.isNotifying(charHandler.characteristic)) {
|
||||||
|
device.disableNotifications(charHandler.characteristic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// if we are connected and still haven't been able to resolve the services, try disconnecting and
|
// if we are connected and still haven't been able to resolve the services, try disconnecting and
|
||||||
// then connecting again
|
// then connecting again
|
||||||
|
@ -117,6 +134,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
|
|
||||||
charHandlers.clear();
|
charHandlers.clear();
|
||||||
channelHandlers.clear();
|
channelHandlers.clear();
|
||||||
|
handlerToChannels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -161,9 +179,11 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
logger.trace("{} processing characteristic {}", address, characteristic.getUuid());
|
logger.trace("{} processing characteristic {}", address, characteristic.getUuid());
|
||||||
CharacteristicHandler handler = getCharacteristicHandler(characteristic);
|
CharacteristicHandler handler = getCharacteristicHandler(characteristic);
|
||||||
List<Channel> chans = handler.buildChannels();
|
List<Channel> chans = handler.buildChannels();
|
||||||
for (Channel channel : chans) {
|
List<ChannelUID> chanUids = chans.stream().map(Channel::getUID).collect(Collectors.toList());
|
||||||
channelHandlers.put(channel.getUID(), handler);
|
for (ChannelUID channel : chanUids) {
|
||||||
|
channelHandlers.put(channel, handler);
|
||||||
}
|
}
|
||||||
|
handlerToChannels.put(handler, chanUids);
|
||||||
return chans.stream();
|
return chans.stream();
|
||||||
})//
|
})//
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -341,8 +361,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
if (gattParser.isKnownCharacteristic(charUUID)) {
|
if (gattParser.isKnownCharacteristic(charUUID)) {
|
||||||
return gattParser.isValidForRead(charUUID);
|
return gattParser.isValidForRead(charUUID);
|
||||||
}
|
}
|
||||||
// TODO: need to evaluate this from characteristic properties, but such properties aren't support yet
|
return characteristic.canRead();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canWrite() {
|
public boolean canWrite() {
|
||||||
|
@ -350,8 +369,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler {
|
||||||
if (gattParser.isKnownCharacteristic(charUUID)) {
|
if (gattParser.isKnownCharacteristic(charUUID)) {
|
||||||
return gattParser.isValidForWrite(charUUID);
|
return gattParser.isValidForWrite(charUUID);
|
||||||
}
|
}
|
||||||
// TODO: need to evaluate this from characteristic properties, but such properties aren't support yet
|
return characteristic.canWrite();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAdvanced() {
|
private boolean isAdvanced() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* @author Chris Jackson - Initial contribution
|
* @author Chris Jackson - Initial contribution
|
||||||
* @author Kai Kreuzer - Cleaned up code
|
* @author Kai Kreuzer - Cleaned up code
|
||||||
|
* @author Peter Rosenberg - Improve properties support
|
||||||
*/
|
*/
|
||||||
public class BluetoothCharacteristic {
|
public class BluetoothCharacteristic {
|
||||||
public static final int FORMAT_UINT8 = 0x11;
|
public static final int FORMAT_UINT8 = 0x11;
|
||||||
|
@ -142,6 +143,16 @@ public class BluetoothCharacteristic {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the raw properties. The individual properties are represented as bits inside
|
||||||
|
* of this int value.
|
||||||
|
*
|
||||||
|
* @param properties of this Characteristic
|
||||||
|
*/
|
||||||
|
public void setProperties(int properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the properties of this characteristic.
|
* Returns the properties of this characteristic.
|
||||||
*
|
*
|
||||||
|
@ -152,6 +163,46 @@ public class BluetoothCharacteristic {
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the given characteristics property is enabled or not.
|
||||||
|
*
|
||||||
|
* @param property one of the Constants BluetoothCharacteristic.PROPERTY_XX
|
||||||
|
* @return true if this characteristic has the given property enabled, false if properties not set or
|
||||||
|
* the given property is not enabled.
|
||||||
|
*/
|
||||||
|
public boolean hasPropertyEnabled(int property) {
|
||||||
|
return (properties & property) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if notifications can be enabled on this characteristic.
|
||||||
|
*
|
||||||
|
* @return true if notifications can be enabled, false if notifications are not supported, characteristic is missing
|
||||||
|
* on device or notifications are not supported.
|
||||||
|
*/
|
||||||
|
public boolean canNotify() {
|
||||||
|
return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the value can be read on this characteristic.
|
||||||
|
*
|
||||||
|
* @return true if the value can be read, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean canRead() {
|
||||||
|
return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the value can be written on this characteristic.
|
||||||
|
*
|
||||||
|
* @return true if the value can be written with of without a response, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean canWrite() {
|
||||||
|
return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE)
|
||||||
|
|| hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the permissions for this characteristic.
|
* Returns the permissions for this characteristic.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Chris Jackson - Initial contribution
|
* @author Chris Jackson - Initial contribution
|
||||||
* @author Kai Kreuzer - Refactored class to use Integer instead of int, fixed bugs, diverse improvements
|
* @author Kai Kreuzer - Refactored class to use Integer instead of int, fixed bugs, diverse improvements
|
||||||
* @author Connor Petty - Made most of the methods abstract
|
* @author Connor Petty - Made most of the methods abstract
|
||||||
|
* @author Peter Rosenberg - Improve notifications
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class BluetoothDevice {
|
public abstract class BluetoothDevice {
|
||||||
|
@ -249,6 +250,15 @@ public abstract class BluetoothDevice {
|
||||||
*/
|
*/
|
||||||
public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic);
|
public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if notification is enabled for the given characteristic.
|
||||||
|
*
|
||||||
|
* @param characteristic the {@link BluetoothCharacteristic} to check if notifications are enabled.
|
||||||
|
* @return true if notification is enabled, false if notification is disabled, characteristic is missing on device
|
||||||
|
* or notifications are not supported.
|
||||||
|
*/
|
||||||
|
public abstract boolean isNotifying(BluetoothCharacteristic characteristic);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables notifications for a characteristic. Only a single read or write operation can be requested at once.
|
* Enables notifications for a characteristic. Only a single read or write operation can be requested at once.
|
||||||
* Attempting to perform an operation when one is already in progress will result in subsequent calls returning
|
* Attempting to perform an operation when one is already in progress will result in subsequent calls returning
|
||||||
|
|
|
@ -112,6 +112,12 @@ public abstract class DelegateBluetoothDevice extends BluetoothDevice {
|
||||||
return delegate != null && delegate.writeCharacteristic(characteristic);
|
return delegate != null && delegate.writeCharacteristic(characteristic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNotifying(BluetoothCharacteristic characteristic) {
|
||||||
|
BluetoothDevice delegate = getDelegate();
|
||||||
|
return delegate != null ? delegate.isNotifying(characteristic) : false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean enableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean enableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
BluetoothDevice delegate = getDelegate();
|
BluetoothDevice delegate = getDelegate();
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.bluetooth;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link BluetoothCharacteristic}.
|
||||||
|
*
|
||||||
|
* @author Peter Rosenberg - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CharacteristicPropertiesTest {
|
||||||
|
private BluetoothCharacteristic characteristic = new BluetoothCharacteristic(UUID.randomUUID(), 0);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllSupportedProperties() {
|
||||||
|
// given
|
||||||
|
// when
|
||||||
|
int properties = 0;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_BROADCAST;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_READ;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_WRITE;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_NOTIFY;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_INDICATE;
|
||||||
|
characteristic.setProperties(properties);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST), "Broastcast not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE),
|
||||||
|
"Write not response not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE),
|
||||||
|
"Signed write set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS),
|
||||||
|
"Extended props set");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoProperties() {
|
||||||
|
// given
|
||||||
|
// when
|
||||||
|
int properties = 0;
|
||||||
|
characteristic.setProperties(properties);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST),
|
||||||
|
"Broastcast not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE),
|
||||||
|
"Write not response not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE),
|
||||||
|
"Signed write set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS),
|
||||||
|
"Extended props set");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSomeSupportedProperties() {
|
||||||
|
// given
|
||||||
|
// when
|
||||||
|
int properties = 0;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_READ;
|
||||||
|
properties |= BluetoothCharacteristic.PROPERTY_NOTIFY;
|
||||||
|
characteristic.setProperties(properties);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST),
|
||||||
|
"Broastcast not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE),
|
||||||
|
"Write not response not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set");
|
||||||
|
assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE),
|
||||||
|
"Signed write set");
|
||||||
|
assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS),
|
||||||
|
"Extended props set");
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,6 +102,11 @@ public class MockBluetoothDevice extends BaseBluetoothDevice {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNotifying(BluetoothCharacteristic characteristic) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
public boolean disableNotifications(BluetoothCharacteristic characteristic) {
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in New Issue