aboutsummaryrefslogtreecommitdiffstats
path: root/examples/direct_bt_scanner00
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2020-05-17 09:58:12 +0200
committerSven Gothel <[email protected]>2020-05-17 09:58:12 +0200
commit525cb093d2ead1be30cf860d096129a8ec7146bf (patch)
tree0bf0e063fc9dfbe7b97cf45301dfe1b859b1e5cf /examples/direct_bt_scanner00
parentb12a3e3adf8159a0c252cee67beae35e1b5b879f (diff)
Working GATT Java Side; GATT Types made fully functional for user to avoid 'technical' GATTHandler
GATT Types made fully functional for user to avoid 'technical' GATTHandler (C++) > GATTService, GATTCharacteristic, GATTDescriptor -- Reside in their own respective files -- Added semantic methods (readValue(), ..) implemented using DBTDevice -> GATTHandler -- GATTDescriptor owns its value -- GATTHandler setSendIndicationConfirmation(..) defaults to true -- Allow user to cirvumvent using GATTHandler manually completely, device 1--*> services 1--*> characteristics 1--*> descriptor -- C++ GATT types aligned 1:1 to TinyB (Java) > Merged GATTIndicationListener + GATTNotificationListener -> GATTCharacteristicListener -- Simplifying usage, unifying notification and indication -- Now using a list of shared_ptr<GATTCharacteristicListener> allowing multiple actors instead of just a one shot entry. Similar to AdapterStatusListener, we utilize this also on the Java side to implement the TinyB notifications. See dbt_scanner00.cpp: Simplified high-level usage. See dbt_scanner01.cpp: Lower-level technical usage w/ GATTHandler. +++ > Simplified more names > Removed redundancy in listener callbacks, -- don't pass adapter when device is already given. device <*--1> adapter -- don't pass GATT handle explicitly when characteristic is passed > Comparison of all GATT types are done by their respective unique handle Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle) > GATTHandler: Own L2CAPComm instance directly, instead of shared_ptr. > JNIMem: Added JNICriticalArray class for RAII style release ++++ ++++ Working GATT Java Side > All toString() methods return the C++ toString() via JNI for better representation. > DBTDevice.java/JNI: Resolved the odd 'adapter' reference issue: -- Was not passing the jobject of DBTAdapter, but its shared container refeference ;-) > All GATT types receive their GATT handler for equal test and identity @ ctor > GATT read/write Value update the cached value as well as issue the notifyValue on change, including GATTCharacteristic notification/indication listener
Diffstat (limited to 'examples/direct_bt_scanner00')
-rw-r--r--examples/direct_bt_scanner00/dbt_scanner00.cpp269
1 files changed, 269 insertions, 0 deletions
diff --git a/examples/direct_bt_scanner00/dbt_scanner00.cpp b/examples/direct_bt_scanner00/dbt_scanner00.cpp
new file mode 100644
index 00000000..5ddef516
--- /dev/null
+++ b/examples/direct_bt_scanner00/dbt_scanner00.cpp
@@ -0,0 +1,269 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2020 Gothel Software e.K.
+ * Copyright (c) 2020 ZAFENA AB
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <direct_bt/DirectBT.hpp>
+#include <cinttypes>
+
+extern "C" {
+ #include <unistd.h>
+}
+
+using namespace direct_bt;
+
+/***
+ * This C++ direct_bt scanner code
+ * uses a more simple high-level approach via semantic GATT types (Service, Characteristic, ..)
+ * without bothering with fine implementation details of GATTHandler.
+ *
+ * For a more technical and low-level approach see dbt_scanner01.cpp!
+ */
+
+std::shared_ptr<DBTDevice> deviceFound = nullptr;
+std::mutex mtxDeviceFound;
+std::condition_variable cvDeviceFound;
+
+class MyAdapterStatusListener : public AdapterStatusListener {
+ void adapterSettingsChanged(DBTAdapter const &a, const AdapterSetting oldmask, const AdapterSetting newmask,
+ const AdapterSetting changedmask, const uint64_t timestamp) override {
+ fprintf(stderr, "****** Native Adapter SETTINGS_CHANGED: %s -> %s, changed %s\n",
+ adapterSettingsToString(oldmask).c_str(),
+ adapterSettingsToString(newmask).c_str(),
+ adapterSettingsToString(changedmask).c_str());
+ fprintf(stderr, "Status DBTAdapter:\n");
+ fprintf(stderr, "%s\n", a.toString().c_str());
+ (void)timestamp;
+ }
+
+ void deviceFound(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override {
+ fprintf(stderr, "****** FOUND__: %s\n", device->toString().c_str());
+ fprintf(stderr, "Status Adapter:\n");
+ fprintf(stderr, "%s\n", device->getAdapter().toString().c_str());
+ {
+ std::unique_lock<std::mutex> lockRead(mtxDeviceFound); // RAII-style acquire and relinquish via destructor
+ ::deviceFound = device;
+ cvDeviceFound.notify_all(); // notify waiting getter
+ }
+ (void)timestamp;
+ }
+ void deviceUpdated(std::shared_ptr<DBTDevice> device, const uint64_t timestamp, const EIRDataType updateMask) override {
+ fprintf(stderr, "****** UPDATED: %s of %s\n", eirDataMaskToString(updateMask).c_str(), device->toString().c_str());
+ fprintf(stderr, "Status Adapter:\n");
+ fprintf(stderr, "%s\n", device->getAdapter().toString().c_str());
+ (void)timestamp;
+ }
+ void deviceConnected(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override {
+ fprintf(stderr, "****** CONNECTED: %s\n", device->toString().c_str());
+ fprintf(stderr, "Status Adapter:\n");
+ fprintf(stderr, "%s\n", device->getAdapter().toString().c_str());
+ (void)timestamp;
+ }
+ void deviceDisconnected(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override {
+ fprintf(stderr, "****** DISCONNECTED: %s\n", device->toString().c_str());
+ fprintf(stderr, "Status Adapter:\n");
+ fprintf(stderr, "%s\n", device->getAdapter().toString().c_str());
+ (void)timestamp;
+ }
+};
+
+static const uuid16_t _TEMPERATURE_MEASUREMENT(GattCharacteristicType::TEMPERATURE_MEASUREMENT);
+
+class MyGATTEventListener : public SpecificGATTCharacteristicListener {
+ public:
+
+ MyGATTEventListener(const GATTCharacteristic * characteristicMatch)
+ : SpecificGATTCharacteristicListener(characteristicMatch) {}
+
+ void notificationReceived(GATTCharacteristicRef charDecl, std::shared_ptr<TROOctets> charValue, const uint64_t timestamp) override {
+ const std::shared_ptr<DBTDevice> dev = charDecl->getDevice();
+ const int64_t tR = getCurrentMilliseconds();
+ fprintf(stderr, "****** GATT Notify (td %" PRIu64 " ms, dev-discovered %" PRIu64 " ms): From %s\n",
+ (tR-timestamp), (tR-dev->ts_creation), dev->toString().c_str());
+ if( nullptr != charDecl ) {
+ fprintf(stderr, "****** decl %s\n", charDecl->toString().c_str());
+ }
+ fprintf(stderr, "****** rawv %s\n", charValue->toString().c_str());
+ }
+
+ void indicationReceived(GATTCharacteristicRef charDecl,
+ std::shared_ptr<TROOctets> charValue, const uint64_t timestamp,
+ const bool confirmationSent) override
+ {
+ const std::shared_ptr<DBTDevice> dev = charDecl->getDevice();
+ const int64_t tR = getCurrentMilliseconds();
+ fprintf(stderr, "****** GATT Indication (confirmed %d, td(msg %" PRIu64 " ms, dev-discovered %" PRIu64 " ms): From %s\n",
+ confirmationSent, (tR-timestamp), (tR-dev->ts_creation), dev->toString().c_str());
+ if( nullptr != charDecl ) {
+ fprintf(stderr, "****** decl %s\n", charDecl->toString().c_str());
+ if( _TEMPERATURE_MEASUREMENT == *charDecl->value_type ) {
+ std::shared_ptr<TemperatureMeasurementCharateristic> temp = TemperatureMeasurementCharateristic::get(*charValue);
+ if( nullptr != temp ) {
+ fprintf(stderr, "****** valu %s\n", temp->toString().c_str());
+ }
+ }
+ }
+ fprintf(stderr, "****** rawv %s\n", charValue->toString().c_str());
+ }
+};
+
+int main(int argc, char *argv[])
+{
+ bool ok = true, foundDevice=false;
+ int dev_id = 0; // default
+ bool waitForEnter=false;
+ EUI48 waitForDevice = EUI48_ANY_DEVICE;
+ bool forever = false;
+
+ /**
+ * BT Core Spec v5.2: Vol 3, Part A L2CAP Spec: 7.9 PRIORITIZING DATA OVER HCI
+ *
+ * In order for guaranteed channels to meet their guarantees,
+ * L2CAP should prioritize traffic over the HCI transport in devices that support HCI.
+ * Packets for Guaranteed channels should receive higher priority than packets for Best Effort channels.
+ * ...
+ * I have noticed that w/o HCI le_connect, overall communication takes twice as long!!!
+ */
+ bool doHCI_Connect = true;
+
+ for(int i=1; i<argc; i++) {
+ if( !strcmp("-wait", argv[i]) ) {
+ waitForEnter = true;
+ } else if( !strcmp("-forever", argv[i]) ) {
+ forever = true;
+ } else if( !strcmp("-dev_id", argv[i]) && argc > (i+1) ) {
+ dev_id = atoi(argv[++i]);
+ } else if( !strcmp("-skipConnect", argv[i]) ) {
+ doHCI_Connect = false;
+ } else if( !strcmp("-mac", argv[i]) && argc > (i+1) ) {
+ std::string macstr = std::string(argv[++i]);
+ waitForDevice = EUI48(macstr);
+ }
+ }
+ fprintf(stderr, "dev_id %d\n", dev_id);
+ fprintf(stderr, "doHCI_Connect %d\n", doHCI_Connect);
+ fprintf(stderr, "waitForDevice: %s\n", waitForDevice.toString().c_str());
+
+ if( waitForEnter ) {
+ fprintf(stderr, "Press ENTER to continue\n");
+ getchar();
+ }
+
+ DBTAdapter adapter(dev_id);
+ if( !adapter.hasDevId() ) {
+ fprintf(stderr, "Default adapter not available.\n");
+ exit(1);
+ }
+ if( !adapter.isValid() ) {
+ fprintf(stderr, "Adapter invalid.\n");
+ exit(1);
+ }
+ fprintf(stderr, "Using adapter: device %s, address %s: %s\n",
+ adapter.getName().c_str(), adapter.getAddressString().c_str(), adapter.toString().c_str());
+
+ adapter.addStatusListener(std::shared_ptr<AdapterStatusListener>(new MyAdapterStatusListener()));
+
+ const int64_t t0 = getCurrentMilliseconds();
+
+ std::shared_ptr<HCISession> session = adapter.open();
+
+ while( ok && ( forever || !foundDevice ) && nullptr != session ) {
+ ok = adapter.startDiscovery();
+ if( !ok) {
+ perror("Adapter start discovery failed");
+ goto out;
+ }
+
+ std::shared_ptr<DBTDevice> device = nullptr;
+ {
+ std::unique_lock<std::mutex> lockRead(mtxDeviceFound); // RAII-style acquire and relinquish via destructor
+ while( nullptr == device ) { // FIXME deadlock, waiting forever!
+ cvDeviceFound.wait(lockRead);
+ if( nullptr != deviceFound ) {
+ foundDevice = deviceFound->getAddress() == waitForDevice; // match
+ if( foundDevice || ( EUI48_ANY_DEVICE == waitForDevice && deviceFound->isLEAddressType() ) ) {
+ // match or any LE device
+ device.swap(deviceFound); // take over deviceFound
+ }
+ }
+ }
+ }
+ adapter.stopDiscovery();
+
+ if( ok && nullptr != device ) {
+ const uint64_t t1 = getCurrentMilliseconds();
+
+ //
+ // GATT Service Processing
+ //
+ std::vector<GATTServiceRef> primServices = device->getServices(); // implicit HCI and GATT connect...
+ if( primServices.size() > 0 ) {
+ const uint64_t t5 = getCurrentMilliseconds();
+ {
+ const uint64_t td15 = t5 - t1; // discovered -> connect -> gatt complete
+ const uint64_t td05 = t5 - t0; // total
+ fprintf(stderr, "\n\n\n");
+ fprintf(stderr, "GATT primary-services completed\n");
+ fprintf(stderr, " discovery-done to gatt complete %" PRIu64 " ms,\n"
+ " discovered to gatt complete %" PRIu64 " ms,\n"
+ " total %" PRIu64 " ms\n\n",
+ td15, (t5 - device->getCreationTimestamp()), td05);
+ }
+
+ for(size_t i=0; i<primServices.size(); i++) {
+ GATTService & primService = *primServices.at(i);
+ fprintf(stderr, " [%2.2d] Service %s\n", (int)i, primService.toString().c_str());
+ fprintf(stderr, " [%2.2d] Service Characteristics\n", (int)i);
+ std::vector<GATTCharacteristicRef> & serviceCharacteristics = primService.characteristicList;
+ for(size_t j=0; j<serviceCharacteristics.size(); j++) {
+ GATTCharacteristic & serviceChar = *serviceCharacteristics.at(j);
+ fprintf(stderr, " [%2.2d.%2.2d] Decla: %s\n", (int)i, (int)j, serviceChar.toString().c_str());
+ if( serviceChar.hasProperties(GATTCharacteristic::PropertyBitVal::Read) ) {
+ POctets value(GATTHandler::ClientMaxMTU, 0);
+ if( serviceChar.readValue(value) ) {
+ fprintf(stderr, " [%2.2d.%2.2d] Value: %s\n", (int)i, (int)j, value.toString().c_str());
+ }
+ }
+ 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",
+ (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 ..
+ sleep(1);
+ }
+ device->disconnect(); // OK if not connected, also issues device->disconnectGATT() -> gatt->disconnect()
+ } // if( ok && nullptr != device )
+ }
+
+out:
+ if( nullptr != session ) {
+ session->close();
+ }
+ return 0;
+}
+