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:
1 parent
ac23235327
commit
7cfbd3d050
5 files changed
+251
-2
No files matched your search
+53
-1
@@ -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.
|
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:
|
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
|
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
|
## 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:
|
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:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
# include "ArduinoJson.h"
|
# include "ArduinoJson.h"
|
||||||
# include "NimBLEDevice.h"
|
# include "NimBLEDevice.h"
|
||||||
|
# include "config_BT.h"
|
||||||
|
|
||||||
extern void pubBT(JsonObject& data);
|
extern void pubBT(JsonObject& data);
|
||||||
|
|
||||||
@@ -13,6 +14,9 @@ public:
|
|||||||
TaskHandle_t m_taskHandle = nullptr;
|
TaskHandle_t m_taskHandle = nullptr;
|
||||||
zBLEConnect(NimBLEAddress& addr) { m_pClient = NimBLEDevice::createClient(addr); }
|
zBLEConnect(NimBLEAddress& addr) { m_pClient = NimBLEDevice::createClient(addr); }
|
||||||
virtual ~zBLEConnect() { NimBLEDevice::deleteClient(m_pClient); }
|
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 void publishData() {}
|
||||||
virtual NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& service, const NimBLEUUID& characteristic);
|
virtual NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& service, const NimBLEUUID& characteristic);
|
||||||
};
|
};
|
||||||
@@ -34,5 +38,12 @@ public:
|
|||||||
void publishData() override;
|
void publishData() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GENERIC_connect : public zBLEConnect {
|
||||||
|
std::vector<uint8_t> m_data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GENERIC_connect(NimBLEAddress& addr) : zBLEConnect(addr) {}
|
||||||
|
};
|
||||||
|
|
||||||
#endif //ESP32
|
#endif //ESP32
|
||||||
#endif //zBLEConnect_h
|
#endif //zBLEConnect_h
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
# include "ArduinoJson.h"
|
# include "ArduinoJson.h"
|
||||||
# include "ArduinoLog.h"
|
# include "ArduinoLog.h"
|
||||||
# include "ZgatewayBLEConnect.h"
|
# include "ZgatewayBLEConnect.h"
|
||||||
# include "config_BT.h"
|
|
||||||
|
|
||||||
# define convertTemp_CtoF(c) ((c * 1.8) + 32)
|
# define convertTemp_CtoF(c) ((c * 1.8) + 32)
|
||||||
|
|
||||||
@@ -35,6 +34,99 @@ NimBLERemoteCharacteristic* zBLEConnect::getCharacteristic(const NimBLEUUID& ser
|
|||||||
return pRemoteCharacteristic;
|
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-----------------------*/
|
/*-----------------------LYWSD03MMC && MHO_C401 HANDLING-----------------------*/
|
||||||
void LYWSD03MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
void LYWSD03MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||||
if (m_taskHandle == nullptr) {
|
if (m_taskHandle == nullptr) {
|
||||||
@@ -160,5 +252,6 @@ void DT24_connect::publishData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# endif //ZgatewayBT
|
# endif //ZgatewayBT
|
||||||
#endif //ESP32
|
#endif //ESP32
|
||||||
@@ -71,6 +71,10 @@ struct decompose {
|
|||||||
bool reverse;
|
bool reverse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ifdef ESP32
|
||||||
|
vector<BLEAction> BLEactions;
|
||||||
|
# endif
|
||||||
|
|
||||||
vector<BLEdevice*> devices;
|
vector<BLEdevice*> devices;
|
||||||
int newDevices = 0;
|
int newDevices = 0;
|
||||||
|
|
||||||
@@ -728,21 +732,38 @@ void BLEconnect() {
|
|||||||
if (p->connect) {
|
if (p->connect) {
|
||||||
Log.trace(F("Model to connect found: %s" CR), p->macAdr);
|
Log.trace(F("Model to connect found: %s" CR), p->macAdr);
|
||||||
NimBLEAddress addr(std::string(p->macAdr));
|
NimBLEAddress addr(std::string(p->macAdr));
|
||||||
|
|
||||||
switch (p->sensorModel) {
|
switch (p->sensorModel) {
|
||||||
case LYWSD03MMC:
|
case LYWSD03MMC:
|
||||||
case MHO_C401: {
|
case MHO_C401: {
|
||||||
LYWSD03MMC_connect BLEclient(addr);
|
LYWSD03MMC_connect BLEclient(addr);
|
||||||
|
BLEclient.processActions(BLEactions);
|
||||||
BLEclient.publishData();
|
BLEclient.publishData();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DT24: {
|
case DT24: {
|
||||||
DT24_connect BLEclient(addr);
|
DT24_connect BLEclient(addr);
|
||||||
|
BLEclient.processActions(BLEactions);
|
||||||
BLEclient.publishData();
|
BLEclient.publishData();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case GENERIC: {
|
||||||
|
GENERIC_connect BLEclient(addr);
|
||||||
|
BLEclient.processActions(BLEactions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
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));
|
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
|
void MQTTtoBT(char* topicOri, JsonObject& BTdata) { // json object decoding
|
||||||
if (cmpToMainTopic(topicOri, subjectMQTTtoBTset)) {
|
if (cmpToMainTopic(topicOri, subjectMQTTtoBTset)) {
|
||||||
Log.trace(F("MQTTtoBT json set" CR));
|
Log.trace(F("MQTTtoBT json set" CR));
|
||||||
@@ -1723,6 +1792,8 @@ void MQTTtoBT(char* topicOri, JsonObject& BTdata) { // json object decoding
|
|||||||
if (BTdata.containsKey("lowpowermode")) {
|
if (BTdata.containsKey("lowpowermode")) {
|
||||||
changelowpowermode((int)BTdata["lowpowermode"]);
|
changelowpowermode((int)BTdata["lowpowermode"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MQTTtoBTAction(BTdata);
|
||||||
# endif
|
# endif
|
||||||
// MinRSSI set
|
// MinRSSI set
|
||||||
if (BTdata.containsKey("minrssi")) {
|
if (BTdata.containsKey("minrssi")) {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ bool bleConnect = AttemptBLECOnnect;
|
|||||||
# ifndef BLE_FILTER_CONNECTABLE
|
# ifndef BLE_FILTER_CONNECTABLE
|
||||||
# define BLE_FILTER_CONNECTABLE 1
|
# define BLE_FILTER_CONNECTABLE 1
|
||||||
# endif
|
# endif
|
||||||
|
# include "NimBLEDevice.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*----------------------BT topics & parameters-------------------------*/
|
/*----------------------BT topics & parameters-------------------------*/
|
||||||
@@ -151,6 +152,7 @@ enum ble_sensor_model {
|
|||||||
IBT4XS,
|
IBT4XS,
|
||||||
DT24,
|
DT24,
|
||||||
EDDYSTONE_TLM,
|
EDDYSTONE_TLM,
|
||||||
|
GENERIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*-------------------PIN DEFINITIONS----------------------*/
|
/*-------------------PIN DEFINITIONS----------------------*/
|
||||||
@@ -168,6 +170,26 @@ enum ble_sensor_model {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*---------------INTERNAL USE: DO NOT MODIFY--------------*/
|
/*---------------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 {
|
struct BLEdevice {
|
||||||
char macAdr[18];
|
char macAdr[18];
|
||||||
bool isDisc;
|
bool isDisc;
|
||||||
|
|||||||
Reference in new issue
Block a user