diff options
author | Sven Gothel <[email protected]> | 2020-07-24 10:00:15 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-07-24 10:00:15 +0200 |
commit | 3437e34c97c1dad0c4c0d27680371f693aadac4f (patch) | |
tree | 5e2e5ad9dd3351ad5e332ed2159a0e6f3428a92e | |
parent | a1e74bf1e23f4833261556e55612f34afc8dbcf1 (diff) |
Reworking GATTCharacteristicListener (C++ and Java)
- Aligned all related C++ and Java API doc entries and made sure it matches implementation.
- Renaming SpecificGATTCharacteristicListener to AssociatedGATTCharacteristicListener,
as we refer to the associate GATTCharacteristic of a GATTCharacteristicListener
AssociatedGATTCharacteristicListener is a specialization knowing its
associated GATTCharacteristic to be used for match().
- Renamed 'configIndicationNotification(..)' to 'configNotificationIndication(..)',
matching order of arguments and the returned enabledState array.
- Exposed 'configNotificationIndication(..)' incl enabledState array to Java
- Clarified the 'add listener' and 'configNotificationIndication(..)' semantic in API doc
and implementation. Added new API entries to distinguish them.
- DBTGattCharacteristic.java skips adding its 'TinyB API compatibility' GATTCharacteristicListener
in case neither notify nor indicate property exist.
Also skip the native configNotificationIndication(..) in such case.
This reduces the overall listener load to GattHandler by factor 5!
- General: Add new method 'removeAllAssociatedCharacteristicListener(GATTCharacteristic)',
allowing removal of all GATTCharacteristic associated listener.
This is usefull to complete the GATTCharacteristic C++ dtor or Java close() operation.
- DBTDevice: Add GATTCharacteristicListener methods to align with Java API
and allow user not to deal with GATTHandler directly.
Convenience and validates the C++/Java API alignement.
- C++ JNICriticalArray: Added 2nd template typename for the java-array-type,
enabling it for other than jbyteArray. Here used for a jbooleanArray.
- The GATTCharacteristicListener Java to C++ native holding specialization JNICharacteristicListener
keeps a new global reference to the Java GATTCharacteristicListener
and the optional associated GATTCharacteristic.
This ensures the instances won't get garbage collected and hence ensures proper
object lifecycle even when passing 'throw away' listener object created just
for the add*Listener call.
- Removal and hence destruction of the listeners is always guaranteed at:
-- Device / GATTHandler disconnect
-- GattCharacteristic dtor or close
27 files changed, 697 insertions, 327 deletions
diff --git a/api/direct_bt/DBTDevice.hpp b/api/direct_bt/DBTDevice.hpp index 1ff1a2a4..4b8eb806 100644 --- a/api/direct_bt/DBTDevice.hpp +++ b/api/direct_bt/DBTDevice.hpp @@ -390,6 +390,44 @@ namespace direct_bt { * </p> */ void disconnectGATT(); + + /** + * Add the given {@link GATTCharacteristicListener} to the listener list if not already present. + * <p> + * Convenience delegation call to GATTHandler + * </p> + * @param listener A {@link GATTCharacteristicListener} instance, listening to all {@link BluetoothGattCharacteristic} events of this device + * @return true if the given listener is not element of the list and has been newly added, otherwise false. + * @throws IllegalStateException if the GATTHandler is null, i.e. not connected + */ + bool addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l); + + /** + * Remove the given {@link GATTCharacteristicListener} from the listener list. + * <p> + * If the GATTHandler is null, i.e. not connected, {@code false} is being returned. + * </p> + * @param listener A {@link GATTCharacteristicListener} instance + * @return true if the given listener is an element of the list and has been removed, otherwise false. + */ + bool removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l); + + /** + * Remove all {@link GATTCharacteristicListener} from the list, which are associated to the given {@link GATTCharacteristic}. + * <p> + * Implementation tests all listener's GATTCharacteristicListener::match(const GATTCharacteristic & characteristic) + * to match with the given associated characteristic. + * </p> + * @param associatedCharacteristic the match criteria to remove any GATTCharacteristicListener from the list + * @return number of removed listener. + */ + int removeAllAssociatedCharacteristicListener(std::shared_ptr<GATTCharacteristic> associatedCharacteristic); + + /** + * Remove all {@link GATTCharacteristicListener} from the list. + * @return number of removed listener. + */ + int removeAllCharacteristicListener(); }; inline bool operator<(const DBTDevice& lhs, const DBTDevice& rhs) diff --git a/api/direct_bt/GATTCharacteristic.hpp b/api/direct_bt/GATTCharacteristic.hpp index 39f132fc..7734c35f 100644 --- a/api/direct_bt/GATTCharacteristic.hpp +++ b/api/direct_bt/GATTCharacteristic.hpp @@ -173,25 +173,32 @@ namespace direct_bt { /** * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration * <p> - * Convenience delegation call to GATTHandler via DBTDevice + * Method enables notification and/or indication for this characteristic at BLE level. * </p> * <p> - * If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. + * Convenience delegation call to GATTHandler via DBTDevice * </p> * <p> * Implementation masks this Characteristic properties PropertyBitVal::Notify and PropertyBitVal::Indicate - * with the respective user request parameters, hence removes unsupported requests.<br> - * If the resulting combination for both requests is false, method returns false. - * </p> - * <p> - * Returns false if there is no GATTDescriptor of type ClientCharacteristicConfiguration, - * or if the operation has failed. - * </p> + * with the respective user request parameters, hence removes unsupported requests. + * </p> + * @param enableNotification + * @param enableIndication + * @param enabledState array of size 2, holding the resulting enabled state for notification and indication. + * @return false if this characteristic has no PropertyBitVal::Notify or PropertyBitVal::Indication present, + * or there is no GATTDescriptor of type ClientCharacteristicConfiguration, or if the operation has failed. + * Otherwise returns true. + * @throws IllegalStateException if notification or indication is set to be enabled + * and the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ - bool configIndicationNotification(const bool enableNotification, const bool enableIndication, bool enableResult[2]); + bool configNotificationIndication(const bool enableNotification, const bool enableIndication, bool enabledState[2]); /** - * Add the given listener to the list if not already present. + * Add the given GATTCharacteristicListener to the listener list if not already present. + * <p> + * Occurring notifications and indications, if enabled via {@link #configNotificationIndication(bool, bool, bool[])}}, + * will call the respective GATTCharacteristicListener callback method. + * </p> * <p> * Returns true if the given listener is not element of the list and has been newly added, * otherwise false. @@ -200,61 +207,88 @@ namespace direct_bt { * Convenience delegation call to GATTHandler via DBTDevice * </p> * <p> - * If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. - * </p> - * <p> * To restrict the listener to listen only to this GATTCharacteristic instance, * user has to implement GATTCharacteristicListener::match(GATTCharacteristicRef) accordingly. * <br> - * For this purpose, use may derive from SpecificGATTCharacteristicListener, + * For this purpose, use may derive from AssociatedGATTCharacteristicListener, * which provides these simple matching filter facilities. * </p> + * @throws IllegalStateException if the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ bool addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l); /** - * Remove the given listener from the list. + * Add the given GATTCharacteristicListener to the listener list if not already present + * and if enabling the notification and/or indication for this characteristic at BLE level was successful. * <p> - * Returns true if the given listener is an element of the list and has been removed, + * Occurring notifications and indications will call the respective {@link GATTCharacteristicListener} + * callback method. + * </p> + * <p> + * Returns true if enabling the notification and/or indication was successful + * and if the given listener is not element of the list and has been newly added, * otherwise false. * </p> * <p> * Convenience delegation call to GATTHandler via DBTDevice + * performing both, configNotificationIndication(..) and addCharacteristicListener(..). * </p> * <p> - * If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. + * To restrict the listener to listen only to this GATTCharacteristic instance, + * user has to implement GATTCharacteristicListener::match(GATTCharacteristicRef) accordingly. + * <br> + * For this purpose, use may derive from AssociatedGATTCharacteristicListener, + * which provides these simple matching filter facilities. * </p> + * @param enabledState array of size 2, holding the resulting enabled state for notification and indication + * using {@link #configNotificationIndication(bool, bool, bool[])}} + * @throws IllegalStateException if the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ - bool removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l); + bool addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l, bool enabledState[2]); /** - * Remove the given listener from the list. + * Disables the notification and/or indication for this characteristic at BLE level + * if {@code disableIndicationNotification == true} + * and removes the given {@link GATTCharacteristicListener} from the listener list. * <p> * Returns true if the given listener is an element of the list and has been removed, * otherwise false. * </p> * <p> * Convenience delegation call to GATTHandler via DBTDevice + * performing addCharacteristicListener(..) + * and {@link #configNotificationIndication(bool, bool, bool[]) if {@code disableIndicationNotification == true}. * </p> * <p> - * If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. + * If the DBTDevice's GATTHandler is null, i.e. not connected, {@code false} is being returned. * </p> + * @param l + * @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic + * using {@link #configNotificationIndication(bool, bool, bool[]) + * @return */ - bool removeCharacteristicListener(const GATTCharacteristicListener * l); + bool removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l, bool disableIndicationNotification); /** - * Remove all event listener from the list. + * Disables the notification and/or indication for this characteristic at BLE level + * if {@code disableIndicationNotification == true} + * and removes all {@link GATTCharacteristicListener} from the listener list. * <p> * Returns the number of removed event listener. * </p> * <p> * Convenience delegation call to GATTHandler via DBTDevice + * performing addCharacteristicListener(..) + * and configNotificationIndication(..) if {@code disableIndicationNotification == true}. * </p> * <p> - * If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. + * If the DBTDevice's GATTHandler is null, i.e. not connected, {@code zero} is being returned. * </p> + * @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic + * using {@link #configNotificationIndication(bool, bool, bool[]) + * @return */ - int removeAllCharacteristicListener(); + int removeAllCharacteristicListener(bool disableIndicationNotification); /** * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value @@ -319,6 +353,9 @@ namespace direct_bt { * see method's API doc for {@link GATTCharacteristic} filtering. * </p> * <p> + * User may utilize {@link AssociatedGATTCharacteristicListener} to listen to only one {@link GATTCharacteristic}. + * </p> + * <p> * A listener instance may be attached to a {@link GATTHandler} via * {@link GATTHandler::addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener>)} * to listen to all events of the device or the matching filtered events. @@ -345,9 +382,24 @@ namespace direct_bt { return true; } + /** + * Called from native BLE stack, initiated by a received notification associated + * with the given {@link GATTCharacteristic}. + * @param charDecl {@link GATTCharacteristic} related to this notification + * @param charValue the notification value + * @param timestamp the indication monotonic timestamp, see getCurrentMilliseconds() + */ virtual void notificationReceived(GATTCharacteristicRef charDecl, std::shared_ptr<TROOctets> charValue, const uint64_t timestamp) = 0; + /** + * Called from native BLE stack, initiated by a received indication associated + * with the given {@link GATTCharacteristic}. + * @param charDecl {@link GATTCharacteristic} related to this indication + * @param charValue the indication value + * @param timestamp the indication monotonic timestamp, see {@link BluetoothUtils#getCurrentMilliseconds()} + * @param confirmationSent if true, the native stack has sent the confirmation, otherwise user is required to do so. + */ virtual void indicationReceived(GATTCharacteristicRef charDecl, std::shared_ptr<TROOctets> charValue, const uint64_t timestamp, const bool confirmationSent) = 0; @@ -367,22 +419,22 @@ namespace direct_bt { { return !(*this == rhs); } }; - class SpecificGATTCharacteristicListener : public GATTCharacteristicListener{ + class AssociatedGATTCharacteristicListener : public GATTCharacteristicListener{ private: - const GATTCharacteristic * characteristicMatch; + const GATTCharacteristic * associatedCharacteristic; public: /** - * Passing the specific GATTCharacteristic to filter out non matching events. + * Passing the associated GATTCharacteristic to filter out non matching events. */ - SpecificGATTCharacteristicListener(const GATTCharacteristic * characteristicMatch) - : characteristicMatch(characteristicMatch) { } + AssociatedGATTCharacteristicListener(const GATTCharacteristic * characteristicMatch) + : associatedCharacteristic(characteristicMatch) { } bool match(const GATTCharacteristic & characteristic) override { - if( nullptr == characteristicMatch ) { + if( nullptr == associatedCharacteristic ) { return true; } - return *characteristicMatch == characteristic; + return *associatedCharacteristic == characteristic; } }; diff --git a/api/direct_bt/GATTHandler.hpp b/api/direct_bt/GATTHandler.hpp index 99d5d7ad..40162184 100644 --- a/api/direct_bt/GATTHandler.hpp +++ b/api/direct_bt/GATTHandler.hpp @@ -106,7 +106,7 @@ namespace direct_bt { /** send immediate confirmation of indication events from device, defaults to true. */ bool sendIndicationConfirmation = true; - std::vector<std::shared_ptr<GATTCharacteristicListener>> eventListenerList; + std::vector<std::shared_ptr<GATTCharacteristicListener>> characteristicListenerList; std::recursive_mutex mtx_eventListenerList; uint16_t serverMTU; @@ -310,10 +310,13 @@ namespace direct_bt { /** * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration * <p> + * Method enables notification and/or indication for the corresponding characteristic at BLE level. + * </p> + * <p> * Throws an IllegalArgumentException if the given GATTDescriptor is not a ClientCharacteristicConfiguration. * </p> */ - bool configIndicationNotification(GATTDescriptor & cd, const bool enableNotification, const bool enableIndication); + bool configNotificationIndication(GATTDescriptor & cd, const bool enableNotification, const bool enableIndication); /** * Add the given listener to the list if not already present. @@ -341,8 +344,22 @@ namespace direct_bt { * </p> */ bool removeCharacteristicListener(const GATTCharacteristicListener * l); + /** + * Remove all {@link GATTCharacteristicListener} from the list, which are associated to the given {@link GATTCharacteristic}. + * <p> + * Implementation tests all listener's GATTCharacteristicListener::match(const GATTCharacteristic & characteristic) + * to match with the given associated characteristic. + * </p> + * @param associatedCharacteristic the match criteria to remove any GATTCharacteristicListener from the list + * @return number of removed listener. + */ + int removeAllAssociatedCharacteristicListener(std::shared_ptr<GATTCharacteristic> associatedCharacteristic); + + int removeAllAssociatedCharacteristicListener(const GATTCharacteristic * associatedCharacteristic); + + /** * Remove all event listener from the list. * <p> * Returns the number of removed event listener. diff --git a/examples/direct_bt_scanner00/dbt_scanner00.cpp b/examples/direct_bt_scanner00/dbt_scanner00.cpp index 617b52b8..e08998c9 100644 --- a/examples/direct_bt_scanner00/dbt_scanner00.cpp +++ b/examples/direct_bt_scanner00/dbt_scanner00.cpp @@ -95,11 +95,11 @@ class MyAdapterStatusListener : public AdapterStatusListener { static const uuid16_t _TEMPERATURE_MEASUREMENT(GattCharacteristicType::TEMPERATURE_MEASUREMENT); -class MyGATTEventListener : public SpecificGATTCharacteristicListener { +class MyGATTEventListener : public AssociatedGATTCharacteristicListener { public: MyGATTEventListener(const GATTCharacteristic * characteristicMatch) - : SpecificGATTCharacteristicListener(characteristicMatch) {} + : AssociatedGATTCharacteristicListener(characteristicMatch) {} void notificationReceived(GATTCharacteristicRef charDecl, std::shared_ptr<TROOctets> charValue, const uint64_t timestamp) override { const std::shared_ptr<DBTDevice> dev = charDecl->getDevice(); @@ -278,7 +278,7 @@ int main(int argc, char *argv[]) } } bool cccdEnableResult[2]; - bool cccdRet = serviceChar.configIndicationNotification(true /* enableNotification */, true /* enableIndication */, cccdEnableResult); + bool cccdRet = serviceChar.configNotificationIndication(true /* enableNotification */, true /* enableIndication */, cccdEnableResult); fprintf(stderr, " [%2.2d.%2.2d] Config Notification(%d), Indication(%d): Result %d\n", (int)i, (int)j, cccdEnableResult[0], cccdEnableResult[1], cccdRet); if( cccdRet ) { diff --git a/examples/direct_bt_scanner01/dbt_scanner01.cpp b/examples/direct_bt_scanner01/dbt_scanner01.cpp index 10eca512..2fead468 100644 --- a/examples/direct_bt_scanner01/dbt_scanner01.cpp +++ b/examples/direct_bt_scanner01/dbt_scanner01.cpp @@ -290,7 +290,7 @@ int main(int argc, char *argv[]) const bool enableNotification = serviceChar.hasProperties(GATTCharacteristic::PropertyBitVal::Notify); const bool enableIndication = serviceChar.hasProperties(GATTCharacteristic::PropertyBitVal::Indicate); if( enableNotification || enableIndication ) { - bool res = gatt->configIndicationNotification(*cccd, enableNotification, enableIndication); + bool res = gatt->configNotificationIndication(*cccd, enableNotification, enableIndication); fprintf(stderr, " [%2.2d.%2.2d] Config Notification(%d), Indication(%d): Result %d\n", (int)i, (int)j, enableNotification, enableIndication, res); } diff --git a/examples/direct_bt_scanner10/dbt_scanner10.cpp b/examples/direct_bt_scanner10/dbt_scanner10.cpp index bde839d8..4a4e7bd8 100644 --- a/examples/direct_bt_scanner10/dbt_scanner10.cpp +++ b/examples/direct_bt_scanner10/dbt_scanner10.cpp @@ -193,11 +193,11 @@ class MyAdapterStatusListener : public AdapterStatusListener { static const uuid16_t _TEMPERATURE_MEASUREMENT(GattCharacteristicType::TEMPERATURE_MEASUREMENT); -class MyGATTEventListener : public SpecificGATTCharacteristicListener { +class MyGATTEventListener : public AssociatedGATTCharacteristicListener { public: MyGATTEventListener(const GATTCharacteristic * characteristicMatch) - : SpecificGATTCharacteristicListener(characteristicMatch) {} + : AssociatedGATTCharacteristicListener(characteristicMatch) {} void notificationReceived(GATTCharacteristicRef charDecl, std::shared_ptr<TROOctets> charValue, const uint64_t timestamp) override { const std::shared_ptr<DBTDevice> dev = charDecl->getDevice(); @@ -306,12 +306,10 @@ static void processConnectedDevice(std::shared_ptr<DBTDevice> device) { } } bool cccdEnableResult[2]; - bool cccdRet = serviceChar.configIndicationNotification(true /* enableNotification */, true /* enableIndication */, cccdEnableResult); - fprintf(stderr, " [%2.2d.%2.2d] Config Notification(%d), Indication(%d): Result %d\n", + bool cccdRet = serviceChar.addCharacteristicListener( std::shared_ptr<GATTCharacteristicListener>( new MyGATTEventListener(&serviceChar) ), + cccdEnableResult ); + fprintf(stderr, " [%2.2d.%2.2d] addCharacteristicListener Notification(%d), Indication(%d): Result %d\n", (int)i, (int)j, cccdEnableResult[0], cccdEnableResult[1], cccdRet); - if( cccdRet ) { - serviceChar.addCharacteristicListener( std::shared_ptr<GATTCharacteristicListener>( new MyGATTEventListener(&serviceChar) ) ); - } } } // FIXME sleep 1s for potential callbacks .. diff --git a/examples/java/ScannerTinyB01.java b/examples/java/ScannerTinyB01.java index c2f7a739..d6385167 100644 --- a/examples/java/ScannerTinyB01.java +++ b/examples/java/ScannerTinyB01.java @@ -203,22 +203,6 @@ public class ScannerTinyB01 { adapter.enablePoweredNotifications(new BooleanNotification("Powered", timestamp_t0)); - final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener() { - @Override - public void notificationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp) { - System.err.println("****** GATT notificationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - - @Override - public void indicationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp, final boolean confirmationSent) { - System.err.println("****** GATT indicationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - }; - int loop = 0; try { while( forever || loop < max_loops ) { @@ -339,13 +323,23 @@ public class ScannerTinyB01 { } } - final boolean addedCharacteristicListenerRes; - if( isDirectBT ) { - addedCharacteristicListenerRes = - BluetoothGattService.addCharacteristicListenerToAll(sensor, primServices, myCharacteristicListener); - } else { - addedCharacteristicListenerRes = false; - } + final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener(null) { + @Override + public void notificationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp) { + System.err.println("****** GATT notificationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + + @Override + public void indicationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp, final boolean confirmationSent) { + System.err.println("****** GATT indicationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + }; + final boolean addedCharacteristicListenerRes = + BluetoothGattService.addCharacteristicListenerToAll(sensor, primServices, myCharacteristicListener); System.err.println("Added GATTCharacteristicListener: "+addedCharacteristicListenerRes); int i=0, j=0; @@ -373,12 +367,7 @@ public class ScannerTinyB01 { } Thread.sleep(1000); // FIXME: Wait for notifications - final boolean remRes; - if( isDirectBT ) { - remRes = BluetoothGattService.removeCharacteristicListenerFromAll(sensor, primServices, myCharacteristicListener); - } else { - remRes = false; - } + final boolean remRes = BluetoothGattService.removeCharacteristicListenerFromAll(sensor, primServices, myCharacteristicListener); System.err.println("Removed GATTCharacteristicListener: "+remRes); } sensor.disconnect(); diff --git a/examples/java/ScannerTinyB02.java b/examples/java/ScannerTinyB02.java index 678e88a0..d02ba9e7 100644 --- a/examples/java/ScannerTinyB02.java +++ b/examples/java/ScannerTinyB02.java @@ -193,22 +193,6 @@ public class ScannerTinyB02 { adapter.enablePoweredNotifications(new BooleanNotification("Powered", timestamp_t0)); - final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener() { - @Override - public void notificationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp) { - System.err.println("****** GATT notificationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - - @Override - public void indicationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp, final boolean confirmationSent) { - System.err.println("****** GATT indicationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - }; - int loop = 0; try { while( forever || loop < max_loops ) { @@ -346,6 +330,21 @@ public class ScannerTinyB02 { if ( null == primServices || primServices.isEmpty() ) { System.err.println("No BluetoothGattService found!"); } else { + final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener(null) { + @Override + public void notificationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp) { + System.err.println("****** GATT notificationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + + @Override + public void indicationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp, final boolean confirmationSent) { + System.err.println("****** GATT indicationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + }; final boolean addedCharacteristicListenerRes = BluetoothGattService.addCharacteristicListenerToAll(sensor, primServices, myCharacteristicListener); System.err.println("Added GATTCharacteristicListener: "+addedCharacteristicListenerRes); diff --git a/examples/java/ScannerTinyB10.java b/examples/java/ScannerTinyB10.java index 959bba5b..78235138 100644 --- a/examples/java/ScannerTinyB10.java +++ b/examples/java/ScannerTinyB10.java @@ -164,22 +164,6 @@ public class ScannerTinyB10 { } }; - final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener() { - @Override - public void notificationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp) { - System.err.println("****** GATT notificationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - - @Override - public void indicationReceived(final BluetoothGattCharacteristic charDecl, - final byte[] value, final long timestamp, final boolean confirmationSent) { - System.err.println("****** GATT indicationReceived: "+charDecl+ - ", value "+BluetoothUtils.bytesHexString(value, true, true)); - } - }; - private void connectDiscoveredDevice(final BluetoothDevice device) { System.err.println("****** Connecting Device: Start " + device.toString()); device.getAdapter().stopDiscovery(); @@ -237,9 +221,26 @@ public class ScannerTinyB10 { System.err.println(" over device : "+char2); } } - final boolean addedCharacteristicListenerRes = - BluetoothGattService.addCharacteristicListenerToAll(device, primServices, myCharacteristicListener); - System.err.println("Added GATTCharacteristicListener: "+addedCharacteristicListenerRes); + { + final GATTCharacteristicListener myCharacteristicListener = new GATTCharacteristicListener(null) { + @Override + public void notificationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp) { + System.err.println("****** GATT notificationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + + @Override + public void indicationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp, final boolean confirmationSent) { + System.err.println("****** GATT indicationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + }; + final boolean addedCharacteristicListenerRes = + BluetoothGattService.addCharacteristicListenerToAll(device, primServices, myCharacteristicListener); + System.err.println("Added GATTCharacteristicListener: "+addedCharacteristicListenerRes); + } int i=0, j=0; for(final Iterator<BluetoothGattService> srvIter = primServices.iterator(); srvIter.hasNext(); i++) { diff --git a/java/direct_bt/tinyb/DBTDevice.java b/java/direct_bt/tinyb/DBTDevice.java index 1f9ec3be..fa47a6ff 100644 --- a/java/direct_bt/tinyb/DBTDevice.java +++ b/java/direct_bt/tinyb/DBTDevice.java @@ -241,7 +241,8 @@ public class DBTDevice extends DBTObject implements BluetoothDevice return; } if( !isShutdown ) { // avoid all interaction @ JVM shutdown, native dtor (deleteImpl) cleans up. - disconnect(); + // implicit via disconnect, gatt.disconnect(): GATTHandler::removeAllCharacteristicListener(); + disconnectImpl(); // make sure, regardless of isConnected state disableConnectedNotifications(); disableRSSINotifications(); @@ -651,12 +652,18 @@ public class DBTDevice extends DBTObject implements BluetoothDevice protected native void deleteImpl(long nativeInstance); @Override - public native boolean addCharacteristicListener(final GATTCharacteristicListener listener, final BluetoothGattCharacteristic characteristicMatch); + public boolean addCharacteristicListener(final GATTCharacteristicListener listener) { + return addCharacteristicListener(listener, (DBTGattCharacteristic)listener.getAssociatedCharacteristic()); + } + private native boolean addCharacteristicListener(final GATTCharacteristicListener listener, final DBTGattCharacteristic associatedCharacteristic); @Override public native boolean removeCharacteristicListener(final GATTCharacteristicListener l); @Override + public native int removeAllAssociatedCharacteristicListener(final BluetoothGattCharacteristic associatedCharacteristic); + + @Override public native int removeAllCharacteristicListener(); /* local functionality */ diff --git a/java/direct_bt/tinyb/DBTGattCharacteristic.java b/java/direct_bt/tinyb/DBTGattCharacteristic.java index 7a0973f1..a5b07f2a 100644 --- a/java/direct_bt/tinyb/DBTGattCharacteristic.java +++ b/java/direct_bt/tinyb/DBTGattCharacteristic.java @@ -33,7 +33,6 @@ import org.tinyb.BluetoothException; import org.tinyb.BluetoothGattCharacteristic; import org.tinyb.BluetoothGattDescriptor; import org.tinyb.BluetoothGattService; -import org.tinyb.BluetoothManager; import org.tinyb.BluetoothNotification; import org.tinyb.BluetoothObject; import org.tinyb.BluetoothType; @@ -57,8 +56,8 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha /* Characteristics Property */ private final String[] properties; - // private final boolean hasNotify; - // private final boolean hasIndicate; + private final boolean hasNotify; + private final boolean hasIndicate; /* Characteristics Value Type UUID */ private final String value_type_uuid; @@ -96,41 +95,9 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha return valueChanged; } - private final GATTCharacteristicListener characteristicListener = new GATTCharacteristicListener() { - - @Override - public void notificationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp) { - final DBTGattCharacteristic cd = (DBTGattCharacteristic)charDecl; - if( !cd.equals(DBTGattCharacteristic.this) ) { - throw new InternalError("Filtered GATTCharacteristicListener.notificationReceived: Wrong Characteristic: Got "+charDecl+ - ", expected "+DBTGattCharacteristic.this.toString()); - } - final boolean valueChanged = updateCachedValue(value, true); - if( DEBUG ) { - System.err.println("GATTCharacteristicListener.notificationReceived: "+charDecl+ - ", value[changed "+valueChanged+", data "+BluetoothUtils.bytesHexString(value, true, true)+"]"); - } - } - - @Override - public void indicationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp, - final boolean confirmationSent) { - final DBTGattCharacteristic cd = (DBTGattCharacteristic)charDecl; - if( !cd.equals(DBTGattCharacteristic.this) ) { - throw new InternalError("Filtered GATTCharacteristicListener.indicationReceived: Wrong Characteristic: Got "+charDecl+ - ", expected "+DBTGattCharacteristic.this.toString()); - } - final boolean valueChanged = updateCachedValue(value, true); - if( DEBUG ) { - System.err.println("GATTCharacteristicListener.indicationReceived: "+charDecl+ - ", value[changed "+valueChanged+", data "+BluetoothUtils.bytesHexString(value, true, true)+"]"); - } - } - - }; - /* pp */ DBTGattCharacteristic(final long nativeInstance, final DBTGattService service, final short handle, final String[] properties, + final boolean hasNotify, final boolean hasIndicate, final String value_type_uuid, final short value_handle, final int clientCharacteristicsConfigIndex) { @@ -139,26 +106,47 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha this.handle = handle; this.properties = properties; - /** { - boolean hasNotify = false; - boolean hasIndicate = false; - for(int i=0; !hasNotify && !hasIndicate && i<properties.length; i++) { - if( "notify".equals(properties[i]) ) { - hasNotify = true; - } - if( "indicate".equals(properties[i]) ) { - hasIndicate = true; - } - } - this.hasNotify = hasNotify; - this.hasIndicate = hasIndicate; - } */ - + this.hasNotify = hasNotify; + this.hasIndicate = hasIndicate; this.value_type_uuid = value_type_uuid; this.value_handle = value_handle; this.clientCharacteristicsConfigIndex = clientCharacteristicsConfigIndex; this.descriptorList = getDescriptorsImpl(); - this.addCharacteristicListener(characteristicListener, false); // silent, don't enable native GATT ourselves + + if( hasNotify || hasIndicate ) { + // This characteristicListener serves TinyB 'enableValueNotification(..)' and 'getValue()' (cached value) + // backwards compatibility only! + final GATTCharacteristicListener characteristicListener = new GATTCharacteristicListener(this) { + @Override + public void notificationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp) { + final DBTGattCharacteristic cd = (DBTGattCharacteristic)charDecl; + if( !cd.equals(DBTGattCharacteristic.this) ) { + throw new InternalError("Filtered GATTCharacteristicListener.notificationReceived: Wrong Characteristic: Got "+charDecl+ + ", expected "+DBTGattCharacteristic.this.toString()); + } + final boolean valueChanged = updateCachedValue(value, true); + if( DEBUG ) { + System.err.println("GATTCharacteristicListener.notificationReceived: "+charDecl+ + ", value[changed "+valueChanged+", data "+BluetoothUtils.bytesHexString(value, true, true)+"]"); + } + } + @Override + public void indicationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp, + final boolean confirmationSent) { + final DBTGattCharacteristic cd = (DBTGattCharacteristic)charDecl; + if( !cd.equals(DBTGattCharacteristic.this) ) { + throw new InternalError("Filtered GATTCharacteristicListener.indicationReceived: Wrong Characteristic: Got "+charDecl+ + ", expected "+DBTGattCharacteristic.this.toString()); + } + final boolean valueChanged = updateCachedValue(value, true); + if( DEBUG ) { + System.err.println("GATTCharacteristicListener.indicationReceived: "+charDecl+ + ", value[changed "+valueChanged+", data "+BluetoothUtils.bytesHexString(value, true, true)+"]"); + } + } + }; + this.addCharacteristicListener(characteristicListener); // silent, don't enable native GATT ourselves + } } @Override @@ -166,8 +154,7 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha if( !isValid() ) { return; } - removeAllCharacteristicListener(); - disableValueNotifications(); + removeAllAssociatedCharacteristicListener(true); super.close(); } @@ -235,57 +222,74 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha public final List<BluetoothGattDescriptor> getDescriptors() { return descriptorList; } @Override - public final synchronized void enableValueNotifications(final BluetoothNotification<byte[]> callback) { - final boolean res = enableValueNotificationsImpl(true); - if( DEBUG ) { - System.err.println("GATTCharacteristicListener.enableValueNotifications: GATT Native: "+res); - } - valueNotificationCB = callback; - } - - @Override - public final synchronized void disableValueNotifications() { - try { - final boolean res = enableValueNotificationsImpl(false); + public final synchronized boolean configNotificationIndication(final boolean enableNotification, final boolean enableIndication, final boolean enabledState[/*2*/]) + throws IllegalStateException + { + if( hasNotify || hasIndicate ) { + final boolean res = configNotificationIndicationImpl(enableNotification, enableIndication, enabledState); if( DEBUG ) { - System.err.println("GATTCharacteristicListener.disableValueNotifications: GATT Native: "+res); + System.err.println("GATTCharacteristicListener.configNotificationIndication: "+res+", enableResult "+Arrays.toString(enabledState)); } - } catch (final Throwable t) { + return res; + } else { + enabledState[0] = false; + enabledState[1] = false; if( DEBUG ) { - System.err.println("Caught "+t.getMessage()); - t.printStackTrace(); + System.err.println("GATTCharacteristicListener.configNotificationIndication: FALSE*"); } + return false; } - valueNotificationCB = null; } + private native boolean configNotificationIndicationImpl(boolean enableNotification, boolean enableIndication, final boolean enabledState[/*2*/]) + throws IllegalStateException; @Override - public final boolean getNotifying() { - return null != valueNotificationCB; + public final boolean addCharacteristicListener(final GATTCharacteristicListener listener) { + return getService().getDevice().addCharacteristicListener(listener); } @Override - public final boolean addCharacteristicListener(final GATTCharacteristicListener listener) { - return addCharacteristicListener(listener, true); - } - private final boolean addCharacteristicListener(final GATTCharacteristicListener listener, final boolean nativeEnable) { - if( nativeEnable ) { - final boolean res = enableValueNotificationsImpl(true); - if( DEBUG ) { - System.err.println("GATTCharacteristicListener.addCharacteristicListener: GATT Native: "+res); - } + public final boolean addCharacteristicListener(final GATTCharacteristicListener listener, final boolean enabledState[/*2*/]) { + if( !configNotificationIndication(true /* enableNotification */, true /* enableIndication */, enabledState) ) { + return false; } - return getService().getDevice().addCharacteristicListener(listener, this); + return getService().getDevice().addCharacteristicListener(listener); } @Override - public final boolean removeCharacteristicListener(final GATTCharacteristicListener l) { + public final boolean removeCharacteristicListener(final GATTCharacteristicListener l, final boolean disableIndicationNotification) { + if( disableIndicationNotification ) { + configNotificationIndication(false /* enableNotification */, false /* enableIndication */, new boolean[2]); + } return getService().getDevice().removeCharacteristicListener(l); } @Override - public final int removeAllCharacteristicListener() { - return getService().getDevice().removeAllCharacteristicListener(); + public final int removeAllAssociatedCharacteristicListener(final boolean disableIndicationNotification) { + if( disableIndicationNotification ) { + configNotificationIndication(false /* enableNotification */, false /* enableIndication */, new boolean[2]); + } + return getService().getDevice().removeAllAssociatedCharacteristicListener(this); + } + + @Override + public final synchronized void enableValueNotifications(final BluetoothNotification<byte[]> callback) { + if( !configNotificationIndication(true /* enableNotification */, true /* enableIndication */, null) ) { + valueNotificationCB = null; + } else { + valueNotificationCB = callback; + } + } + + @Override + public final synchronized void disableValueNotifications() { + configNotificationIndication(false /* enableNotification */, false /* enableIndication */, null); + valueNotificationCB = null; + } + + @Override + public final boolean getNotifying() { + return null != valueNotificationCB; } /** @@ -325,11 +329,6 @@ public class DBTGattCharacteristic extends DBTObject implements BluetoothGattCha private native List<BluetoothGattDescriptor> getDescriptorsImpl(); - /** - * Enables disables GATT notification and/or indication. - */ - private native boolean enableValueNotificationsImpl(boolean v); - @Override protected native void deleteImpl(long nativeInstance); diff --git a/java/jni/BluetoothUtils.cxx b/java/jni/BluetoothUtils.cxx index b05ec66f..8278b8a7 100644 --- a/java/jni/BluetoothUtils.cxx +++ b/java/jni/BluetoothUtils.cxx @@ -70,7 +70,7 @@ jstring Java_org_tinyb_BluetoothUtils_decodeUTF8String(JNIEnv *env, jclass clazz throw std::invalid_argument(msg.c_str()); } - JNICriticalArray<uint8_t> criticalArray(env); // RAII - release + JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release uint8_t * buffer_ptr = criticalArray.get(jbuffer, criticalArray.Mode::NO_UPDATE_AND_RELEASE); if( NULL == buffer_ptr ) { throw std::invalid_argument("GetPrimitiveArrayCritical(byte array) is null"); diff --git a/java/jni/JNIMem.hpp b/java/jni/JNIMem.hpp index 6cdc832a..03ec4948 100644 --- a/java/jni/JNIMem.hpp +++ b/java/jni/JNIMem.hpp @@ -121,7 +121,7 @@ public: * * RAII-style acquire and relinquish via destructor */ -template <typename T> +template <typename T, typename U> class JNICriticalArray { public: enum Mode : jint { @@ -138,7 +138,7 @@ public: private: JNIEnv *env; Mode mode = UPDATE_AND_RELEASE; - jbyteArray jarray = nullptr; + U jarray = nullptr; T* narray = nullptr; jboolean isCopy = false; @@ -173,7 +173,7 @@ public: /** * Acquired the primitive array. */ - T* get(jbyteArray jarray, Mode mode=UPDATE_AND_RELEASE) { + T* get(U jarray, Mode mode=UPDATE_AND_RELEASE) { if( nullptr == jarray ) { return nullptr; } diff --git a/java/jni/direct_bt/DBTDevice.cxx b/java/jni/direct_bt/DBTDevice.cxx index cd073cb9..ae57644a 100644 --- a/java/jni/direct_bt/DBTDevice.cxx +++ b/java/jni/direct_bt/DBTDevice.cxx @@ -59,26 +59,28 @@ class JNICharacteristicListener : public GATTCharacteristicListener { }; */ - const GATTCharacteristic * characteristicMatchRef; - std::shared_ptr<JavaAnonObj> deviceObjRef; - std::unique_ptr<JNIGlobalRef> listenerObjRef; + const GATTCharacteristic * associatedCharacteristicRef; + JNIGlobalRef listenerObj; // keep listener instance alive + JNIGlobalRef associatedCharacteristicObj; // keeps associated characteristic alive, if not null jmethodID mNotificationReceived = nullptr; jmethodID mIndicationReceived = nullptr; public: - JNICharacteristicListener(JNIEnv *env, DBTDevice *device, jobject listener, const GATTCharacteristic * characteristicMatchRef) { - deviceObjRef = device->getJavaObject(); - JavaGlobalObj::check(deviceObjRef, E_FILE_LINE); - - listenerObjRef = std::unique_ptr<JNIGlobalRef>(new JNIGlobalRef(listener)); - jclass listenerClazz = search_class(env, listenerObjRef->getObject()); + JNICharacteristicListener(JNIEnv *env, DBTDevice *device, jobject listener, GATTCharacteristic * associatedCharacteristicRef) + : associatedCharacteristicRef(associatedCharacteristicRef), + listenerObj(listener) + { + jclass listenerClazz = search_class(env, listenerObj.getObject()); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == listenerClazz ) { throw InternalError("CharacteristicListener not found", E_FILE_LINE); } - this->characteristicMatchRef = characteristicMatchRef; + if( nullptr != associatedCharacteristicRef ) { + JavaGlobalObj::check(associatedCharacteristicRef->getJavaObject(), E_FILE_LINE); + associatedCharacteristicObj = JavaGlobalObj::GetJavaObject(associatedCharacteristicRef->getJavaObject()); // new global ref + } mNotificationReceived = search_method(env, listenerClazz, "notificationReceived", _notificationReceivedMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); @@ -93,10 +95,10 @@ class JNICharacteristicListener : public GATTCharacteristicListener { } bool match(const GATTCharacteristic & characteristic) override { - if( nullptr == characteristicMatchRef ) { + if( nullptr == associatedCharacteristicRef ) { return true; } - return characteristic == *characteristicMatchRef; + return characteristic == *associatedCharacteristicRef; } void notificationReceived(GATTCharacteristicRef charDecl, @@ -111,7 +113,7 @@ class JNICharacteristicListener : public GATTCharacteristicListener { java_exception_check_and_throw(env, E_FILE_LINE); - env->CallVoidMethod(listenerObjRef->getObject(), mNotificationReceived, + env->CallVoidMethod(listenerObj.getObject(), mNotificationReceived, jCharDecl, jvalue, (jlong)timestamp); java_exception_check_and_throw(env, E_FILE_LINE); } @@ -129,7 +131,7 @@ class JNICharacteristicListener : public GATTCharacteristicListener { java_exception_check_and_throw(env, E_FILE_LINE); - env->CallVoidMethod(listenerObjRef->getObject(), mIndicationReceived, + env->CallVoidMethod(listenerObj.getObject(), mIndicationReceived, jCharDecl, jvalue, (jlong)timestamp, (jboolean)confirmationSent); java_exception_check_and_throw(env, E_FILE_LINE); } @@ -168,16 +170,16 @@ jstring Java_direct_1bt_tinyb_DBTDevice_toStringImpl(JNIEnv *env, jobject obj) { return nullptr; } -jboolean Java_direct_1bt_tinyb_DBTDevice_addCharacteristicListener(JNIEnv *env, jobject obj, jobject listener, jobject jcharacteristicMatch) { +jboolean Java_direct_1bt_tinyb_DBTDevice_addCharacteristicListener(JNIEnv *env, jobject obj, jobject listener, jobject jAssociatedCharacteristic) { try { if( nullptr == listener ) { - throw IllegalArgumentException("characteristicListener is null", E_FILE_LINE); + throw IllegalArgumentException("characteristicListener argument is null", E_FILE_LINE); } { JNICharacteristicListener * pre = getObjectRef<JNICharacteristicListener>(env, listener, "nativeInstance"); if( nullptr != pre ) { - WARN_PRINT("characteristicListener's nativeInstance not null, already in use"); + throw IllegalStateException("CharacteristicListener's nativeInstance not null, already in use", E_FILE_LINE); return false; } } @@ -188,14 +190,13 @@ jboolean Java_direct_1bt_tinyb_DBTDevice_addCharacteristicListener(JNIEnv *env, throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ device->toString(), E_FILE_LINE); } - GATTCharacteristic * characteristicMatchRef = nullptr; - if( nullptr != jcharacteristicMatch ) { - characteristicMatchRef = getInstance<GATTCharacteristic>(env, jcharacteristicMatch); - JavaGlobalObj::check(characteristicMatchRef->getJavaObject(), E_FILE_LINE); + GATTCharacteristic * associatedCharacteristicRef = nullptr; + if( nullptr != jAssociatedCharacteristic ) { + associatedCharacteristicRef = getInstance<GATTCharacteristic>(env, jAssociatedCharacteristic); } std::shared_ptr<GATTCharacteristicListener> l = - std::shared_ptr<GATTCharacteristicListener>( new JNICharacteristicListener(env, device, listener, characteristicMatchRef) ); + std::shared_ptr<GATTCharacteristicListener>( new JNICharacteristicListener(env, device, listener, associatedCharacteristicRef) ); if( gatt->addCharacteristicListener(l) ) { setInstance(env, listener, l.get()); @@ -207,18 +208,18 @@ jboolean Java_direct_1bt_tinyb_DBTDevice_addCharacteristicListener(JNIEnv *env, return JNI_FALSE; } -jboolean Java_direct_1bt_tinyb_DBTDevice_removeCharacteristicListener(JNIEnv *env, jobject obj, jobject statusListener) { +jboolean Java_direct_1bt_tinyb_DBTDevice_removeCharacteristicListener(JNIEnv *env, jobject obj, jobject jlistener) { try { - if( nullptr == statusListener ) { - throw IllegalArgumentException("characteristicListener is null", E_FILE_LINE); + if( nullptr == jlistener ) { + throw IllegalArgumentException("characteristicListener argument is null", E_FILE_LINE); } JNICharacteristicListener * pre = - getObjectRef<JNICharacteristicListener>(env, statusListener, "nativeInstance"); + getObjectRef<JNICharacteristicListener>(env, jlistener, "nativeInstance"); if( nullptr == pre ) { WARN_PRINT("characteristicListener's nativeInstance is null, not in use"); return false; } - setObjectRef<JNICharacteristicListener>(env, statusListener, nullptr, "nativeInstance"); + setObjectRef<JNICharacteristicListener>(env, jlistener, nullptr, "nativeInstance"); DBTDevice *device = getInstance<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); @@ -240,6 +241,30 @@ jboolean Java_direct_1bt_tinyb_DBTDevice_removeCharacteristicListener(JNIEnv *en return JNI_FALSE; } +jint Java_direct_1bt_tinyb_DBTDevice_removeAllAssociatedCharacteristicListener(JNIEnv *env, jobject obj, jobject jAssociatedCharacteristic) { + try { + if( nullptr == jAssociatedCharacteristic ) { + throw IllegalArgumentException("associatedCharacteristic argument is null", E_FILE_LINE); + } + DBTDevice *device = getInstance<DBTDevice>(env, obj); + JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); + if( nullptr == gatt ) { + // OK to have GATTHandler being shutdown @ disable + DBG_PRINT("Characteristic's device GATTHandle not connected: %s", device->toString().c_str()); + return 0; + } + + GATTCharacteristic * associatedCharacteristicRef = getInstance<GATTCharacteristic>(env, jAssociatedCharacteristic); + JavaGlobalObj::check(associatedCharacteristicRef->getJavaObject(), E_FILE_LINE); + + return gatt->removeAllAssociatedCharacteristicListener(associatedCharacteristicRef); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return 0; +} + jint Java_direct_1bt_tinyb_DBTDevice_removeAllCharacteristicListener(JNIEnv *env, jobject obj) { try { DBTDevice *device = getInstance<DBTDevice>(env, obj); diff --git a/java/jni/direct_bt/DBTGattCharacteristic.cxx b/java/jni/direct_bt/DBTGattCharacteristic.cxx index d7541e2e..f376618e 100644 --- a/java/jni/direct_bt/DBTGattCharacteristic.cxx +++ b/java/jni/direct_bt/DBTGattCharacteristic.cxx @@ -145,7 +145,7 @@ jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_writeValueImpl(JNIEnv *env, GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); - JNICriticalArray<uint8_t> criticalArray(env); // RAII - release + JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release uint8_t * value_ptr = criticalArray.get(jvalue, criticalArray.Mode::NO_UPDATE_AND_RELEASE); if( NULL == value_ptr ) { throw InternalError("GetPrimitiveArrayCritical(byte array) is null", E_FILE_LINE); @@ -162,16 +162,31 @@ jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_writeValueImpl(JNIEnv *env, return JNI_FALSE; } -jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_enableValueNotificationsImpl(JNIEnv *env, jobject obj, jboolean enable) { +jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_configNotificationIndicationImpl(JNIEnv *env, jobject obj, + jboolean enableNotification, jboolean enableIndication, jbooleanArray jEnabledState) { try { GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); + if( nullptr == jEnabledState ) { + throw IllegalArgumentException("boolean array null", E_FILE_LINE); + } + const int state_size = env->GetArrayLength(jEnabledState); + if( 2 > state_size ) { + throw IllegalArgumentException("boolean array smaller than 2, length "+std::to_string(state_size), E_FILE_LINE); + } + JNICriticalArray<jboolean, jbooleanArray> criticalArray(env); // RAII - release + jboolean * state_ptr = criticalArray.get(jEnabledState, criticalArray.Mode::UPDATE_AND_RELEASE); + if( NULL == state_ptr ) { + throw InternalError("GetPrimitiveArrayCritical(boolean array) is null", E_FILE_LINE); + } + bool cccdEnableResult[2]; - bool res = characteristic->configIndicationNotification(enable, enable, cccdEnableResult); - DBG_PRINT("DBTGattCharacteristic::configIndicationNotification Config Notification(%d), Indication(%d): Result %d", + bool res = characteristic->configNotificationIndication(enableNotification, enableIndication, cccdEnableResult); + DBG_PRINT("DBTGattCharacteristic::configNotificationIndication Config Notification(%d), Indication(%d): Result %d", cccdEnableResult[0], cccdEnableResult[1], res); - (void) cccdEnableResult; + state_ptr[0] = cccdEnableResult[0]; + state_ptr[1] = cccdEnableResult[1]; return res; } catch(...) { rethrow_and_raise_java_exception(env); diff --git a/java/jni/direct_bt/DBTGattDescriptor.cxx b/java/jni/direct_bt/DBTGattDescriptor.cxx index 081fda33..066e9f5f 100644 --- a/java/jni/direct_bt/DBTGattDescriptor.cxx +++ b/java/jni/direct_bt/DBTGattDescriptor.cxx @@ -92,7 +92,7 @@ jboolean Java_direct_1bt_tinyb_DBTGattDescriptor_writeValueImpl(JNIEnv *env, job GATTDescriptor *descriptor = getInstance<GATTDescriptor>(env, obj); JavaGlobalObj::check(descriptor->getJavaObject(), E_FILE_LINE); - JNICriticalArray<uint8_t> criticalArray(env); // RAII - release + JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release uint8_t * value_ptr = criticalArray.get(jvalue, criticalArray.Mode::NO_UPDATE_AND_RELEASE); if( NULL == value_ptr ) { throw InternalError("GetPrimitiveArrayCritical(byte array) is null", E_FILE_LINE); diff --git a/java/jni/direct_bt/DBTGattService.cxx b/java/jni/direct_bt/DBTGattService.cxx index fcb234e8..7023d755 100644 --- a/java/jni/direct_bt/DBTGattService.cxx +++ b/java/jni/direct_bt/DBTGattService.cxx @@ -60,7 +60,7 @@ void Java_direct_1bt_tinyb_DBTGattService_deleteImpl(JNIEnv *env, jobject obj, j } } -static const std::string _characteristicClazzCtorArgs("(JLdirect_bt/tinyb/DBTGattService;S[Ljava/lang/String;Ljava/lang/String;SI)V"); +static const std::string _characteristicClazzCtorArgs("(JLdirect_bt/tinyb/DBTGattService;S[Ljava/lang/String;ZZLjava/lang/String;SI)V"); jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristicsImpl(JNIEnv *env, jobject obj) { try { @@ -71,6 +71,7 @@ jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristicsImpl(JNIEnv *env, // DBTGattCharacteristic(final long nativeInstance, final DBTGattService service, // final short handle, final String[] properties, + // final boolean hasNotify, final boolean hasIndicate, // final String value_type_uuid, final short value_handle, // final int clientCharacteristicsConfigIndex) @@ -97,13 +98,16 @@ jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristicsImpl(JNIEnv *env, } java_exception_check_and_throw(env, E_FILE_LINE); + const bool hasNotify = characteristic->hasProperties(GATTCharacteristic::PropertyBitVal::Notify); + const bool hasIndicate = characteristic->hasProperties(GATTCharacteristic::PropertyBitVal::Indicate); + const jstring uuid = from_string_to_jstring(env, directBTJNISettings.getUnifyUUID128Bit() ? characteristic->value_type->toUUID128String() : characteristic->value_type->toString()); java_exception_check_and_throw(env, E_FILE_LINE); jobject jchar = env->NewObject(clazz, clazz_ctor, (jlong)characteristic, jservice, - characteristic->handle, jproperties, + characteristic->handle, jproperties, hasNotify, hasIndicate, uuid, characteristic->value_handle, characteristic->clientCharacteristicsConfigIndex); java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(jchar, E_FILE_LINE); diff --git a/java/jni/direct_bt/helper_dbt.hpp b/java/jni/direct_bt/helper_dbt.hpp index d71104e7..abd78b50 100644 --- a/java/jni/direct_bt/helper_dbt.hpp +++ b/java/jni/direct_bt/helper_dbt.hpp @@ -106,6 +106,10 @@ namespace direct_bt { /* Provides access to the stored GlobalRef as a jclass. */ jclass getClass() const { return javaObjectRef.getClass(); } + /* Provides access to the stored GlobalRef as an getJavaObject. */ + static JNIGlobalRef GetJavaObject(const std::shared_ptr<JavaAnonObj> & shref) { + return static_cast<JavaGlobalObj*>(shref.get())->getJavaObject(); + } /* Provides access to the stored GlobalRef as an jobject. */ static jobject GetObject(const std::shared_ptr<JavaAnonObj> & shref) { return static_cast<JavaGlobalObj*>(shref.get())->getObject(); diff --git a/java/org/tinyb/BluetoothDevice.java b/java/org/tinyb/BluetoothDevice.java index 0db213f0..d3b004f9 100644 --- a/java/org/tinyb/BluetoothDevice.java +++ b/java/org/tinyb/BluetoothDevice.java @@ -443,16 +443,20 @@ public interface BluetoothDevice extends BluetoothObject /** * Add the given {@link GATTCharacteristicListener} to the listener list if not already present. * @param listener A {@link GATTCharacteristicListener} instance, listening to all {@link BluetoothGattCharacteristic} events of this device - * @param characteristicMatch Optional {@link BluetoothGattCharacteristic} to be matched before calling any - * {@link GATTCharacteristicListener} methods. Pass {@code null} for no filtering. * @return true if the given listener is not element of the list and has been newly added, otherwise false. + * @throws IllegalStateException if the {@link BluetoothDevice}'s GATTHandler is null, i.e. not connected + * @throws IllegalStateException if the given {@link GATTCharacteristicListener} is already in use, i.e. added. * @since 2.0.0 * @implNote not implemented in tinyb.dbus */ - public boolean addCharacteristicListener(final GATTCharacteristicListener listener, final BluetoothGattCharacteristic characteristicMatch); + public boolean addCharacteristicListener(final GATTCharacteristicListener listener) + throws IllegalStateException; /** * Remove the given {@link GATTCharacteristicListener} from the listener list. + * <p> + * If the {@link BluetoothDevice}'s GATTHandler is null, i.e. not connected, {@code false} is being returned. + * </p> * @param listener A {@link GATTCharacteristicListener} instance * @return true if the given listener is an element of the list and has been removed, otherwise false. * @since 2.0.0 @@ -461,6 +465,19 @@ public interface BluetoothDevice extends BluetoothObject public boolean removeCharacteristicListener(final GATTCharacteristicListener l); /** + * Remove all {@link GATTCharacteristicListener} from the list, which are associated to the given {@link BluetoothGattCharacteristic}. + * <p> + * Implementation tests all listener's {@link GATTCharacteristicListener#getAssociatedCharacteristic()} + * to match with the given associated characteristic. + * </p> + * @param associatedCharacteristic the match criteria to remove any GATTCharacteristicListener from the list + * @return number of removed listener. + * @since 2.0.0 + * @implNote not implemented in tinyb.dbus + */ + public int removeAllAssociatedCharacteristicListener(final BluetoothGattCharacteristic associatedCharacteristic); + + /** * Remove all {@link GATTCharacteristicListener} from the list. * @return number of removed listener. * @since 2.0.0 diff --git a/java/org/tinyb/BluetoothGattCharacteristic.java b/java/org/tinyb/BluetoothGattCharacteristic.java index 27c0bccc..586e7b2c 100644 --- a/java/org/tinyb/BluetoothGattCharacteristic.java +++ b/java/org/tinyb/BluetoothGattCharacteristic.java @@ -71,31 +71,112 @@ public interface BluetoothGattCharacteristic extends BluetoothObject public byte[] readValue() throws BluetoothException; /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * <p> + * Method enables notification and/or indication for this characteristic at BLE level. + * </p> + * <p> + * Implementation masks this Characteristic properties PropertyBitVal::Notify and PropertyBitVal::Indicate + * with the respective user request parameters, hence removes unsupported requests. + * </p> + * @param enableNotification + * @param enableIndication + * @param enabledState array of size 2, holding the resulting enabled state for notification and indication. + * @return false if this characteristic has no PropertyBitVal::Notify or PropertyBitVal::Indication present, + * or there is no GATTDescriptor of type ClientCharacteristicConfiguration, or if the operation has failed. + * Otherwise returns true. + * @throws IllegalStateException if notification or indication is set to be enabled + * and the {@link BluetoothDevice}'s GATTHandler is null, i.e. not connected + * @since 2.0.0 + * @implNote not implemented in tinyb.dbus + */ + public boolean configNotificationIndication(final boolean enableNotification, final boolean enableIndication, final boolean enabledState[/*2*/]) + throws IllegalStateException; + + /** * Add the given {@link GATTCharacteristicListener} to the listener list if not already present. + * <p> + * Occurring notifications and indications, if enabled via {#link {@link #configNotificationIndication(boolean, boolean, boolean[])}}, + * will call the respective GATTCharacteristicListener callback method. + * </p> * @param listener A {@link GATTCharacteristicListener} instance, listening to this {@link BluetoothGattCharacteristic}'s events * @return true if the given listener is not element of the list and has been newly added, otherwise false. + * @throws IllegalStateException if the DBTDevice's GATTHandler is null, i.e. not connected + * @throws IllegalStateException if the given {@link GATTCharacteristicListener} is already in use, i.e. added. + * @since 2.0.0 + * @implNote not implemented in tinyb.dbus + */ + public boolean addCharacteristicListener(final GATTCharacteristicListener listener) + throws IllegalStateException; + + /** + * Add the given {@link GATTCharacteristicListener} to the listener list if not already present + * and if enabling the notification and/or indication for this characteristic at BLE level was successful. + * <p> + * Occurring notifications and indications will call the respective {@link GATTCharacteristicListener} + * callback method. + * </p> + * @param listener A {@link GATTCharacteristicListener} instance, listening to this {@link BluetoothGattCharacteristic}'s events + * @param enabledState array of size 2, holding the resulting enabled state for notification and indication + * using #configNotificationIndication(boolean, boolean, boolean[]) + * @return true if enabling the notification and/or indication was successful + * and if the given listener is not element of the list and has been newly added, otherwise false. + * @throws IllegalStateException if the {@link BluetoothDevice}'s GATTHandler is null, i.e. not connected + * @throws IllegalStateException if the given {@link GATTCharacteristicListener} is already in use, i.e. added. + * @see #configNotificationIndication(boolean, boolean, boolean[]) * @since 2.0.0 + * @implNote not implemented in tinyb.dbus */ - public boolean addCharacteristicListener(final GATTCharacteristicListener listener); + public boolean addCharacteristicListener(final GATTCharacteristicListener listener, final boolean enabledState[/*2*/]) + throws IllegalStateException; /** - * Remove the given {@link GATTCharacteristicListener} from the listener list. + * Disables the notification and/or indication for this characteristic at BLE level + * if {@code disableIndicationNotification == true} + * and removes the given {@link GATTCharacteristicListener} from the listener list. + * <p> + * If the DBTDevice's GATTHandler is null, i.e. not connected, {@code false} is being returned. + * </p> + * * @param listener A {@link GATTCharacteristicListener} instance + * @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic + * using {@link #configNotificationIndication(boolean, boolean, boolean[]) * @return true if the given listener is an element of the list and has been removed, otherwise false. + * @see #configNotificationIndication(boolean, boolean, boolean[]) * @since 2.0.0 + * @implNote not implemented in tinyb.dbus */ - public boolean removeCharacteristicListener(final GATTCharacteristicListener l); + public boolean removeCharacteristicListener(final GATTCharacteristicListener l, final boolean disableIndicationNotification); /** - * Remove all {@link GATTCharacteristicListener} from the list. + * Disables the notification and/or indication for this characteristic BLE level + * if {@code disableIndicationNotification == true} + * and removes all {@link GATTCharacteristicListener} from the listener list, + * which are associated with this characteristic instance. + * <p> + * Implementation tests all listener's {@link GATTCharacteristicListener#getAssociatedCharacteristic()} + * to match with this characteristic instance. + * </p> + * <p> + * If the DBTDevice's GATTHandler is null, i.e. not connected, {@code false} is being returned. + * </p> + * + * @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic + * using {@link #configNotificationIndication(boolean, boolean, boolean[]) * @return number of removed listener. + * @see #configNotificationIndication(boolean, boolean, boolean[]) + * @see BluetoothDevice#removeAllAssociatedCharacteristicListener(BluetoothGattCharacteristic) * @since 2.0.0 + * @implNote not implemented in tinyb.dbus */ - public int removeAllCharacteristicListener(); + public int removeAllAssociatedCharacteristicListener(final boolean disableIndicationNotification); /** - * Enables notifications for the value and calls run function of the BluetoothNotification - * object. It enables notifications for this characteristic at BLE level. + * Sets the given value BluetoothNotification to have its run function + * receive the enabled notification and/or indication sent. + * <p> + * Enables notification and/or indication for this characteristic at BLE level. + * </p. * @param callback A BluetoothNotification<byte[]> object. Its run function will be called * when a notification is issued. The run function will deliver the new value of the value * property. @@ -104,7 +185,7 @@ public interface BluetoothGattCharacteristic extends BluetoothObject /** * Disables notifications of the value and unregisters the callback object - * passed through the corresponding enable method. It disables notications + * passed through the corresponding enable method. It disables notifcations * at BLE level for this characteristic. */ public void disableValueNotifications(); diff --git a/java/org/tinyb/BluetoothGattService.java b/java/org/tinyb/BluetoothGattService.java index 797a2e82..52f766ea 100644 --- a/java/org/tinyb/BluetoothGattService.java +++ b/java/org/tinyb/BluetoothGattService.java @@ -89,18 +89,30 @@ public interface BluetoothGattService extends BluetoothObject /** * Adds the given {@link GATTCharacteristicListener} to the {@link BluetoothDevice} for all {@link BluetoothGattCharacteristic}s. * @param listener {@link GATTCharacteristicListener} to add to the {@link BluetoothDevice}. + * It is important to have hte listener's {@link GATTCharacteristicListener#getAssociatedCharacteristic() associated characteristic} == null, + * otherwise the listener can't be used for all characteristics. * @return true if successful, otherwise false + * @throws IllegalArgumentException if listener's {@link GATTCharacteristicListener#getAssociatedCharacteristic() associated characteristic} + * is not null. * @since 2.0.0 * @implNote not implemented in tinyb.dbus + * @see BluetoothGattCharacteristic#configNotificationIndication(boolean, boolean, boolean[]) + * @see BluetoothDevice#addCharacteristicListener(GATTCharacteristicListener, BluetoothGattCharacteristic) */ - public static boolean addCharacteristicListenerToAll(final BluetoothDevice device, final List<BluetoothGattService> services, final GATTCharacteristicListener listener) { - final boolean res = device.addCharacteristicListener(listener, null /* for all */); + public static boolean addCharacteristicListenerToAll(final BluetoothDevice device, final List<BluetoothGattService> services, + final GATTCharacteristicListener listener) { + if( null == listener ) { + throw new IllegalArgumentException("listener argument null"); + } + if( null != listener.getAssociatedCharacteristic() ) { + throw new IllegalArgumentException("listener's associated characteristic is not null"); + } + final boolean res = device.addCharacteristicListener(listener); for(final Iterator<BluetoothGattService> is = services.iterator(); is.hasNext(); ) { final BluetoothGattService s = is.next(); final List<BluetoothGattCharacteristic> characteristics = s.getCharacteristics(); for(final Iterator<BluetoothGattCharacteristic> ic = characteristics.iterator(); ic.hasNext(); ) { - final BluetoothGattCharacteristic c = ic.next(); - c.enableValueNotifications(null); + ic.next().configNotificationIndication(true /* enableNotification */, true /* enableIndication */, new boolean[2]); } } return res; @@ -112,14 +124,16 @@ public interface BluetoothGattService extends BluetoothObject * @return true if successful, otherwise false * @since 2.0.0 * @implNote not implemented in tinyb.dbus + * @see BluetoothGattCharacteristic#configNotificationIndication(boolean, boolean, boolean[]) + * @see BluetoothDevice#removeCharacteristicListener(GATTCharacteristicListener) */ - public static boolean removeCharacteristicListenerFromAll(final BluetoothDevice device, final List<BluetoothGattService> services, final GATTCharacteristicListener listener) { + public static boolean removeCharacteristicListenerFromAll(final BluetoothDevice device, final List<BluetoothGattService> services, + final GATTCharacteristicListener listener) { for(final Iterator<BluetoothGattService> is = services.iterator(); is.hasNext(); ) { final BluetoothGattService s = is.next(); final List<BluetoothGattCharacteristic> characteristics = s.getCharacteristics(); for(final Iterator<BluetoothGattCharacteristic> ic = characteristics.iterator(); ic.hasNext(); ) { - final BluetoothGattCharacteristic c = ic.next(); - c.disableValueNotifications(); + ic.next().configNotificationIndication(false /* enableNotification */, false /* enableIndication */, new boolean[2]); } } return device.removeCharacteristicListener(listener); @@ -130,18 +144,17 @@ public interface BluetoothGattService extends BluetoothObject * @return count of removed {@link GATTCharacteristicListener} * @since 2.0.0 * @implNote not implemented in tinyb.dbus + * @see BluetoothGattCharacteristic#configNotificationIndication(boolean, boolean, boolean[]) + * @see BluetoothDevice#removeAllCharacteristicListener() */ public static int removeAllCharacteristicListener(final BluetoothDevice device, final List<BluetoothGattService> services) { for(final Iterator<BluetoothGattService> is = services.iterator(); is.hasNext(); ) { final BluetoothGattService s = is.next(); final List<BluetoothGattCharacteristic> characteristics = s.getCharacteristics(); for(final Iterator<BluetoothGattCharacteristic> ic = characteristics.iterator(); ic.hasNext(); ) { - final BluetoothGattCharacteristic c = ic.next(); - c.disableValueNotifications(); + ic.next().configNotificationIndication(false /* enableNotification */, false /* enableIndication */, new boolean[2]); } } return device.removeAllCharacteristicListener(); } - - } diff --git a/java/org/tinyb/GATTCharacteristicListener.java b/java/org/tinyb/GATTCharacteristicListener.java index 693b5532..f47e17d3 100644 --- a/java/org/tinyb/GATTCharacteristicListener.java +++ b/java/org/tinyb/GATTCharacteristicListener.java @@ -25,6 +25,8 @@ package org.tinyb; +import java.lang.ref.WeakReference; + /** * {@link BluetoothGattCharacteristic} event listener for notification and indication events. * <p> @@ -55,14 +57,59 @@ package org.tinyb; public abstract class GATTCharacteristicListener { @SuppressWarnings("unused") private long nativeInstance; + private final WeakReference<BluetoothGattCharacteristic> associatedCharacteristic; + + /** + * Returns the weakly associated {@link BluetoothGattCharacteristic} to this listener instance. + * <p> + * Returns {@code null} if no association has been made + * or if the associated {@link BluetoothGattCharacteristic} has been garbage collected. + * </p> + */ + public final BluetoothGattCharacteristic getAssociatedCharacteristic() { + return null != associatedCharacteristic ? associatedCharacteristic.get() : null; + } + /** + * @param associatedCharacteristic weakly associates this listener instance to one {@link BluetoothGattCharacteristic}, + * may be {@code null} for no association. + * @see #getAssociatedCharacteristic() + */ + public GATTCharacteristicListener(final BluetoothGattCharacteristic associatedCharacteristic) { + if( null != associatedCharacteristic ) { + this.associatedCharacteristic = new WeakReference<BluetoothGattCharacteristic>(associatedCharacteristic); + } else { + this.associatedCharacteristic = null; + } + } + + /** + * Called from native BLE stack, initiated by a received notification associated + * with the given {@link BluetoothGattCharacteristic}. + * @param charDecl {@link BluetoothGattCharacteristic} related to this notification + * @param value the notification value + * @param timestamp the indication monotonic timestamp, see {@link BluetoothUtils#getCurrentMilliseconds()} + */ public void notificationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp) { } + /** + * Called from native BLE stack, initiated by a received indication associated + * with the given {@link BluetoothGattCharacteristic}. + * @param charDecl {@link BluetoothGattCharacteristic} related to this indication + * @param value the indication value + * @param timestamp the indication monotonic timestamp, see {@link BluetoothUtils#getCurrentMilliseconds()} + * @param confirmationSent if true, the native stack has sent the confirmation, otherwise user is required to do so. + */ public void indicationReceived(final BluetoothGattCharacteristic charDecl, final byte[] value, final long timestamp, final boolean confirmationSent) { } + public String toString() { + final BluetoothGattCharacteristic c = getAssociatedCharacteristic(); + final String cs = null != c ? c.toString() : "null"; + return "GATTCharacteristicListener[associated "+cs+"]"; + } }; diff --git a/java/tinyb/dbus/DBusDevice.java b/java/tinyb/dbus/DBusDevice.java index 023607ed..5bbb8f7d 100644 --- a/java/tinyb/dbus/DBusDevice.java +++ b/java/tinyb/dbus/DBusDevice.java @@ -233,7 +233,7 @@ public class DBusDevice extends DBusObject implements BluetoothDevice public native void disableServicesResolvedNotifications(); @Override - public boolean addCharacteristicListener(final GATTCharacteristicListener listener, final BluetoothGattCharacteristic characteristicMatch) { + public boolean addCharacteristicListener(final GATTCharacteristicListener listener) { return false; // FIXME } @@ -243,6 +243,11 @@ public class DBusDevice extends DBusObject implements BluetoothDevice } @Override + public int removeAllAssociatedCharacteristicListener(final BluetoothGattCharacteristic associatedCharacteristic) { + return 0; // FIXME + } + + @Override public int removeAllCharacteristicListener() { return 0; // FIXME } diff --git a/java/tinyb/dbus/DBusGattCharacteristic.java b/java/tinyb/dbus/DBusGattCharacteristic.java index 719d4369..2cf874bc 100644 --- a/java/tinyb/dbus/DBusGattCharacteristic.java +++ b/java/tinyb/dbus/DBusGattCharacteristic.java @@ -102,16 +102,30 @@ public class DBusGattCharacteristic extends DBusObject implements BluetoothGattC { super(instance); } + @Override public boolean addCharacteristicListener(final GATTCharacteristicListener listener) { return false; // FIXME } @Override - public boolean removeCharacteristicListener(final GATTCharacteristicListener l) { + public boolean configNotificationIndication(final boolean enableNotification, final boolean enableIndication, final boolean[] enabledState) + throws IllegalStateException + { + return false; // FIXME + } + + @Override + public boolean addCharacteristicListener(final GATTCharacteristicListener listener, final boolean[] enabledState) + throws IllegalStateException + { + return false; // FIXME + } + @Override + public boolean removeCharacteristicListener(final GATTCharacteristicListener l, final boolean disableIndicationNotification) { return false; // FIXME } @Override - public int removeAllCharacteristicListener() { + public int removeAllAssociatedCharacteristicListener(final boolean disableIndicationNotification) { return 0; // FIXME } diff --git a/src/direct_bt/DBTDevice.cpp b/src/direct_bt/DBTDevice.cpp index 4a7fafee..bdcb4fd3 100644 --- a/src/direct_bt/DBTDevice.cpp +++ b/src/direct_bt/DBTDevice.cpp @@ -577,3 +577,42 @@ void DBTDevice::disconnectGATT() { } INFO_PRINT("DBTDevice::disconnectGATT: End"); } + +bool DBTDevice::addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l) { + std::shared_ptr<GATTHandler> gatt = getGATTHandler(); + if( nullptr == gatt ) { + throw IllegalStateException("Device's GATTHandle not connected: "+ + toString() + ", " + toString(), E_FILE_LINE); + } + return gatt->addCharacteristicListener(l); +} + +bool DBTDevice::removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l) { + std::shared_ptr<GATTHandler> gatt = getGATTHandler(); + if( nullptr == gatt ) { + // OK to have GATTHandler being shutdown @ disable + DBG_PRINT("Device's GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); + return false; + } + return gatt->removeCharacteristicListener(l); +} + +int DBTDevice::removeAllAssociatedCharacteristicListener(std::shared_ptr<GATTCharacteristic> associatedCharacteristic) { + std::shared_ptr<GATTHandler> gatt = getGATTHandler(); + if( nullptr == gatt ) { + // OK to have GATTHandler being shutdown @ disable + DBG_PRINT("Device's GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); + return false; + } + return gatt->removeAllAssociatedCharacteristicListener( associatedCharacteristic ); +} + +int DBTDevice::removeAllCharacteristicListener() { + std::shared_ptr<GATTHandler> gatt = getGATTHandler(); + if( nullptr == gatt ) { + // OK to have GATTHandler being shutdown @ disable + DBG_PRINT("Device's GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); + return 0; + } + return gatt->removeAllCharacteristicListener(); +} diff --git a/src/direct_bt/GATTCharacteristic.cpp b/src/direct_bt/GATTCharacteristic.cpp index c6de3092..b36091b4 100644 --- a/src/direct_bt/GATTCharacteristic.cpp +++ b/src/direct_bt/GATTCharacteristic.cpp @@ -144,11 +144,16 @@ std::string GATTCharacteristic::toString() const { service_name+" ]"; } -bool GATTCharacteristic::configIndicationNotification(const bool enableNotification, const bool enableIndication, bool enableResult[2]) { +bool GATTCharacteristic::configNotificationIndication(const bool enableNotification, const bool enableIndication, bool enabledState[2]) { + enabledState[0] = false; + enabledState[1] = false; + const bool hasEnableNotification = hasProperties(GATTCharacteristic::PropertyBitVal::Notify); const bool hasEnableIndication = hasProperties(GATTCharacteristic::PropertyBitVal::Indicate); - const bool resEnableNotification = hasEnableNotification & enableNotification; - const bool resEnableIndication = hasEnableIndication & enableIndication; + if( !hasEnableNotification && !hasEnableIndication ) { + DBG_PRINT("Characteristic has neither Notify nor Indicate property present: %s", toString().c_str()); + return false; + } std::shared_ptr<DBTDevice> device = getDevice(); std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); @@ -161,20 +166,20 @@ bool GATTCharacteristic::configIndicationNotification(const bool enableNotificat throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ toString() + ", " + device->toString(), E_FILE_LINE); } + const bool resEnableNotification = hasEnableNotification & enableNotification; + const bool resEnableIndication = hasEnableIndication & enableIndication; + GATTDescriptorRef cccd = this->getClientCharacteristicConfig(); if( nullptr == cccd ) { DBG_PRINT("Characteristic has no ClientCharacteristicConfig descriptor: %s", toString().c_str()); return false; } - bool res = gatt->configIndicationNotification(*cccd, resEnableNotification, resEnableIndication); + bool res = gatt->configNotificationIndication(*cccd, resEnableNotification, resEnableIndication); if( res ) { - enableResult[0] = resEnableNotification; - enableResult[1] = resEnableIndication; - } else { - enableResult[0] = false; - enableResult[1] = false; + enabledState[0] = resEnableNotification; + enabledState[1] = resEnableIndication; } - DBG_PRINT("GATTCharacteristic::configIndicationNotification: res %d, notification[shall %d, has %d = %d], indication[shall %s, has %d = %d]", + DBG_PRINT("GATTCharacteristic::configNotificationIndication: res %d, notification[shall %d, has %d = %d], indication[shall %s, has %d = %d]", res, enableNotification. hasEnableNotification, resEnableNotification, enableIndication. hasEnableIndication, resEnableIndication); @@ -182,46 +187,30 @@ bool GATTCharacteristic::configIndicationNotification(const bool enableNotificat } bool GATTCharacteristic::addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l) { - std::shared_ptr<DBTDevice> device = getDevice(); - std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); - if( nullptr == gatt ) { - throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ - toString() + ", " + device->toString(), E_FILE_LINE); - } - return gatt->addCharacteristicListener(l); + return getDevice()->addCharacteristicListener(l); } -bool GATTCharacteristic::removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l) { - std::shared_ptr<DBTDevice> device = getDevice(); - std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); - if( nullptr == gatt ) { - // OK to have GATTHandler being shutdown @ disable - DBG_PRINT("Characteristic's device GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); +bool GATTCharacteristic::addCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l, bool enabledState[2]) { + if( !configNotificationIndication(true, true, enabledState) ) { return false; } - return gatt->removeCharacteristicListener(l); + return addCharacteristicListener(l); } -bool GATTCharacteristic::removeCharacteristicListener(const GATTCharacteristicListener * l) { - std::shared_ptr<DBTDevice> device = getDevice(); - std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); - if( nullptr == gatt ) { - // OK to have GATTHandler being shutdown @ disable - DBG_PRINT("Characteristic's device GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); - return false; +bool GATTCharacteristic::removeCharacteristicListener(std::shared_ptr<GATTCharacteristicListener> l, bool disableIndicationNotification) { + if( disableIndicationNotification ) { + bool enabledState[2]; + configNotificationIndication(false, false, enabledState); } - return gatt->removeCharacteristicListener(l); + return getDevice()->removeCharacteristicListener(l); } -int GATTCharacteristic::removeAllCharacteristicListener() { - std::shared_ptr<DBTDevice> device = getDevice(); - std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); - if( nullptr == gatt ) { - // OK to have GATTHandler being shutdown @ disable - DBG_PRINT("Characteristic's device GATTHandle not connected: %s, %s", toString().c_str(), device->toString().c_str()); - return 0; +int GATTCharacteristic::removeAllCharacteristicListener(bool disableIndicationNotification) { + if( disableIndicationNotification ) { + bool enabledState[2]; + configNotificationIndication(false, false, enabledState); } - return gatt->removeAllCharacteristicListener(); + return getDevice()->removeAllCharacteristicListener(); } bool GATTCharacteristic::readValue(POctets & res, int expectedLength) { diff --git a/src/direct_bt/GATTHandler.cpp b/src/direct_bt/GATTHandler.cpp index c75408df..5ba74d1e 100644 --- a/src/direct_bt/GATTHandler.cpp +++ b/src/direct_bt/GATTHandler.cpp @@ -91,14 +91,14 @@ bool GATTHandler::addCharacteristicListener(std::shared_ptr<GATTCharacteristicLi throw IllegalArgumentException("GATTEventListener ref is null", E_FILE_LINE); } const std::lock_guard<std::recursive_mutex> lock(mtx_eventListenerList); // RAII-style acquire and relinquish via destructor - for(auto it = eventListenerList.begin(); it != eventListenerList.end(); ) { + for(auto it = characteristicListenerList.begin(); it != characteristicListenerList.end(); ) { if ( **it == *l ) { return false; // already included } else { ++it; } } - eventListenerList.push_back(l); + characteristicListenerList.push_back(l); return true; } @@ -106,10 +106,17 @@ bool GATTHandler::removeCharacteristicListener(std::shared_ptr<GATTCharacteristi if( nullptr == l ) { throw IllegalArgumentException("GATTEventListener ref is null", E_FILE_LINE); } + return removeCharacteristicListener( l.get() ); +} + +bool GATTHandler::removeCharacteristicListener(const GATTCharacteristicListener * l) { + if( nullptr == l ) { + throw IllegalArgumentException("GATTEventListener ref is null", E_FILE_LINE); + } const std::lock_guard<std::recursive_mutex> lock(mtx_eventListenerList); // RAII-style acquire and relinquish via destructor - for(auto it = eventListenerList.begin(); it != eventListenerList.end(); ) { + for(auto it = characteristicListenerList.begin(); it != characteristicListenerList.end(); ) { if ( **it == *l ) { - it = eventListenerList.erase(it); + it = characteristicListenerList.erase(it); return true; } else { ++it; @@ -118,14 +125,21 @@ bool GATTHandler::removeCharacteristicListener(std::shared_ptr<GATTCharacteristi return false; } -bool GATTHandler::removeCharacteristicListener(const GATTCharacteristicListener * l) { - if( nullptr == l ) { - throw IllegalArgumentException("GATTEventListener ref is null", E_FILE_LINE); +int GATTHandler::removeAllAssociatedCharacteristicListener(std::shared_ptr<GATTCharacteristic> associatedCharacteristic) { + if( nullptr == associatedCharacteristic ) { + throw IllegalArgumentException("GATTCharacteristic ref is null", E_FILE_LINE); + } + return removeAllAssociatedCharacteristicListener( associatedCharacteristic.get() ); +} + +int GATTHandler::removeAllAssociatedCharacteristicListener(const GATTCharacteristic * associatedCharacteristic) { + if( nullptr == associatedCharacteristic ) { + throw IllegalArgumentException("GATTCharacteristic ref is null", E_FILE_LINE); } const std::lock_guard<std::recursive_mutex> lock(mtx_eventListenerList); // RAII-style acquire and relinquish via destructor - for(auto it = eventListenerList.begin(); it != eventListenerList.end(); ) { - if ( **it == *l ) { - it = eventListenerList.erase(it); + for(auto it = characteristicListenerList.begin(); it != characteristicListenerList.end(); ) { + if ( (*it)->match(*associatedCharacteristic) ) { + it = characteristicListenerList.erase(it); return true; } else { ++it; @@ -136,8 +150,8 @@ bool GATTHandler::removeCharacteristicListener(const GATTCharacteristicListener int GATTHandler::removeAllCharacteristicListener() { const std::lock_guard<std::recursive_mutex> lock(mtx_eventListenerList); // RAII-style acquire and relinquish via destructor - int count = eventListenerList.size(); - eventListenerList.clear(); + int count = characteristicListenerList.size(); + characteristicListenerList.clear(); return count; } @@ -176,19 +190,19 @@ void GATTHandler::l2capReaderThreadImpl() { if( AttPDUMsg::Opcode::ATT_HANDLE_VALUE_NTF == opc ) { const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU); - DBG_PRINT("GATTHandler: NTF: %s", a->toString().c_str()); + DBG_PRINT("GATTHandler: NTF: %s, listener %zd", a->toString().c_str(), characteristicListenerList.size()); GATTCharacteristicRef decl = findCharacterisicsByValueHandle(a->getHandle()); const std::shared_ptr<TROOctets> data(new POctets(a->getValue())); const uint64_t timestamp = a->ts_creation; int i=0; - for_each_idx_mtx(mtx_eventListenerList, eventListenerList, [&](std::shared_ptr<GATTCharacteristicListener> &l) { + for_each_idx_mtx(mtx_eventListenerList, characteristicListenerList, [&](std::shared_ptr<GATTCharacteristicListener> &l) { try { if( l->match(*decl) ) { l->notificationReceived(decl, data, timestamp); } } catch (std::exception &e) { ERR_PRINT("GATTHandler::notificationReceived-CBs %d/%zd: GATTCharacteristicListener %s: Caught exception %s", - i+1, eventListenerList.size(), + i+1, characteristicListenerList.size(), aptrHexString((void*)l.get()).c_str(), e.what()); } i++; @@ -196,7 +210,8 @@ void GATTHandler::l2capReaderThreadImpl() { attPDU = nullptr; } else if( AttPDUMsg::Opcode::ATT_HANDLE_VALUE_IND == opc ) { const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU); - DBG_PRINT("GATTHandler: IND: %s, sendIndicationConfirmation %d", a->toString().c_str(), sendIndicationConfirmation); + DBG_PRINT("GATTHandler: IND: %s, sendIndicationConfirmation %d, listener %zd", a->toString().c_str(), + sendIndicationConfirmation, characteristicListenerList.size()); bool cfmSent = false; if( sendIndicationConfirmation ) { AttHandleValueCfm cfm; @@ -208,14 +223,14 @@ void GATTHandler::l2capReaderThreadImpl() { const std::shared_ptr<TROOctets> data(new POctets(a->getValue())); const uint64_t timestamp = a->ts_creation; int i=0; - for_each_idx_mtx(mtx_eventListenerList, eventListenerList, [&](std::shared_ptr<GATTCharacteristicListener> &l) { + for_each_idx_mtx(mtx_eventListenerList, characteristicListenerList, [&](std::shared_ptr<GATTCharacteristicListener> &l) { try { if( l->match(*decl) ) { l->indicationReceived(decl, data, timestamp, cfmSent); } } catch (std::exception &e) { ERR_PRINT("GATTHandler::indicationReceived-CBs %d/%zd: GATTCharacteristicListener %s, cfmSent %d: Caught exception %s", - i+1, eventListenerList.size(), + i+1, characteristicListenerList.size(), aptrHexString((void*)l.get()).c_str(), cfmSent, e.what()); } i++; @@ -254,7 +269,6 @@ GATTHandler::GATTHandler(const std::shared_ptr<DBTDevice> &device, const int rep { } GATTHandler::~GATTHandler() { - eventListenerList.clear(); disconnect(false /* disconnectDevice */, false /* ioErrorCause */); services.clear(); } @@ -313,6 +327,7 @@ bool GATTHandler::disconnect(const bool disconnectDevice, const bool ioErrorCaus DBG_PRINT("GATTHandler::disconnect: Not connected: disconnectDevice %d, ioErrorCause %d: GattHandler[%s], l2cap[%s]: %s", disconnectDevice, ioErrorCause, getStateString().c_str(), l2cap.getStateString().c_str(), deviceString.c_str()); l2cap.disconnect(); // interrupt GATT's L2CAP ::connect(..), avoiding prolonged hang + characteristicListenerList.clear(); return false; } hasIOError = false; @@ -336,6 +351,8 @@ bool GATTHandler::disconnect(const bool disconnectDevice, const bool ioErrorCaus } } } + removeAllCharacteristicListener(); + std::shared_ptr<DBTDevice> device = getDevice(); if( disconnectDevice && nullptr != device ) { @@ -836,13 +853,13 @@ bool GATTHandler::writeValue(const uint16_t handle, const TROOctets & value, con return res; } -bool GATTHandler::configIndicationNotification(GATTDescriptor & cccd, const bool enableNotification, const bool enableIndication) { +bool GATTHandler::configNotificationIndication(GATTDescriptor & cccd, const bool enableNotification, const bool enableIndication) { if( !cccd.isClientCharacteristicConfiguration() ) { throw IllegalArgumentException("Not a ClientCharacteristicConfiguration: "+cccd.toString(), E_FILE_LINE); } /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */ const uint16_t ccc_value = enableNotification | ( enableIndication << 1 ); - DBG_PRINT("GATTHandler::configIndicationNotification decl %s, enableNotification %d, enableIndication %d", + DBG_PRINT("GATTHandler::configNotificationIndication decl %s, enableNotification %d, enableIndication %d", cccd.toString().c_str(), enableNotification, enableIndication); cccd.value.resize(2, 2); cccd.value.put_uint16(0, ccc_value); |