diff options
-rw-r--r-- | api/direct_bt/GATTHandler.hpp | 14 | ||||
-rw-r--r-- | api/direct_bt/L2CAPComm.hpp | 64 | ||||
-rw-r--r-- | examples/direct_bt_scanner10/dbt_scanner10.cpp | 9 | ||||
-rw-r--r-- | examples/java/ScannerTinyB10.java | 45 | ||||
-rw-r--r-- | src/direct_bt/GATTHandler.cpp | 5 | ||||
-rw-r--r-- | src/direct_bt/L2CAPComm.cpp | 46 |
6 files changed, 152 insertions, 31 deletions
diff --git a/api/direct_bt/GATTHandler.hpp b/api/direct_bt/GATTHandler.hpp index 784f665e..346d4153 100644 --- a/api/direct_bt/GATTHandler.hpp +++ b/api/direct_bt/GATTHandler.hpp @@ -71,17 +71,9 @@ namespace direct_bt { public: /** - * L2CAP poll timeout for reader thread, defaults to 10s. - * <p> - * Environment variable is 'direct_bt.gatt.reader.timeout'. - * </p> - */ - const int32_t L2CAP_READER_THREAD_POLL_TIMEOUT; - - /** * Timeout for GATT read command replies, defaults to 500ms. * <p> - * Environment variable is 'direct_bt.gatt.read.timeout'. + * Environment variable is 'direct_bt.gatt.cmd.read.timeout'. * </p> */ const int32_t GATT_READ_COMMAND_REPLY_TIMEOUT; @@ -89,7 +81,7 @@ namespace direct_bt { /** * Timeout for GATT write command replies, defaults to 500ms. * <p> - * Environment variable is 'direct_bt.gatt.write.timeout'. + * Environment variable is 'direct_bt.gatt.cmd.write.timeout'. * </p> */ const int32_t GATT_WRITE_COMMAND_REPLY_TIMEOUT; @@ -97,7 +89,7 @@ namespace direct_bt { /** * Timeout for l2cap _initial_ command reply, defaults to 2500ms. * <p> - * Environment variable is 'direct_bt.gatt.init.timeout'. + * Environment variable is 'direct_bt.gatt.cmd.init.timeout'. * </p> */ const int32_t GATT_INITIAL_COMMAND_REPLY_TIMEOUT; diff --git a/api/direct_bt/L2CAPComm.hpp b/api/direct_bt/L2CAPComm.hpp index 793c36d2..bdb7f17e 100644 --- a/api/direct_bt/L2CAPComm.hpp +++ b/api/direct_bt/L2CAPComm.hpp @@ -35,6 +35,7 @@ #include <mutex> #include <atomic> +#include "DBTEnv.hpp" #include "UUID.hpp" #include "BTTypes.hpp" @@ -50,6 +51,63 @@ namespace direct_bt { class DBTDevice; // forward /** + * L2CAP Singleton runtime environment properties + * <p> + * Also see {@link DBTEnv::getExplodingProperties(const std::string & prefixDomain)}. + * </p> + */ + class L2CAPEnv : public DBTEnvrionment { + private: + L2CAPEnv() noexcept; + + const bool exploding; // just to trigger exploding properties + + public: + /** + * L2CAP poll timeout for reading, defaults to 10s. + * <p> + * Environment variable is 'direct_bt.l2cap.reader.timeout'. + * </p> + */ + const int32_t L2CAP_READER_POLL_TIMEOUT; + + /** + * Debugging facility: L2CAP restart count on transmission errors, defaults to 5 attempts. + * <p> + * If negative, L2CAPComm will abort() the program. + * </p> + * <p> + * Environment variable is 'direct_bt.l2cap.restart.count'. + * </p> + */ + const int32_t L2CAP_RESTART_COUNT_ON_ERROR; + + /** + * Debug all GATT Data communication + * <p> + * Environment variable is 'direct_bt.debug.l2cap.data'. + * </p> + */ + const bool DEBUG_DATA; + + public: + static L2CAPEnv& get() noexcept { + /** + * Thread safe starting with C++11 6.7: + * + * If control enters the declaration concurrently while the variable is being initialized, + * the concurrent execution shall wait for completion of the initialization. + * + * (Magic Statics) + * + * Avoiding non-working double checked locking. + */ + static L2CAPEnv e; + return e; + } + }; + + /** * Read/Write L2CAP communication channel. */ class L2CAPComm { @@ -67,6 +125,8 @@ namespace direct_bt { static int l2cap_open_dev(const EUI48 & adapterAddress, const uint16_t psm, const uint16_t cid, const bool pubaddr); static int l2cap_close_dev(int dd); + const L2CAPEnv & env; + std::recursive_mutex mtx_write; std::shared_ptr<DBTDevice> device; const std::string deviceString; @@ -102,8 +162,8 @@ namespace direct_bt { /** Return the recursive write mutex for multithreading access. */ std::recursive_mutex & mutex_write() { return mtx_write; } - /** Generic read w/ own timeoutMS, w/o locking suitable for a unique ringbuffer sink. */ - int read(uint8_t* buffer, const int capacity, const int32_t timeoutMS); + /** Generic read, w/o locking suitable for a unique ringbuffer sink. Using L2CAPEnv::L2CAP_READER_POLL_TIMEOUT.*/ + int read(uint8_t* buffer, const int capacity); /** Generic write, locking {@link #mutex_write()}. */ int write(const uint8_t *buffer, const int length); diff --git a/examples/direct_bt_scanner10/dbt_scanner10.cpp b/examples/direct_bt_scanner10/dbt_scanner10.cpp index c62ade8b..81770ee2 100644 --- a/examples/direct_bt_scanner10/dbt_scanner10.cpp +++ b/examples/direct_bt_scanner10/dbt_scanner10.cpp @@ -500,6 +500,8 @@ int main(int argc, char *argv[]) setenv("direct_bt.verbose", argv[++i], 1 /* overwrite */); } else if( !strcmp("-dbt_gatt", argv[i]) && argc > (i+1) ) { setenv("direct_bt.gatt", argv[++i], 1 /* overwrite */); + } else if( !strcmp("-dbt_l2cap", argv[i]) && argc > (i+1) ) { + setenv("direct_bt.l2cap", argv[++i], 1 /* overwrite */); } else if( !strcmp("-dbt_hci", argv[i]) && argc > (i+1) ) { setenv("direct_bt.hci", argv[++i], 1 /* overwrite */); } else if( !strcmp("-dbt_mgmt", argv[i]) && argc > (i+1) ) { @@ -543,9 +545,10 @@ int main(int argc, char *argv[]) "(-mac <device_address>)* (-wl <device_address>)* " "[-dbt_verbose true|false] " "[-dbt_debug true|false|adapter.event,gatt.data,hci.event,mgmt.event] " - "[-dbt_mgmt cmd.timeout=3000,ringsize=64,... " - "[-dbt_hci cmd.complete.timeout=10000,cmd.status.timeout=3000,ringsize=64,... " - "[-dbt_gatt cmd.read.timeout=500,cmd.write.timeout=500,cmd.init.timeout=2500,ringsize=128,... " + "[-dbt_mgmt cmd.timeout=3000,ringsize=64,...] " + "[-dbt_hci cmd.complete.timeout=10000,cmd.status.timeout=3000,ringsize=64,...] " + "[-dbt_gatt cmd.read.timeout=500,cmd.write.timeout=500,cmd.init.timeout=2500,ringsize=128,...] " + "[-dbt_l2cap reader.timeout=10000,restart.count=0,...] " "\n"); fprintf(stderr, "MULTI_MEASUREMENTS %d\n", MULTI_MEASUREMENTS); diff --git a/examples/java/ScannerTinyB10.java b/examples/java/ScannerTinyB10.java index b57c992e..99882518 100644 --- a/examples/java/ScannerTinyB10.java +++ b/examples/java/ScannerTinyB10.java @@ -77,7 +77,8 @@ public class ScannerTinyB10 { boolean REMOVE_DEVICE = true; boolean USE_WHITELIST = false; final List<String> whitelist = new ArrayList<String>(); - final List<String> characteristicList = new ArrayList<String>(); + final List<String> charIdentifierList = new ArrayList<String>(); + final List<String> charValueList = new ArrayList<String>(); boolean SHOW_UPDATE_EVENTS = false; boolean SILENT_GATT = false; @@ -269,7 +270,7 @@ public class ScannerTinyB10 { final List<PairingMode> spm = Arrays.asList(device.getSupportedPairingModes()); println("Supported Secure Pairing Modes: " + spm.toString()); final List<PairingMode> rpm = Arrays.asList(device.getRequiredPairingModes()); - println("Required Secure Pairing Modes: " + spm.toString()); + println("Required Secure Pairing Modes: " + rpm.toString()); if( spm.contains(PairingMode.JUST_WORKS) ) { final HCIStatusCode res = device.pair(null); // empty for JustWorks @@ -314,7 +315,7 @@ public class ScannerTinyB10 { "PERF: adapter-init to gatt-complete " + td05 + " ms"+System.lineSeparator()); } { - for(final String characteristic : characteristicList) { + for(final String characteristic : charIdentifierList) { final BluetoothGattCharacteristic char0 = (BluetoothGattCharacteristic) manager.find(BluetoothType.GATT_CHARACTERISTIC, null, characteristic, null); final BluetoothGattCharacteristic char1 = (BluetoothGattCharacteristic) @@ -325,6 +326,31 @@ public class ScannerTinyB10 { println(" over manager: "+char0); println(" over adapter: "+char1); println(" over device : "+char2); + if( null != char2 ) { + final GATTCharacteristicListener charPingPongListener = new GATTCharacteristicListener(null) { + @Override + public void notificationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp) { + println("****** PingPong GATT notificationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + + @Override + public void indicationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp, final boolean confirmationSent) { + println("****** PingPong GATT indicationReceived: "+charDecl+ + ", value "+BluetoothUtils.bytesHexString(value, true, true)); + } + }; + final boolean enabledState[] = { false, false }; + final boolean addedCharPingPongListenerRes = + char2.addCharacteristicListener(charPingPongListener, enabledState); + BluetoothGattService.addCharacteristicListenerToAll(device, primServices, charPingPongListener); + if( !SILENT_GATT ) { + println("Added CharPingPongListenerRes: "+addedCharPingPongListenerRes+", enabledState "+Arrays.toString(enabledState)); + } + + } } } { @@ -551,6 +577,8 @@ public class ScannerTinyB10 { System.setProperty("direct_bt.verbose", args[++i]); } else if( arg.equals("-dbt_gatt") && args.length > (i+1) ) { System.setProperty("direct_bt.gatt", args[++i]); + } else if( arg.equals("-dbt_l2cap") && args.length > (i+1) ) { + System.setProperty("direct_bt.l2cap", args[++i]); } else if( arg.equals("-dbt_hci") && args.length > (i+1) ) { System.setProperty("direct_bt.hci", args[++i]); } else if( arg.equals("-dbt_mgmt") && args.length > (i+1) ) { @@ -598,7 +626,7 @@ public class ScannerTinyB10 { test.whitelist.add(addr); test.USE_WHITELIST = true; } else if( arg.equals("-char") && args.length > (i+1) ) { - test.characteristicList.add(args[++i]); + test.charIdentifierList.add(args[++i]); } else if( arg.equals("-disconnect") ) { test.KEEP_CONNECTED = false; } else if( arg.equals("-keepDevice") ) { @@ -616,9 +644,10 @@ public class ScannerTinyB10 { "[-verbose] [-debug] "+ "[-dbt_verbose true|false] "+ "[-dbt_debug true|false|adapter.event,gatt.data,hci.event,mgmt.event] "+ - "[-dbt_mgmt cmd.timeout=3000,ringsize=64,... "+ - "[-dbt_hci cmd.complete.timeout=10000,cmd.status.timeout=3000,ringsize=64,... "+ - "[-dbt_gatt cmd.read.timeout=500,cmd.write.timeout=500,cmd.init.timeout=2500,ringsize=128,... "+ + "[-dbt_mgmt cmd.timeout=3000,ringsize=64,...] "+ + "[-dbt_hci cmd.complete.timeout=10000,cmd.status.timeout=3000,ringsize=64,...] "+ + "[-dbt_gatt cmd.read.timeout=500,cmd.write.timeout=500,cmd.init.timeout=2500,ringsize=128,...] "+ + "[-dbt_l2cap reader.timeout=10000,restart.count=0,...] "+ "[-shutdown <int>]'"); } @@ -632,7 +661,7 @@ public class ScannerTinyB10 { println("dev_id "+test.dev_id); println("waitForDevice: "+Arrays.toString(test.waitForDevices.toArray())); - println("characteristicList: "+Arrays.toString(test.characteristicList.toArray())); + println("characteristicList: "+Arrays.toString(test.charIdentifierList.toArray())); if( waitForEnter ) { println("Press ENTER to continue\n"); diff --git a/src/direct_bt/GATTHandler.cpp b/src/direct_bt/GATTHandler.cpp index 4ed2da75..34c13066 100644 --- a/src/direct_bt/GATTHandler.cpp +++ b/src/direct_bt/GATTHandler.cpp @@ -67,7 +67,6 @@ using namespace direct_bt; GATTEnv::GATTEnv() noexcept : exploding( DBTEnv::getExplodingProperties("direct_bt.gatt") ), - L2CAP_READER_THREAD_POLL_TIMEOUT( DBTEnv::getInt32Property("direct_bt.gatt.reader.timeout", 10000, 1500 /* min */, INT32_MAX /* max */) ), GATT_READ_COMMAND_REPLY_TIMEOUT( DBTEnv::getInt32Property("direct_bt.gatt.cmd.read.timeout", 500, 250 /* min */, INT32_MAX /* max */) ), GATT_WRITE_COMMAND_REPLY_TIMEOUT( DBTEnv::getInt32Property("direct_bt.gatt.cmd.write.timeout", 500, 250 /* min */, INT32_MAX /* max */) ), GATT_INITIAL_COMMAND_REPLY_TIMEOUT( DBTEnv::getInt32Property("direct_bt.gatt.cmd.init.timeout", 2500, 2000 /* min */, INT32_MAX /* max */) ), @@ -201,7 +200,7 @@ void GATTHandler::l2capReaderThreadImpl() { break; } - len = l2cap.read(rbuffer.get_wptr(), rbuffer.getSize(), env.L2CAP_READER_THREAD_POLL_TIMEOUT); + len = l2cap.read(rbuffer.get_wptr(), rbuffer.getSize()); if( 0 < len ) { const AttPDUMsg * attPDU = AttPDUMsg::getSpecialized(rbuffer.get_ptr(), len); const AttPDUMsg::Opcode opc = attPDU->getOpcode(); @@ -263,7 +262,7 @@ void GATTHandler::l2capReaderThreadImpl() { delete attPDU; // free unhandled PDU } } else if( ETIMEDOUT != errno && !l2capReaderShallStop ) { // expected exits - IRQ_PRINT("GATTHandler::l2capReaderThread: l2cap read error -> Stop"); + IRQ_PRINT("GATTHandler::l2capReaderThread: l2cap read error -> Stop; l2cap.read %d", len); l2capReaderShallStop = true; has_ioerror = true; } diff --git a/src/direct_bt/L2CAPComm.cpp b/src/direct_bt/L2CAPComm.cpp index 86367408..f3e1d2aa 100644 --- a/src/direct_bt/L2CAPComm.cpp +++ b/src/direct_bt/L2CAPComm.cpp @@ -51,6 +51,14 @@ extern "C" { using namespace direct_bt; +L2CAPEnv::L2CAPEnv() noexcept +: exploding( DBTEnv::getExplodingProperties("direct_bt.l2cap") ), + L2CAP_READER_POLL_TIMEOUT( DBTEnv::getInt32Property("direct_bt.l2cap.reader.timeout", 10000, 1500 /* min */, INT32_MAX /* max */) ), + L2CAP_RESTART_COUNT_ON_ERROR( DBTEnv::getInt32Property("direct_bt.l2cap.restart.count", 5, INT32_MIN /* min */, INT32_MAX /* max */) ), // FIXME: Move to L2CAPComm + DEBUG_DATA( DBTEnv::getBooleanProperty("direct_bt.debug.l2cap.data", false) ) +{ +} + int L2CAPComm::l2cap_open_dev(const EUI48 & adapterAddress, const uint16_t psm, const uint16_t cid, const bool pubaddrAdapter) { sockaddr_l2 a; int fd, err; @@ -97,7 +105,8 @@ int L2CAPComm::l2cap_close_dev(int dd) // ************************************************* L2CAPComm::L2CAPComm(std::shared_ptr<DBTDevice> device, const uint16_t psm, const uint16_t cid) -: device(device), deviceString(device->getAddressString()), psm(psm), cid(cid), +: env(L2CAPEnv::get()), + device(device), deviceString(device->getAddressString()), psm(psm), cid(cid), socket_descriptor( l2cap_open_dev(device->getAdapter().getAddress(), psm, cid, true /* pubaddrAdptr */) ), is_connected(true), has_ioerror(false), interrupt_flag(false), tid_connect(0) { @@ -192,9 +201,13 @@ bool L2CAPComm::disconnect() noexcept { return true; } -int L2CAPComm::read(uint8_t* buffer, const int capacity, const int32_t timeoutMS) { +int L2CAPComm::read(uint8_t* buffer, const int capacity) { + const int32_t timeoutMS = env.L2CAP_READER_POLL_TIMEOUT; int len = 0; + int err_res = 0; + if( 0 > socket_descriptor || 0 > capacity ) { + err_res = -1; // invalid socket_descriptor or capacity goto errout; } if( 0 == capacity ) { @@ -211,9 +224,11 @@ int L2CAPComm::read(uint8_t* buffer, const int capacity, const int32_t timeoutMS // cont temp unavail or interruption continue; } + err_res = -10; // poll error !(ETIMEDOUT || EAGAIN || EINTR) goto errout; } if (!n) { + err_res = -11; // poll error ETIMEDOUT errno = ETIMEDOUT; goto errout; } @@ -224,6 +239,7 @@ int L2CAPComm::read(uint8_t* buffer, const int capacity, const int32_t timeoutMS // cont temp unavail or interruption continue; } + err_res = -20; // read error goto errout; } @@ -234,14 +250,26 @@ errout: if( errno != ETIMEDOUT ) { has_ioerror = true; } - return -1; + if( is_connected ) { + if( env.L2CAP_RESTART_COUNT_ON_ERROR < 0 ) { + ABORT("L2CAPComm::read: Error res %d; %s, dd %d, %s, psm %u, cid %u", + err_res, getStateString().c_str(), socket_descriptor.load(), deviceString.c_str(), psm, cid); + } else { + IRQ_PRINT("L2CAPComm::read: Error res %d; %s, dd %d, %s, psm %u, cid %u", + err_res, getStateString().c_str(), socket_descriptor.load(), deviceString.c_str(), psm, cid); + } + } + return err_res; } int L2CAPComm::write(const uint8_t * buffer, const int length) { const std::lock_guard<std::recursive_mutex> lock(mtx_write); // RAII-style acquire and relinquish via destructor int len = 0; + int err_res = 0; + if( 0 > socket_descriptor || 0 > length ) { + err_res = -1; // invalid socket_descriptor or capacity goto errout; } if( 0 == length ) { @@ -252,6 +280,7 @@ int L2CAPComm::write(const uint8_t * buffer, const int length) { if( EAGAIN == errno || EINTR == errno ) { continue; } + err_res = -10; // write error !(EAGAIN || EINTR) goto errout; } @@ -260,6 +289,15 @@ done: errout: has_ioerror = true; - return -1; + if( is_connected ) { + if( env.L2CAP_RESTART_COUNT_ON_ERROR < 0 ) { + ABORT("L2CAPComm::write: Error res %d; %s, dd %d, %s, psm %u, cid %u", + err_res, getStateString().c_str(), socket_descriptor.load(), deviceString.c_str(), psm, cid); + } else { + IRQ_PRINT("L2CAPComm::write: Error res %d; %s, dd %d, %s, psm %u, cid %u", + err_res, getStateString().c_str(), socket_descriptor.load(), deviceString.c_str(), psm, cid); + } + } + return err_res; } |