diff options
-rw-r--r-- | api/direct_bt/BTAdapter.hpp | 13 | ||||
-rw-r--r-- | api/direct_bt/BTDevice.hpp | 46 | ||||
-rw-r--r-- | api/direct_bt/BTGattHandler.hpp | 172 | ||||
-rw-r--r-- | java/jau/direct_bt/DBTDevice.java | 7 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTDevice.cxx | 7 | ||||
-rw-r--r-- | java/org/direct_bt/AdapterStatusListener.java | 6 | ||||
-rw-r--r-- | java/org/direct_bt/BTDevice.java | 10 | ||||
-rw-r--r-- | src/direct_bt/BTDevice.cpp | 102 | ||||
-rw-r--r-- | src/direct_bt/BTGattHandler.cpp | 99 |
9 files changed, 288 insertions, 174 deletions
diff --git a/api/direct_bt/BTAdapter.hpp b/api/direct_bt/BTAdapter.hpp index fcfa64ac..b389b940 100644 --- a/api/direct_bt/BTAdapter.hpp +++ b/api/direct_bt/BTAdapter.hpp @@ -245,13 +245,15 @@ namespace direct_bt { /** * Remote BTDevice is ready for user (GATT) processing, i.e. already connected, optionally (SMP) paired. * - * In case of a LE connection to a remote BTDevice in BTRole::Slave, a GATT server (GATTRole::Server), GATT MTU size is negotiated and GATT services discovered. + * In case of a LE connection to a remote BTDevice in BTRole::Slave, a GATT server (GATTRole::Server), + * user needs to call {@link BTDevice#getGattServices()} to have GATT MTU size negotiated and GATT services discovered. * <p> * Method is being called from a dedicated native thread, hence restrictions on method duration and complex mutable operations don't apply here. * </p> * @param device the remote device ready to use * @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds(). * @see ::SMPPairingState::COMPLETED + * @see BTDevice::getGattServices() */ virtual void deviceReady(BTDeviceRef device, const uint64_t timestamp) { (void)device; @@ -459,6 +461,7 @@ namespace direct_bt { friend void BTDevice::hciSMPMsgCallback(BTDeviceRef sthis, const SMPPDUMsg& msg, const HCIACLData::l2cap_frame& source) noexcept; friend void BTDevice::processDeviceReady(BTDeviceRef sthis, const uint64_t timestamp); friend bool BTDevice::connectGATT(std::shared_ptr<BTDevice> sthis) noexcept; + friend jau::darray<BTGattServiceRef> BTDevice::getGattServices() noexcept; bool lockConnect(const BTDevice & device, const bool wait, const SMPIOCapability io_cap) noexcept; bool unlockConnect(const BTDevice & device) noexcept; @@ -1305,6 +1308,14 @@ namespace direct_bt { void printStatusListenerList() noexcept; }; + inline bool operator==(const BTAdapter& lhs, const BTAdapter& rhs) noexcept + { return lhs.getAddressAndType() == rhs.getAddressAndType(); } + + inline bool operator!=(const BTAdapter& lhs, const BTAdapter& rhs) noexcept + { return !(lhs == rhs); } + + typedef std::shared_ptr<BTAdapter> BTAdapterRef; + } // namespace direct_bt #endif /* BT_ADAPTER_HPP_ */ diff --git a/api/direct_bt/BTDevice.hpp b/api/direct_bt/BTDevice.hpp index ea6a79e5..6699661d 100644 --- a/api/direct_bt/BTDevice.hpp +++ b/api/direct_bt/BTDevice.hpp @@ -96,7 +96,7 @@ namespace direct_bt { std::shared_ptr<BTGattHandler> gattHandler = nullptr; mutable std::recursive_mutex mtx_gattHandler; mutable std::recursive_mutex mtx_connect; - mutable std::mutex mtx_data; + mutable std::mutex mtx_eir; jau::relaxed_atomic_bool isConnected; jau::relaxed_atomic_bool allowDisconnect; // allowDisconnect = isConnected || 'isConnectIssued' jau::relaxed_atomic_int32 supervision_timeout; // [ms] @@ -1006,28 +1006,45 @@ namespace direct_bt { */ void remove() noexcept; - /** Returns the connected GATTHandler or nullptr, see connectGATT(), getGattService() and disconnect(). */ + /** + * Returns the connected GATTHandler or nullptr, see connectGATT(), getGattServices() and disconnect(). + * + * @return + * @see connectGATT() + * @see getGattServices() + * @see disconnect() + */ std::shared_ptr<BTGattHandler> getGattHandler() noexcept; /** * Returns a list of shared GATTService available on this device if successful, * otherwise returns an empty list if an error occurred. - * <p> + * + * Method is only functional on a remote BTDevice in BTRole::Slave, a GATT server (GATTRole::Server), + * i.e. the local BTAdapter acting as a BTRole::Master GATT client. + * * The HCI connectLE(..) or connectBREDR(..) must be performed first, see {@link #connectDefault()}. - * </p> - * <p> - * If this method has been called for the first time or no services has been detected yet, - * a list of GATTService will be retrieved. - * <br> + * + * If this method has been called for the first time or no services have been detected yet: + * - the client MTU exchange will be performed + * - a list of GATTService will be retrieved + * * A GATT connection will be created via connectGATT() if not established yet. - * </p> */ jau::darray<BTGattServiceRef> getGattServices() noexcept; /** + * Returns the shared GenericAccess instance, retrieved by getGattServices() or nullptr if not available. + * + * @return + * @see getGattServices() + */ + std::shared_ptr<GattGenericAccessSvc> getGattGenericAccess(); + + /** * Find a BTGattService by its service_uuid. * - * It will check objects of a connected device using getGattService(). + * It will check objects of a connected device using getGattServices(). * * It will not turn on discovery or connect to this remote device. * @@ -1040,7 +1057,7 @@ namespace direct_bt { /** * Find a BTGattChar by its service_uuid and char_uuid. * - * It will check objects of this connected device using getGattService(). + * It will check objects of this connected device using getGattServices(). * * It will not turn on discovery or connect to this remote device. * @@ -1055,7 +1072,7 @@ namespace direct_bt { /** * Find a BTGattChar by its char_uuid only. * - * It will check objects of this connected device using getGattService(). + * It will check objects of this connected device using getGattServices(). * * It will not turn on discovery or connect to this remote device. * @@ -1069,9 +1086,6 @@ namespace direct_bt { */ BTGattCharRef findGattChar(const jau::uuid_t& char_uuid) noexcept; - /** Returns the shared GenericAccess instance, retrieved by {@link #getGattService()} or nullptr if not available. */ - std::shared_ptr<GattGenericAccessSvc> getGattGenericAccess(); - /** * Send a notification event consisting out of the given `value` representing the given characteristic value handle * to the connected BTRole::Master. @@ -1111,7 +1125,7 @@ namespace direct_bt { * A disconnect will finally being issued. * </p> * <p> - * GATT services must have been initialized via {@link #getGattService()}, otherwise `false` is being returned. + * GATT services must have been initialized via getGattServices(), otherwise `false` is being returned. * </p> * @return `true` if successful, otherwise false in case no GATT services exists or is not connected .. etc. */ diff --git a/api/direct_bt/BTGattHandler.hpp b/api/direct_bt/BTGattHandler.hpp index 3defeecf..483b73af 100644 --- a/api/direct_bt/BTGattHandler.hpp +++ b/api/direct_bt/BTGattHandler.hpp @@ -196,6 +196,10 @@ namespace direct_bt { jau::service_runner l2cap_reader_service; jau::ringbuffer<std::unique_ptr<const AttPDUMsg>, jau::nsize_t> attPDURing; + jau::relaxed_atomic_uint16 serverMTU; // set in initClientGatt() + jau::relaxed_atomic_uint16 usedMTU; // concurrent use in initClientGatt(set), send and l2capReaderThreadImpl + jau::relaxed_atomic_bool clientMTUExchanged; // set in initClientGatt() + /** send immediate confirmation of indication events from device, defaults to true. */ jau::relaxed_atomic_bool sendIndicationConfirmation = true; typedef jau::cow_darray<std::shared_ptr<BTGattCharListener>> characteristicListenerList_t; @@ -206,8 +210,6 @@ namespace direct_bt { jau::darray<AttPrepWrite> writeDataQueue; jau::darray<uint16_t> writeDataQueueHandles; - uint16_t serverMTU; - std::atomic<uint16_t> usedMTU; // concurrent use in ctor(set), send and l2capReaderThreadImpl jau::darray<BTGattServiceRef> services; std::shared_ptr<GattGenericAccessSvc> genericAccess = nullptr; @@ -231,55 +233,64 @@ namespace direct_bt { void l2capReaderEndFinal(jau::service_runner& sr) noexcept; /** - * Sends the given AttPDUMsg to the connected device via l2cap. + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4.2 MTU Exchange * - * Implementation throws an IllegalStateException if not connected, - * a IllegalArgumentException if message size exceeds usedMTU-1. + * Returns the server-mtu if successful, otherwise 0. * - * ATT_MTU range - * - ATT_MTU minimum is 23 bytes (Vol 3, Part G: 5.2.1) - * - ATT_MTU is negotiated, maximum attribute value length is 512 bytes (Vol 3, Part F: 3.2.8-9) - * - ATT Value sent: [1 .. ATT_MTU-1] (Vol 3, Part F: 3.2.8-9) + * Method usually called via initClientGatt() and is only exposed special applications. * - * Implementation disconnect() and throws an BluetoothException - * if an l2cap write errors occurs. + * @see initClientGatt() + */ + uint16_t clientMTUExchange(const int32_t timeout); + + /** + * Discover all primary services _only_. + * - BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services * - * In case method completes, the message has been send out successfully. - * @param msg the message to be send - * @see exchangeMTUImpl() + * @param shared_this shared pointer of this instance, used to forward a weak_ptr to BTGattService for back-reference. Reference is validated. + * @param result vector containing all discovered primary services + * @return true on success, otherwise false + * @see initClientGatt() + * @see discoverCompletePrimaryServices() */ - void send(const AttPDUMsg & msg); + bool discoverPrimaryServices(std::shared_ptr<BTGattHandler> shared_this, jau::darray<BTGattServiceRef> & result); /** - * Sends the given AttPDUMsg to the connected device via l2cap using {@link #send()}. - * <p> - * Implementation waits for timeout milliseconds receiving the response - * from the ringbuffer, filled from the reader-thread. - * </p> - * <p> - * Implementation disconnect() and throws an BluetoothException - * if no matching reply has been received within timeout milliseconds. - * </p> - * <p> - * In case method completes, the message has been send out successfully - * and a reply has also been received and is returned as a result.<br> - * Hence this method either throws an exception or returns a matching reply. - * </p> + * Discover all characteristics of a service and declaration attributes _only_. + * - BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * - BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characterisic Declaration Attribute Value * - * @param msg the message to be send - * @param timeout milliseconds to wait for a reply - * @return a valid reply, never nullptrs + * @see initClientGatt() + * @see discoverCompletePrimaryServices() */ - std::unique_ptr<const AttPDUMsg> sendWithReply(const AttPDUMsg & msg, const int timeout); + bool discoverCharacteristics(BTGattServiceRef & service); /** - * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4.2 MTU Exchange + * Discover all descriptors of a service _only_. + * - BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors * - * Returns the server-mtu if successful, otherwise 0. + * @see initClientGatt() + * @see discoverCompletePrimaryServices() + */ + bool discoverDescriptors(BTGattServiceRef & service); + + /** + * Discover all primary services _and_ all its characteristics declarations + * including their client config. + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * </p> + * Method returns reference to BTGattHandler's internal BTGattService vector of discovered services + * + * Service discovery may consume 500ms - 2000ms, depending on bandwidth. * - * @see send() + * Method usually called via initClientGatt() and is only exposed special applications. + * + * @param shared_this shared pointer of this instance, used to forward a weak_ptr to BTGattService for back-reference. Reference is validated. + * @return BTGattHandler's internal BTGattService vector of discovered services + * @see initClientGatt() */ - uint16_t exchangeMTUImpl(const uint16_t clientMaxMTU, const int32_t timeout); + jau::darray<BTGattServiceRef> & discoverCompletePrimaryServices(std::shared_ptr<BTGattHandler> shared_this); public: /** @@ -345,60 +356,75 @@ namespace direct_bt { BTGattCharRef findCharacterisicsByValueHandle(const BTGattServiceRef service, const uint16_t charValueHandle) noexcept; /** - * Discover all primary services _and_ all its characteristics declarations - * including their client config. - * <p> - * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services - * </p> - * Method returns reference to BTGattHandler's internal BTGattService vector of discovered services + * Initialize the connection and internal data set for GATT client operations: + * - Exchange MTU + * - Discover all primary services, its characteristics and its descriptors * - * @param shared_this shared pointer of this instance, used to forward a weak_ptr to BTGattService for back-reference. Reference is validated. - * @return BTGattHandler's internal BTGattService vector of discovered services + * Service discovery may consume 500ms - 2000ms, depending on bandwidth. + * + * @param shared_this the shared BTGattHandler reference + * @param already_init if already initialized true, will hold true, otherwise false + * @return true if already initialized or newly initialized, otherwise false + * @see clientMTUExchange() + * @see discoverCompletePrimaryServices() */ - jau::darray<BTGattServiceRef> & discoverCompletePrimaryServices(std::shared_ptr<BTGattHandler> shared_this); + bool initClientGatt(std::shared_ptr<BTGattHandler> shared_this, bool& already_init) noexcept; /** * Returns a reference of the internal kept BTGattService list. - * <p> - * The internal list will be populated via {@link #discoverCompletePrimaryServices()}. - * </p> + * + * The internal list should have been populated via initClientGatt() once. + * + * @see initClientGatt() */ inline jau::darray<BTGattServiceRef> & getServices() noexcept { return services; } /** * Returns the internal kept shared GattGenericAccessSvc instance. - * <p> - * This instance is created via {@link #discoverCompletePrimaryServices()}. - * </p> + * + * This instance is created via initClientGatt(). + * + * @see initClientGatt() */ inline std::shared_ptr<GattGenericAccessSvc> getGenericAccess() noexcept { return genericAccess; } /** - * Discover all primary services _only_. - * <p> - * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services - * </p> - * @param shared_this shared pointer of this instance, used to forward a weak_ptr to BTGattService for back-reference. Reference is validated. - * @param result vector containing all discovered primary services - * @return true on success, otherwise false - */ - bool discoverPrimaryServices(std::shared_ptr<BTGattHandler> shared_this, jau::darray<BTGattServiceRef> & result); - - /** - * Discover all characteristics of a service and declaration attributes _only_. - * <p> - * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service - * </p> - * <p> - * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characterisic Declaration Attribute Value - * </p> + * Sends the given AttPDUMsg to the connected device via l2cap. + * + * Implementation throws an IllegalStateException if not connected, + * a IllegalArgumentException if message size exceeds usedMTU-1. + * + * ATT_MTU range + * - ATT_MTU minimum is 23 bytes (Vol 3, Part G: 5.2.1) + * - ATT_MTU is negotiated, maximum attribute value length is 512 bytes (Vol 3, Part F: 3.2.8-9) + * - ATT Value sent: [1 .. ATT_MTU-1] (Vol 3, Part F: 3.2.8-9) + * + * Implementation disconnect() and throws an BluetoothException + * if an l2cap write errors occurs. + * + * In case method completes, the message has been send out successfully. + * @param msg the message to be send */ - bool discoverCharacteristics(BTGattServiceRef & service); + void send(const AttPDUMsg & msg); /** - * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors + * Sends the given AttPDUMsg to the connected device via l2cap using {@link #send()}. + * + * Implementation waits for timeout milliseconds receiving the response + * from the ringbuffer, filled from the reader-thread. + * + * Implementation disconnect() and throws an BluetoothException + * if no matching reply has been received within timeout milliseconds. + * + * In case method completes, the message has been send out successfully + * and a reply has also been received and is returned as a result.<br> + * Hence this method either throws an exception or returns a matching reply. + * + * @param msg the message to be send + * @param timeout milliseconds to wait for a reply + * @return a valid reply, never nullptrs */ - bool discoverDescriptors(BTGattServiceRef & service); + std::unique_ptr<const AttPDUMsg> sendWithReply(const AttPDUMsg & msg, const int timeout); /** * Generic read GATT value and long value diff --git a/java/jau/direct_bt/DBTDevice.java b/java/jau/direct_bt/DBTDevice.java index 0e1b44f6..904110ec 100644 --- a/java/jau/direct_bt/DBTDevice.java +++ b/java/jau/direct_bt/DBTDevice.java @@ -612,7 +612,10 @@ public class DBTDevice extends DBTObject implements BTDevice @Override public List<BTGattService> getGattServices() { try { - final List<BTGattService> services = getGattServicesImpl(); + List<BTGattService> services = getGattServicesImpl(); + if( null == services ) { + services = new ArrayList<BTGattService>(); + } updateServiceCache(services); return services; } catch (final Throwable t) { @@ -621,7 +624,7 @@ public class DBTDevice extends DBTObject implements BTDevice t.printStackTrace(); } } - return null; + return new ArrayList<BTGattService>(); } private native List<BTGattService> getGattServicesImpl(); diff --git a/java/jni/direct_bt/DBTDevice.cxx b/java/jni/direct_bt/DBTDevice.cxx index eb267b1b..82316073 100644 --- a/java/jni/direct_bt/DBTDevice.cxx +++ b/java/jni/direct_bt/DBTDevice.cxx @@ -837,12 +837,7 @@ jobject Java_jau_direct_1bt_DBTDevice_getGattServicesImpl(JNIEnv *env, jobject o JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); jau::darray<BTGattServiceRef> services = device->getGattServices(); // implicit GATT connect and discovery if required incl GenericAccess retrieval - if( services.size() > 0 ) { - std::shared_ptr<GattGenericAccessSvc> ga = device->getGattGenericAccess(); - if( nullptr != ga ) { - DBG_PRINT("BTDevice.getServices(): GenericAccess: %s", ga->toString().c_str()); - } - } else { + if( services.size() == 0 ) { return nullptr; } diff --git a/java/org/direct_bt/AdapterStatusListener.java b/java/org/direct_bt/AdapterStatusListener.java index 8083d1d0..0c3513b4 100644 --- a/java/org/direct_bt/AdapterStatusListener.java +++ b/java/org/direct_bt/AdapterStatusListener.java @@ -147,13 +147,15 @@ public abstract class AdapterStatusListener { /** * Remote {@link BTDevice} is ready for user (GATT) processing, i.e. already connected and optionally (SMP) paired. * - * In case of a LE connection to a remote {@link BTDevice} in {@link BTRole#Slave}, a GATT server, GATT MTU size is negotiated and GATT services discovered. + * In case of a LE connection to a remote {@link BTDevice} in {@link BTRole#Slave}, a GATT server, + * user needs to call {@link BTDevice#getGattServices()} to have GATT MTU size negotiated and GATT services discovered. * <p> * Method is being called from a dedicated native thread, hence restrictions on method duration and complex mutable operations don't apply here. * </p> * @param device the remote device ready to use * @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds(). - * @see {@link SMPPairingState#COMPLETED} + * @see SMPPairingState#COMPLETED + * @see BTDevice#getGattServices() */ public void deviceReady(final BTDevice device, final long timestamp) {} diff --git a/java/org/direct_bt/BTDevice.java b/java/org/direct_bt/BTDevice.java index 4e595d1b..f1218b3a 100644 --- a/java/org/direct_bt/BTDevice.java +++ b/java/org/direct_bt/BTDevice.java @@ -631,11 +631,17 @@ public interface BTDevice extends BTObject * Returns a list of shared BTGattService available on this device if successful, * otherwise returns an empty list if an error occurred. * <p> + * Method is only functional on a remote BTDevice in {@link BTRole#Slave}, a GATT server, + * i.e. the local BTAdapter acting as a {@link BTRole#Master} GATT client. + * </p> + * <p> * The HCI connectLE(..) or connectBREDR(..) must be performed first, see {@link #connectDefault()}. * </p> * <p> - * If this method has been called for the first time or no services has been detected yet, - * a list of GATTService will be retrieved. + * If this method has been called for the first time or no services have been detected yet: + * - the client MTU exchange will be performed + * - a list of GATTService will be retrieved + * </p> * @since 2.4.0 */ List<BTGattService> getGattServices(); diff --git a/src/direct_bt/BTDevice.cpp b/src/direct_bt/BTDevice.cpp index 39e01597..8a212ac9 100644 --- a/src/direct_bt/BTDevice.cpp +++ b/src/direct_bt/BTDevice.cpp @@ -654,7 +654,7 @@ void BTDevice::processDeviceReady(std::shared_ptr<BTDevice> sthis, const uint64_ std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } - const bool gatt_res = connectGATT(sthis); // may close connection and hence clear pairing_data + const bool gatt_res = connectGATT(sthis); if( !gatt_res && using_enc ) { // Need to repair as GATT communication failed @@ -1908,41 +1908,10 @@ bool BTDevice::connectGATT(std::shared_ptr<BTDevice> sthis) noexcept { return false; } else if ( BTRole::Master == btRole ) { DBG_PRINT("BTDevice::connectGATT: Local GATT Server: Done: %s", toString().c_str()); - return true; } else { - DBG_PRINT("BTDevice::connectGATT: Local GATT Client: Service Discovery Start: %s", toString().c_str()); - try { - // Service discovery may consume 500ms - 2000ms, depending on bandwidth - jau::darray<BTGattServiceRef>& gattServices = gattHandler->discoverCompletePrimaryServices(gattHandler); - if( gattServices.size() == 0 ) { // nothing discovered - ERR_PRINT2("No primary services discovered"); - gattHandler = nullptr; - return false; - } - DBG_PRINT("BTDevice::connectGATT: %zu Services Discovered: %s", gattServices.size(), toString().c_str()); - - // discovery success, parse GenericAccess - std::shared_ptr<GattGenericAccessSvc> gattGenericAccess = gattHandler->getGenericAccess(); - if( nullptr != gattGenericAccess ) { - const uint64_t ts = jau::getCurrentMilliseconds(); - EIRDataType updateMask = update(*gattGenericAccess, ts); - DBG_PRINT("BTDevice::connectGATT: GenericAccess updated %s:\n %s\n -> %s", - to_string(updateMask).c_str(), gattGenericAccess->toString().c_str(), toString().c_str()); - if( EIRDataType::NONE != updateMask ) { - adapter.sendDeviceUpdated("connectGATT", sthis, ts, updateMask); - } - } else { - // else: Actually an error w/o valid mandatory GenericAccess - WARN_PRINT("No GenericAccess: %s", toString().c_str()); - } - return true; - } catch (std::exception &e) { - WARN_PRINT("Caught exception: '%s' on %s", e.what(), toString().c_str()); - } + DBG_PRINT("BTDevice::connectGATT: Local GATT Client: Done: %s", toString().c_str()); } - ERR_PRINT2("Failed GATT Service Processing"); - gattHandler = nullptr; - return false; + return true; } std::shared_ptr<BTGattHandler> BTDevice::getGattHandler() noexcept { @@ -1953,10 +1922,62 @@ std::shared_ptr<BTGattHandler> BTDevice::getGattHandler() noexcept { jau::darray<BTGattServiceRef> BTDevice::getGattServices() noexcept { std::shared_ptr<BTGattHandler> gh = getGattHandler(); if( nullptr == gh ) { - ERR_PRINT("GATTHandler nullptr"); + ERR_PRINT("GATTHandler nullptr: %s", toString().c_str()); return jau::darray<std::shared_ptr<BTGattService>>(); } - return gh->getServices(); // copy previous discovery result + if( BTRole::Slave != getRole() ) { + // Remote device is not a slave (peripheral, responder) - hence no GATT services + ERR_PRINT("Remote device not a GATT server: ", toString().c_str()); + return jau::darray<std::shared_ptr<BTGattService>>(); + } + + bool gatt_already_init = false; + const bool gatt_client_init = gh->initClientGatt(gh, gatt_already_init); + jau::darray<BTGattServiceRef>& gattServices = gh->getServices(); + if( !gatt_client_init ) { + ERR_PRINT2("BTDevice::getGattServices: Client GATT Initialization failed"); + return gattServices; // copy previous discovery result (zero sized) + } + if( gatt_already_init ) { + return gattServices; // copy previous discovery result + } + if( gattServices.size() == 0 ) { // nothing discovered + ERR_PRINT2("BTDevice::getGattServices: No primary services discovered"); + return gattServices; + } + try { + // discovery success, parse GenericAccess + std::shared_ptr<GattGenericAccessSvc> gattGenericAccess = gh->getGenericAccess(); + if( nullptr != gattGenericAccess ) { + const uint64_t ts = jau::getCurrentMilliseconds(); + EIRDataType updateMask = update(*gattGenericAccess, ts); + DBG_PRINT("BTDevice::getGattServices: GenericAccess updated %s:\n %s\n -> %s", + to_string(updateMask).c_str(), gattGenericAccess->toString().c_str(), toString().c_str()); + if( EIRDataType::NONE != updateMask ) { + std::shared_ptr<BTDevice> sharedInstance = getSharedInstance(); + if( nullptr == sharedInstance ) { + ERR_PRINT("Device unknown to adapter and not tracked: %s", toString().c_str()); + } else { + adapter.sendDeviceUpdated("getGattServices", sharedInstance, ts, updateMask); + } + } + } else { + // else: Actually an error w/o valid mandatory GenericAccess + WARN_PRINT("No GenericAccess: %s", toString().c_str()); + } + } catch (std::exception &e) { + WARN_PRINT("Caught exception: '%s' on %s", e.what(), toString().c_str()); + } + return gattServices; // return copy +} + +std::shared_ptr<GattGenericAccessSvc> BTDevice::getGattGenericAccess() { + std::shared_ptr<BTGattHandler> gh = getGattHandler(); + if( nullptr == gh ) { + ERR_PRINT("GATTHandler nullptr"); + return nullptr; + } + return gh->getGenericAccess(); } BTGattServiceRef BTDevice::findGattService(const jau::uuid_t& service_uuid) noexcept { @@ -2024,15 +2045,6 @@ bool BTDevice::pingGATT() noexcept { return false; } -std::shared_ptr<GattGenericAccessSvc> BTDevice::getGattGenericAccess() { - std::shared_ptr<BTGattHandler> gh = getGattHandler(); - if( nullptr == gh ) { - ERR_PRINT("GATTHandler nullptr"); - return nullptr; - } - return gh->getGenericAccess(); -} - bool BTDevice::addCharListener(std::shared_ptr<BTGattCharListener> l) { std::shared_ptr<BTGattHandler> gatt = getGattHandler(); if( nullptr == gatt ) { diff --git a/src/direct_bt/BTGattHandler.cpp b/src/direct_bt/BTGattHandler.cpp index 066ea960..c58fe844 100644 --- a/src/direct_bt/BTGattHandler.cpp +++ b/src/direct_bt/BTGattHandler.cpp @@ -1198,8 +1198,8 @@ BTGattHandler::BTGattHandler(const BTDeviceRef &device, L2CAPComm& l2cap_att, co jau::bindMemberFunc(this, &BTGattHandler::l2capReaderEndLocked), jau::bindMemberFunc(this, &BTGattHandler::l2capReaderEndFinal)), attPDURing(env.ATTPDU_RING_CAPACITY), + serverMTU(number(Defaults::MIN_ATT_MTU)), usedMTU(number(Defaults::MIN_ATT_MTU)), clientMTUExchanged(false), gattServerData( GATTRole::Server == role ? device->getAdapter().getGATTServerData() : nullptr ), - serverMTU(number(Defaults::MIN_ATT_MTU)), usedMTU(number(Defaults::MIN_ATT_MTU)) { if( !validateConnected() ) { ERR_PRINT("GATTHandler.ctor: L2CAP could not connect"); @@ -1216,32 +1216,17 @@ BTGattHandler::BTGattHandler(const BTDeviceRef &device, L2CAPComm& l2cap_att, co l2cap_reader_service.start(); if( GATTRole::Client == getRole() ) { - // First point of failure if remote device exposes no GATT functionality. Allow a longer timeout! - const int32_t initial_command_reply_timeout = std::min<int32_t>(10000, std::max<int32_t>(env.GATT_INITIAL_COMMAND_REPLY_TIMEOUT, 2*supervision_timeout)); - uint16_t mtu = 0; - try { - mtu = exchangeMTUImpl(number(Defaults::MAX_ATT_MTU), initial_command_reply_timeout); - } catch (std::exception &e) { - ERR_PRINT2("GattHandler.ctor: exchangeMTU failed: %s", e.what()); - } catch (std::string &msg) { - ERR_PRINT2("GattHandler.ctor: exchangeMTU failed: %s", msg.c_str()); - } catch (const char *msg) { - ERR_PRINT2("GattHandler.ctor: exchangeMTU failed: %s", msg); - } - if( 0 == mtu ) { - ERR_PRINT2("GATTHandler::ctor: Zero serverMTU -> disconnect: %s", toString().c_str()); - disconnect(true /* disconnectDevice */, false /* ioErrorCause */); - } else { - serverMTU = mtu; - usedMTU = std::min(number(Defaults::MAX_ATT_MTU), serverMTU); - } + // MTU to be negotiated via initClientGatt() from this GATT client later + serverMTU = number(Defaults::MAX_ATT_MTU); + usedMTU = number(Defaults::MIN_ATT_MTU); } else { + // MTU to be negotiated via client request on this GATT server if( nullptr != gattServerData ) { serverMTU = std::max( std::min( gattServerData->getMaxAttMTU(), number(Defaults::MAX_ATT_MTU) ), number(Defaults::MIN_ATT_MTU) ); } else { serverMTU = number(Defaults::MAX_ATT_MTU); } - usedMTU = number(Defaults::MIN_ATT_MTU); // until negotiated! + usedMTU = number(Defaults::MIN_ATT_MTU); if( nullptr != gattServerData ) { int i=0; @@ -1316,6 +1301,8 @@ bool BTGattHandler::disconnect(const bool disconnectDevice, const bool ioErrorCa l2cap_reader_service.stop(); PERF3_TS_TD("GATTHandler::disconnect.2"); + clientMTUExchanged = false; + if( disconnectDevice ) { BTDeviceRef device = getDeviceUnchecked(); if( nullptr != device ) { @@ -1378,15 +1365,16 @@ std::unique_ptr<const AttPDUMsg> BTGattHandler::sendWithReply(const AttPDUMsg & return res; } -uint16_t BTGattHandler::exchangeMTUImpl(const uint16_t clientMaxMTU, const int32_t timeout) { +uint16_t BTGattHandler::clientMTUExchange(const int32_t timeout) { + if( GATTRole::Client != getRole() ) { + ERR_PRINT("GATT MTU exchange only allowed in client mode"); + return usedMTU; + } /*** * BT Core Spec v5.2: Vol 3, Part G GATT: 4.3.1 Exchange MTU (Server configuration) */ - if( clientMaxMTU > number(Defaults::MAX_ATT_MTU) ) { - throw jau::IllegalArgumentException("clientMaxMTU "+std::to_string(clientMaxMTU)+" > ClientMaxMTU "+std::to_string(number(Defaults::MAX_ATT_MTU)), E_FILE_LINE); - } - const AttExchangeMTU req(AttPDUMsg::ReqRespType::REQUEST, clientMaxMTU); - // called by ctor only, no locking: const std::lock_guard<std::recursive_mutex> lock(mtx_command); + const AttExchangeMTU req(AttPDUMsg::ReqRespType::REQUEST, number(Defaults::MAX_ATT_MTU)); + const std::lock_guard<std::recursive_mutex> lock(mtx_command); PERF_TS_T0(); uint16_t mtu = 0; @@ -1485,6 +1473,63 @@ BTGattCharRef BTGattHandler::findCharacterisicsByValueHandle(const BTGattService return nullptr; } +bool BTGattHandler::initClientGatt(std::shared_ptr<BTGattHandler> shared_this, bool& already_init) noexcept { + const std::lock_guard<std::recursive_mutex> lock(mtx_command); + already_init = clientMTUExchanged && services.size() > 0; + if( already_init ) { + return true; + } + if( !isConnected() ) { + DBG_PRINT("GATTHandler::initClientGatt: Not connected: %s", toString().c_str()); + return false; + } + if( !clientMTUExchanged) { + // First point of failure if remote device exposes no GATT functionality. Allow a longer timeout! + const int32_t initial_command_reply_timeout = std::min<int32_t>(10000, std::max<int32_t>(env.GATT_INITIAL_COMMAND_REPLY_TIMEOUT, 2*supervision_timeout)); + uint16_t mtu = 0; + try { + DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: MTU Exchange Start: %s", toString().c_str()); + mtu = clientMTUExchange(initial_command_reply_timeout); + } catch (std::exception &e) { + ERR_PRINT2("GattHandler.initClientGatt: exchangeMTU failed: %s", e.what()); + } catch (std::string &msg) { + ERR_PRINT2("GattHandler.initClientGatt: exchangeMTU failed: %s", msg.c_str()); + } catch (const char *msg) { + ERR_PRINT2("GattHandler.initClientGatt: exchangeMTU failed: %s", msg); + } + if( 0 == mtu ) { + ERR_PRINT2("GATTHandler::initClientGatt: Local GATT Client: Zero serverMTU -> disconnect: %s", toString().c_str()); + disconnect(true /* disconnectDevice */, false /* ioErrorCause */); + return false; + } + serverMTU = mtu; + usedMTU = std::min(number(Defaults::MAX_ATT_MTU), serverMTU.load()); + clientMTUExchanged = true; + DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: MTU Exchanged: server %u -> used %u, %s", serverMTU.load(), usedMTU.load(), toString().c_str()); + } + + if( services.size() > 0 ) { + // already initialized + return true; + } + + try { + // Service discovery may consume 500ms - 2000ms, depending on bandwidth + DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: Service Discovery Start: %s", toString().c_str()); + jau::darray<BTGattServiceRef>& gattServices = discoverCompletePrimaryServices(shared_this); + if( gattServices.size() == 0 ) { // nothing discovered + ERR_PRINT2("GATTHandler::initClientGatt: No primary services discovered"); + disconnect(true /* disconnectDevice */, false /* ioErrorCause */); + return false; + } + DBG_PRINT("GATTHandler::initClientGatt: %zu Services Discovered: %s", gattServices.size(), toString().c_str()); + return true; + } catch (std::exception &e) { + WARN_PRINT("GATTHandler::initClientGatt: Caught exception: '%s' on %s", e.what(), toString().c_str()); + } + return false; +} + jau::darray<BTGattServiceRef> & BTGattHandler::discoverCompletePrimaryServices(std::shared_ptr<BTGattHandler> shared_this) { const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor if( !discoverPrimaryServices(shared_this, services) ) { |