diff options
author | Sven Gothel <[email protected]> | 2020-05-17 09:58:12 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-05-17 09:58:12 +0200 |
commit | 525cb093d2ead1be30cf860d096129a8ec7146bf (patch) | |
tree | 0bf0e063fc9dfbe7b97cf45301dfe1b859b1e5cf /examples/direct_bt_scanner00 | |
parent | b12a3e3adf8159a0c252cee67beae35e1b5b879f (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.cpp | 269 |
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; +} + |