aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/direct_bt/BTAdapter.hpp13
-rw-r--r--api/direct_bt/BTDevice.hpp46
-rw-r--r--api/direct_bt/BTGattHandler.hpp172
-rw-r--r--java/jau/direct_bt/DBTDevice.java7
-rw-r--r--java/jni/direct_bt/DBTDevice.cxx7
-rw-r--r--java/org/direct_bt/AdapterStatusListener.java6
-rw-r--r--java/org/direct_bt/BTDevice.java10
-rw-r--r--src/direct_bt/BTDevice.cpp102
-rw-r--r--src/direct_bt/BTGattHandler.cpp99
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) ) {