summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/direct_bt/GATTHandler.hpp14
-rw-r--r--api/direct_bt/L2CAPComm.hpp64
-rw-r--r--examples/direct_bt_scanner10/dbt_scanner10.cpp9
-rw-r--r--examples/java/ScannerTinyB10.java45
-rw-r--r--src/direct_bt/GATTHandler.cpp5
-rw-r--r--src/direct_bt/L2CAPComm.cpp46
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;
}