diff options
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); |