diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java index afa8ba4843b83..43206c9b1c37f 100644 --- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java +++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java @@ -27,7 +27,7 @@ */ public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic { - private boolean notificationEnabled; + private boolean notifying; public BlueGigaBluetoothCharacteristic(int handle) { super(null, handle); @@ -45,11 +45,11 @@ public void setUUID(UUID uuid) { this.uuid = uuid; } - public boolean isNotificationEnabled() { - return notificationEnabled; + public boolean isNotifying() { + return notifying; } - public void setNotificationEnabled(boolean enable) { - this.notificationEnabled = enable; + public void setNotifying(boolean enable) { + this.notifying = enable; } } diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java index c2cb969e8fe4e..ccff259a0b878 100644 --- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java @@ -196,7 +196,7 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { } BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; - if (ch.isNotificationEnabled()) { + if (ch.isNotifying()) { return true; } @@ -241,12 +241,12 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { @Override public boolean disableNotifications(BluetoothCharacteristic characteristic) { if (connection == -1) { - logger.debug("Cannot enable notifications, device not connected {}", this); + logger.debug("Cannot disable notifications, device not connected {}", this); return false; } BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; - if (ch.isNotificationEnabled()) { + if (!ch.isNotifying()) { return true; } @@ -288,6 +288,12 @@ public boolean disableNotifications(BluetoothCharacteristic characteristic) { return true; } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; + return ch.isNotifying(); + } + @Override public boolean enableNotifications(BluetoothDescriptor descriptor) { // TODO will be implemented in a followup PR @@ -613,7 +619,7 @@ private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event if (!success) { logger.debug("write to descriptor failed"); } - ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(success); + ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(success); procedureProgress = BlueGigaProcedure.NONE; procedureCharacteristic = null; break; @@ -622,7 +628,7 @@ private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event if (!success) { logger.debug("write to descriptor failed"); } - ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(!success); + ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(!success); procedureProgress = BlueGigaProcedure.NONE; procedureCharacteristic = null; break; @@ -656,7 +662,7 @@ private void handleDisconnectedEvent(BlueGigaDisconnectedEvent event) { } for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) { - ch.setNotificationEnabled(false); + ch.setNotifying(false); } cancelTimer(procedureTimer); diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java index f4d78ab591e64..509f21fa429a1 100644 --- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java @@ -57,6 +57,7 @@ * * @author Kai Kreuzer - Initial contribution and API * @author Benjamin Lafois - Replaced tinyB with bluezDbus + * @author Peter Rosenberg - Improve notifications and properties support * */ @NonNullByDefault @@ -399,6 +400,7 @@ public boolean discoverServices() { for (BluetoothGattCharacteristic dBusBlueZCharacteristic : dBusBlueZService.getGattCharacteristics()) { BluetoothCharacteristic characteristic = new BluetoothCharacteristic( UUID.fromString(dBusBlueZCharacteristic.getUuid()), 0); + convertCharacteristicProperties(dBusBlueZCharacteristic, characteristic); for (BluetoothGattDescriptor dBusBlueZDescriptor : dBusBlueZCharacteristic.getGattDescriptors()) { BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic, @@ -414,6 +416,42 @@ public boolean discoverServices() { 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 public boolean readCharacteristic(BluetoothCharacteristic characteristic) { BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString()); @@ -428,7 +466,8 @@ public boolean readCharacteristic(BluetoothCharacteristic characteristic) { characteristic.setValue(value); notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, 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(), e.getMessage()); notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, @@ -438,6 +477,18 @@ public boolean readCharacteristic(BluetoothCharacteristic characteristic) { 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 public boolean disableNotifications(BluetoothCharacteristic characteristic) { BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString()); diff --git a/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java b/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java index 5717b08210729..633170f4284e4 100644 --- a/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java +++ b/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java @@ -58,6 +58,7 @@ * channels based off of a bluetooth device's GATT characteristics. * * @author Connor Petty - Initial contribution + * @author Peter Rosenberg - Use notifications * */ @NonNullByDefault @@ -68,6 +69,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler { private final Map channelHandlers = new ConcurrentHashMap<>(); private final BluetoothGattParser gattParser = BluetoothGattParserFactory.getDefault(); private final CharacteristicChannelTypeProvider channelTypeProvider; + private final Map> handlerToChannels = new ConcurrentHashMap<>(); private @Nullable ScheduledFuture readCharacteristicJob = null; @@ -84,12 +86,14 @@ public void initialize() { readCharacteristicJob = scheduler.scheduleWithFixedDelay(() -> { if (device.getConnectionState() == ConnectionState.CONNECTED) { if (resolved) { - for (CharacteristicHandler charHandler : charHandlers.values()) { - if (charHandler.canRead()) { + handlerToChannels.forEach((charHandler, channelUids) -> { + // 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); try { // 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 // on implementing a better more generalized solution later Thread.sleep(50); @@ -97,7 +101,20 @@ public void initialize() { 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 { // if we are connected and still haven't been able to resolve the services, try disconnecting and // then connecting again @@ -117,6 +134,7 @@ public void dispose() { charHandlers.clear(); channelHandlers.clear(); + handlerToChannels.clear(); } @Override @@ -161,9 +179,11 @@ private void updateThingChannels() { logger.trace("{} processing characteristic {}", address, characteristic.getUuid()); CharacteristicHandler handler = getCharacteristicHandler(characteristic); List chans = handler.buildChannels(); - for (Channel channel : chans) { - channelHandlers.put(channel.getUID(), handler); + List chanUids = chans.stream().map(Channel::getUID).collect(Collectors.toList()); + for (ChannelUID channel : chanUids) { + channelHandlers.put(channel, handler); } + handlerToChannels.put(handler, chanUids); return chans.stream(); })// .collect(Collectors.toList()); @@ -341,8 +361,7 @@ public boolean canRead() { if (gattParser.isKnownCharacteristic(charUUID)) { return gattParser.isValidForRead(charUUID); } - // TODO: need to evaluate this from characteristic properties, but such properties aren't support yet - return true; + return characteristic.canRead(); } public boolean canWrite() { @@ -350,8 +369,7 @@ public boolean canWrite() { if (gattParser.isKnownCharacteristic(charUUID)) { return gattParser.isValidForWrite(charUUID); } - // TODO: need to evaluate this from characteristic properties, but such properties aren't support yet - return true; + return characteristic.canWrite(); } private boolean isAdvanced() { diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java index 0a014ecb36e93..dd591acc723f6 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java @@ -31,6 +31,7 @@ * * @author Chris Jackson - Initial contribution * @author Kai Kreuzer - Cleaned up code + * @author Peter Rosenberg - Improve properties support */ public class BluetoothCharacteristic { public static final int FORMAT_UINT8 = 0x11; @@ -142,6 +143,16 @@ public int getInstanceId() { 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. * @@ -152,6 +163,46 @@ public int getProperties() { 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. */ diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java index f9dfde0862b5d..ba1186829f12b 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java @@ -28,6 +28,7 @@ * @author Chris Jackson - Initial contribution * @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 Peter Rosenberg - Improve notifications */ @NonNullByDefault public abstract class BluetoothDevice { @@ -249,6 +250,15 @@ public BluetoothAdapter getAdapter() { */ 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. * Attempting to perform an operation when one is already in progress will result in subsequent calls returning diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java index bb4c37029bf1b..074d9a8f39e7e 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java @@ -112,6 +112,12 @@ public boolean writeCharacteristic(BluetoothCharacteristic characteristic) { return delegate != null && delegate.writeCharacteristic(characteristic); } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + BluetoothDevice delegate = getDelegate(); + return delegate != null ? delegate.isNotifying(characteristic) : false; + } + @Override public boolean enableNotifications(BluetoothCharacteristic characteristic) { BluetoothDevice delegate = getDelegate(); diff --git a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java new file mode 100644 index 0000000000000..3d40537f6d550 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java @@ -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"); + } +} diff --git a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java index d5170cd071df5..892362f86c1d2 100644 --- a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java @@ -102,6 +102,11 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { return false; } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + return false; + } + @Override public boolean disableNotifications(BluetoothCharacteristic characteristic) { return false;