Ble read/write characteristics over MQTT. (#988)

* EXPERIMENTAL: Ble read/write characteristics over MQTT.

Uses #979

This allows reading and writing BLE characteristics from an MQTT message.

Example format:
mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{"ble_write_address":"AA:BB:CC:DD:EE:FF", "ble_write_service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b", "ble_write_char":"cba20002-224d-11e6-9fb8-0002a5d5c51b", "ble_write_value":"TEST", "ttl":4 }'
mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{"ble_read_address":"30:AE:A4:7C:3C:A6", "ble_read_service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b", "ble_read_char":"cba20002-224d-11e6-9fb8-0002a5d5c51b", "ttl": 2 }'
The ttl parameter is the number of calls to connect (defaults to 1), which occur after the BLE scan completes.

A response is provided over MQTT in the format:
write : {"id":"30:AE:A4:7C:3C:A6","service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b","characteristic":"cba20002-224d-11e6-9fb8-0002a5d5c51b","write":"TEST","success":true}
read : {"id":"30:AE:A4:7C:3C:A6","service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b","characteristic":"cba20002-224d-11e6-9fb8-0002a5d5c51b","read":"TEST","success":true}

* Add generic BLE connect class and read data as hex.

* Add value_type parameter and document usage

* Add note to docs
This commit is contained in:
h2zero authored and GitHub committed 2021-06-21 20:27:06 -05:00
1 parent ac23235327
commit 7cfbd3d050
5 files changed
+251 -2

No files matched your search

+53 -1
View File
@@ -103,7 +103,7 @@ In this case you should deactivate the BLE connection mechanism to avoid concurr
For certain devices like LYWSD03MMC OpenMQTTGateway use a connection (due to the fact that the advertized data are encrypted), this connection mechanism is launched after every `ScanBeforeConnect` per default, you can modify it by following the procedure below.
:::
## Setting the number of scans between before connect attempt
## Setting the number of scans between connection attempts
If you want to change the number of BLE scans that are done before a BLE connect you can change it by MQTT, if you want the BLE connect to be every 30 scans:
@@ -152,6 +152,58 @@ you can also accept all the devices by the following command:
The default value is set into config_BT.h
## Read/write BLE characteristics over MQTT (ESP32 only)
The gateway can read and write BLE characteristics from devices and provide the results in an MQTT message.
::: tip
These actions will be taken on the next BLE connection, which occurs after scanning and after the scan count is reached, [see above to set this.](#setting-the-number-of-scans-between-connection-attempts)
:::
### Example write command
```
mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{
"ble_write_address":"AA:BB:CC:DD:EE:FF",
"ble_write_service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b",
"ble_write_char":"cba20002-224d-11e6-9fb8-0002a5d5c51b",
"ble_write_value":"TEST",
"value_type":"STRING",
"ttl":4 }'
```
Response:
```
{
"id":"AA:BB:CC:DD:EE:FF",
"service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b",
"characteristic":"cba20002-224d-11e6-9fb8-0002a5d5c51b",
"write":"TEST",
"success":true
}
```
### Example read commnad
```
mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{
"ble_read_address":"AA:BB:CC:DD:EE:FF",
"ble_read_service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b",
"ble_read_char":"cba20002-224d-11e6-9fb8-0002a5d5c51b",
"value_type":"STRING",
"ttl": 2 }'
```
Response:
```
{
"id":"AA:BB:CC:DD:EE:FF",
"service":"cba20d00-224d-11e6-9fb8-0002a5d5c51b",
"characteristic":"cba20002-224d-11e6-9fb8-0002a5d5c51b",
"read":"TEST",
"success":true
}
```
::: tip
The `ttl` parameter is the number of attempts to connect (defaults to 1), which occur after the BLE scan completes.
`value_type` can be one of: STRING, HEX, INT, FLOAT. Default is STRING if omitted in the message.
:::
## Other
To check your hm10 firmware version upload a serial sketch to the nodemcu (this will enable communication directly with the hm10) and launch the command:
+11
View File
@@ -4,6 +4,7 @@
#ifdef ESP32
# include "ArduinoJson.h"
# include "NimBLEDevice.h"
# include "config_BT.h"
extern void pubBT(JsonObject& data);
@@ -13,6 +14,9 @@ public:
TaskHandle_t m_taskHandle = nullptr;
zBLEConnect(NimBLEAddress& addr) { m_pClient = NimBLEDevice::createClient(addr); }
virtual ~zBLEConnect() { NimBLEDevice::deleteClient(m_pClient); }
virtual bool writeData(BLEAction* action);
virtual bool readData(BLEAction* action);
virtual bool processActions(std::vector<BLEAction>& actions);
virtual void publishData() {}
virtual NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& service, const NimBLEUUID& characteristic);
};
@@ -34,5 +38,12 @@ public:
void publishData() override;
};
class GENERIC_connect : public zBLEConnect {
std::vector<uint8_t> m_data;
public:
GENERIC_connect(NimBLEAddress& addr) : zBLEConnect(addr) {}
};
#endif //ESP32
#endif //zBLEConnect_h
+94 -1
View File
@@ -4,7 +4,6 @@
# include "ArduinoJson.h"
# include "ArduinoLog.h"
# include "ZgatewayBLEConnect.h"
# include "config_BT.h"
# define convertTemp_CtoF(c) ((c * 1.8) + 32)
@@ -35,6 +34,99 @@ NimBLERemoteCharacteristic* zBLEConnect::getCharacteristic(const NimBLEUUID& ser
return pRemoteCharacteristic;
}
bool zBLEConnect::writeData(BLEAction* action) {
NimBLERemoteCharacteristic* pChar = getCharacteristic(action->service, action->characteristic);
if (pChar && pChar->canWrite()) {
switch (action->value_type) {
case BLE_VAL_HEX: {
int len = action->value.length();
if (len % 2) {
Log.error(F("Invalid HEX value length" CR));
return false;
}
std::vector<uint8_t> buf;
for (auto i = 0; i < len; i += 2) {
std::string temp = action->value.substr(i, 2);
buf.push_back((uint8_t)strtoul(temp.c_str(), nullptr, 16));
}
return pChar->writeValue((const uint8_t*)&buf[0], buf.size());
}
case BLE_VAL_INT:
return pChar->writeValue(strtol(action->value.c_str(), nullptr, 0));
case BLE_VAL_FLOAT:
return pChar->writeValue(strtod(action->value.c_str(), nullptr));
default:
return pChar->writeValue(action->value);
}
}
return false;
}
bool zBLEConnect::readData(BLEAction* action) {
NimBLERemoteCharacteristic* pChar = getCharacteristic(action->service, action->characteristic);
if (pChar && pChar->canRead()) {
action->value = pChar->readValue();
if (action->value != "") {
return true;
}
}
return false;
}
bool zBLEConnect::processActions(std::vector<BLEAction>& actions) {
bool result = false;
if (actions.size() > 0) {
for (auto& it : actions) {
if (NimBLEAddress(it.addr) == m_pClient->getPeerAddress()) {
JsonObject& BLEresult = getBTJsonObject();
BLEresult.set("id", it.addr);
BLEresult.set("service", (char*)it.service.toString().c_str());
BLEresult.set("characteristic", (char*)it.characteristic.toString().c_str());
if (it.write) {
Log.trace(F("processing BLE write" CR));
BLEresult.set("write", it.value.c_str());
result = writeData(&it);
} else {
Log.trace(F("processing BLE read" CR));
result = readData(&it);
if (result) {
switch (it.value_type) {
case BLE_VAL_HEX: {
char* pHex = NimBLEUtils::buildHexData(nullptr, (uint8_t*)it.value.c_str(), it.value.length());
BLEresult.set("read", pHex);
free(pHex);
break;
}
case BLE_VAL_INT: {
int ival = *(int*)it.value.data();
BLEresult.set<int>("read", ival);
break;
}
case BLE_VAL_FLOAT: {
float fval = *(double*)it.value.data();
BLEresult.set<float>("read", fval);
break;
}
default:
BLEresult.set("read", it.value.c_str());
break;
}
}
}
it.complete = true;
BLEresult.set("success", result);
pubBT(BLEresult);
}
}
}
return result;
}
/*-----------------------LYWSD03MMC && MHO_C401 HANDLING-----------------------*/
void LYWSD03MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (m_taskHandle == nullptr) {
@@ -160,5 +252,6 @@ void DT24_connect::publishData() {
}
}
}
# endif //ZgatewayBT
#endif //ESP32
+71
View File
@@ -71,6 +71,10 @@ struct decompose {
bool reverse;
};
# ifdef ESP32
vector<BLEAction> BLEactions;
# endif
vector<BLEdevice*> devices;
int newDevices = 0;
@@ -728,21 +732,38 @@ void BLEconnect() {
if (p->connect) {
Log.trace(F("Model to connect found: %s" CR), p->macAdr);
NimBLEAddress addr(std::string(p->macAdr));
switch (p->sensorModel) {
case LYWSD03MMC:
case MHO_C401: {
LYWSD03MMC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
break;
}
case DT24: {
DT24_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
break;
}
case GENERIC: {
GENERIC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
break;
}
default:
break;
}
if (BLEactions.size() > 0) {
std::vector<BLEAction> swap;
for (auto& it : BLEactions) {
if (!it.complete && --it.ttl) {
swap.push_back(it);
}
}
std::swap(BLEactions, swap);
}
}
}
Log.notice(F("BLE Connect end" CR));
@@ -1662,6 +1683,54 @@ void BTforceScan() {
}
}
void MQTTtoBTAction(JsonObject& BTdata) {
# ifdef ESP32
BLEAction action;
action.ttl = BTdata.containsKey("ttl") ? (uint8_t)BTdata["ttl"] : 1;
action.value_type = BLE_VAL_STRING;
if (BTdata.containsKey("value_type")) {
String vt = BTdata["value_type"];
vt.toUpperCase();
if (vt == "HEX")
action.value_type = BLE_VAL_HEX;
else if (vt == "INT")
action.value_type = BLE_VAL_INT;
else if (vt == "FLOAT")
action.value_type = BLE_VAL_FLOAT;
else if (vt != "STRING") {
Log.error(F("BLE value type invalid %s" CR), vt.c_str());
return;
}
}
Log.trace(F("BLE ACTION TTL = %u" CR), action.ttl);
action.complete = false;
if (BTdata.containsKey("ble_write_address") &&
BTdata.containsKey("ble_write_service") &&
BTdata.containsKey("ble_write_char") &&
BTdata.containsKey("ble_write_value")) {
strcpy(action.addr, (const char*)BTdata["ble_write_address"]);
action.service = NimBLEUUID((const char*)BTdata["ble_write_service"]);
action.characteristic = NimBLEUUID((const char*)BTdata["ble_write_char"]);
action.value = std::string((const char*)BTdata["ble_write_value"]);
action.write = true;
Log.trace(F("BLE ACTION Write" CR));
} else if (BTdata.containsKey("ble_read_address") &&
BTdata.containsKey("ble_read_service") &&
BTdata.containsKey("ble_read_char")) {
strcpy(action.addr, (const char*)BTdata["ble_read_address"]);
action.service = NimBLEUUID((const char*)BTdata["ble_read_service"]);
action.characteristic = NimBLEUUID((const char*)BTdata["ble_read_char"]);
action.write = false;
Log.trace(F("BLE ACTION Read" CR));
} else {
return;
}
createOrUpdateDevice(action.addr, device_flags_connect, GENERIC);
BLEactions.push_back(action);
# endif
}
void MQTTtoBT(char* topicOri, JsonObject& BTdata) { // json object decoding
if (cmpToMainTopic(topicOri, subjectMQTTtoBTset)) {
Log.trace(F("MQTTtoBT json set" CR));
@@ -1723,6 +1792,8 @@ void MQTTtoBT(char* topicOri, JsonObject& BTdata) { // json object decoding
if (BTdata.containsKey("lowpowermode")) {
changelowpowermode((int)BTdata["lowpowermode"]);
}
MQTTtoBTAction(BTdata);
# endif
// MinRSSI set
if (BTdata.containsKey("minrssi")) {
+22
View File
@@ -46,6 +46,7 @@ bool bleConnect = AttemptBLECOnnect;
# ifndef BLE_FILTER_CONNECTABLE
# define BLE_FILTER_CONNECTABLE 1
# endif
# include "NimBLEDevice.h"
#endif
/*----------------------BT topics & parameters-------------------------*/
@@ -151,6 +152,7 @@ enum ble_sensor_model {
IBT4XS,
DT24,
EDDYSTONE_TLM,
GENERIC,
};
/*-------------------PIN DEFINITIONS----------------------*/
@@ -168,6 +170,26 @@ enum ble_sensor_model {
#endif
/*---------------INTERNAL USE: DO NOT MODIFY--------------*/
#ifdef ESP32
enum ble_val_type {
BLE_VAL_STRING = 0,
BLE_VAL_HEX,
BLE_VAL_INT,
BLE_VAL_FLOAT,
};
struct BLEAction {
std::string value;
char addr[18];
NimBLEUUID service;
NimBLEUUID characteristic;
bool write;
bool complete;
uint8_t ttl;
ble_val_type value_type;
};
#endif
struct BLEdevice {
char macAdr[18];
bool isDisc;