From 7cfbd3d0500b5d11d42b2e17124bee869c1674ec Mon Sep 17 00:00:00 2001 From: h2zero <32826625+h2zero@users.noreply.github.com> Date: Mon, 21 Jun 2021 19:27:06 -0600 Subject: [PATCH] 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 --- docs/use/ble.md | 54 ++++++++++++++++++++- main/ZgatewayBLEConnect.h | 11 +++++ main/ZgatewayBLEConnect.ino | 95 ++++++++++++++++++++++++++++++++++++- main/ZgatewayBT.ino | 71 +++++++++++++++++++++++++++ main/config_BT.h | 22 +++++++++ 5 files changed, 251 insertions(+), 2 deletions(-) diff --git a/docs/use/ble.md b/docs/use/ble.md index 4c9c860..0f7154a 100644 --- a/docs/use/ble.md +++ b/docs/use/ble.md @@ -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: diff --git a/main/ZgatewayBLEConnect.h b/main/ZgatewayBLEConnect.h index ccf703c..3ac1221 100644 --- a/main/ZgatewayBLEConnect.h +++ b/main/ZgatewayBLEConnect.h @@ -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& 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 m_data; + +public: + GENERIC_connect(NimBLEAddress& addr) : zBLEConnect(addr) {} +}; + #endif //ESP32 #endif //zBLEConnect_h diff --git a/main/ZgatewayBLEConnect.ino b/main/ZgatewayBLEConnect.ino index 5db595d..18cd07e 100644 --- a/main/ZgatewayBLEConnect.ino +++ b/main/ZgatewayBLEConnect.ino @@ -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 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& 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("read", ival); + break; + } + case BLE_VAL_FLOAT: { + float fval = *(double*)it.value.data(); + BLEresult.set("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 diff --git a/main/ZgatewayBT.ino b/main/ZgatewayBT.ino index 19c5751..c8b36cf 100644 --- a/main/ZgatewayBT.ino +++ b/main/ZgatewayBT.ino @@ -71,6 +71,10 @@ struct decompose { bool reverse; }; +# ifdef ESP32 +vector BLEactions; +# endif + vector 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 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")) { diff --git a/main/config_BT.h b/main/config_BT.h index de60797..9a6fd38 100644 --- a/main/config_BT.h +++ b/main/config_BT.h @@ -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;