mirror of
https://github.com/1technophile/OpenMQTTGateway.git
synced 2026-02-19 16:21:44 +01:00
* [BLE] Add BM6 Battery Monitor connection support Implement BLE connection support for BM6 Battery Monitor devices, following the BM2 pattern with BM6-specific protocol handling. Key features: - Encrypted command/response using AES-128 CBC - Read voltage, temperature, and State of Charge (SoC) - Home Assistant MQTT Discovery support - Device tracker integration Technical implementation: - Add BM6_connect class with encrypted command handling - Encryption key (static const): "leagend\xff\xfe0100009" - Parse hex string positions for voltage (15-17), temp (8-9), SoC (12-13) - Add discovery with 3 sensors (voltage, temperature, battery %) - Validate message signature (D15507) - Add canWrite() check before writing to characteristic - Empty stub for non-ESP32 builds Code quality improvements: - Extract AES key as static constant to avoid duplication - Use shared jsonVoltBM template for both BM2 and BM6 - Clean up commented-out reasoning in parsing logic Based on reverse engineering from: https://github.com/JeffWDH/bm6-battery-monitor Files modified: - main/gatewayBLEConnect.h: Add BM6_connect class - main/gatewayBLEConnect.cpp: Implement BM6 connection logic - main/gatewayBT.cpp: Add detection, connection, and discovery handlers - main/config_mqttDiscovery.h: Rename jsonVoltBM2 to jsonVoltBM Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add BM6 to exceptions --------- Co-authored-by: Florian <1technophile@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
842 lines
33 KiB
C++
842 lines
33 KiB
C++
#ifdef ESP32
|
|
# include "User_config.h"
|
|
# ifdef ZgatewayBT
|
|
# include "gatewayBLEConnect.h"
|
|
# include "mbedtls/aes.h"
|
|
|
|
# define convertTemp_CtoF(c) ((c * 1.8) + 32)
|
|
|
|
extern std::vector<BLEdevice*> devices;
|
|
extern bool BTProcessLock;
|
|
|
|
NimBLERemoteCharacteristic* zBLEConnect::getCharacteristic(const NimBLEUUID& service,
|
|
const NimBLEUUID& characteristic) {
|
|
BLERemoteCharacteristic* pRemoteCharacteristic = nullptr;
|
|
if (!m_pClient) {
|
|
THEENGS_LOG_ERROR(F("No BLE client" CR));
|
|
} else if (!m_pClient->isConnected() && !m_pClient->connect()) {
|
|
THEENGS_LOG_ERROR(F("Connect to: %s failed" CR), m_pClient->getPeerAddress().toString().c_str());
|
|
} else {
|
|
BLERemoteService* pRemoteService = m_pClient->getService(service);
|
|
if (!pRemoteService) {
|
|
THEENGS_LOG_NOTICE(F("Failed to find service UUID: %s" CR), service.toString().c_str());
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Found service: %s" CR), service.toString().c_str());
|
|
THEENGS_LOG_TRACE(F("Client isConnected, freeHeap: %d" CR), ESP.getFreeHeap());
|
|
pRemoteCharacteristic = pRemoteService->getCharacteristic(characteristic);
|
|
if (!pRemoteCharacteristic) {
|
|
THEENGS_LOG_NOTICE(F("Failed to find characteristic UUID: %s" CR), characteristic.toString().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
return pRemoteCharacteristic;
|
|
}
|
|
|
|
bool zBLEConnect::writeData(BLEAction* action) {
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(action->service, action->characteristic);
|
|
if (pChar && (pChar->canWrite() || pChar->canWriteNoResponse())) {
|
|
switch (action->value_type) {
|
|
case BLE_VAL_HEX: {
|
|
int len = action->value.length();
|
|
if (len % 2) {
|
|
THEENGS_LOG_ERROR(F("Invalid HEX value length" CR));
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> buf;
|
|
buf.reserve(len / 2);
|
|
for (auto i = 0; i < len; i += 2) {
|
|
char sVal[3] = {action->value[i], action->value[i + 1], 0};
|
|
buf.push_back((uint8_t)strtoul(sVal, nullptr, 16));
|
|
}
|
|
return pChar->writeValue(&buf[0], buf.size(), !pChar->canWriteNoResponse());
|
|
}
|
|
case BLE_VAL_INT:
|
|
return pChar->writeValue(strtol(action->value.c_str(), nullptr, 0), !pChar->canWriteNoResponse());
|
|
case BLE_VAL_FLOAT:
|
|
return pChar->writeValue(strtod(action->value.c_str(), nullptr), !pChar->canWriteNoResponse());
|
|
default:
|
|
return pChar->writeValue(action->value, !pChar->canWriteNoResponse());
|
|
}
|
|
}
|
|
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()) {
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["id"] = m_pClient->getPeerAddress().toString();
|
|
BLEdata["service"] = it.service.toString();
|
|
BLEdata["characteristic"] = it.characteristic.toString();
|
|
|
|
if (it.write) {
|
|
THEENGS_LOG_TRACE(F("processing BLE write" CR));
|
|
BLEdata["write"] = std::string(it.value);
|
|
result = writeData(&it);
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("processing BLE read" CR));
|
|
result = readData(&it);
|
|
if (result) {
|
|
switch (it.value_type) {
|
|
case BLE_VAL_HEX: {
|
|
BLEdata["read"] = NimBLEUtils::dataToHexString(it.value.data(), it.value.size());
|
|
break;
|
|
}
|
|
case BLE_VAL_INT: {
|
|
int ival = *(int*)it.value.data();
|
|
BLEdata["read"] = ival;
|
|
break;
|
|
}
|
|
case BLE_VAL_FLOAT: {
|
|
float fval = *(double*)it.value.data();
|
|
BLEdata["read"] = fval;
|
|
break;
|
|
}
|
|
default:
|
|
BLEdata["read"] = it.value.c_str();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
it.complete = result;
|
|
BLEdata["success"] = result;
|
|
if (result || it.ttl <= 1) {
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*-----------------------LYWSD03MMC && MHO_C401 HANDLING-----------------------*/
|
|
void LYWSD03MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
|
|
if (length == 5) {
|
|
THEENGS_LOG_TRACE(F("Device identified creating BLE buffer" CR));
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
auto mac_addr = m_pClient->getPeerAddress().toString();
|
|
for (std::vector<BLEdevice*>::iterator it = devices.begin(); it != devices.end(); ++it) {
|
|
BLEdevice* p = *it;
|
|
if (p->macAdr == mac_addr) {
|
|
if (p->sensorModel_id == BLEconectable::id::LYWSD03MMC)
|
|
BLEdata["model"] = "LYWSD03MMC";
|
|
else if (p->sensorModel_id == BLEconectable::id::MHO_C401)
|
|
BLEdata["model"] = "MHO-C401";
|
|
}
|
|
}
|
|
BLEdata["id"] = mac_addr;
|
|
THEENGS_LOG_TRACE(F("Device identified in CB: %s" CR), mac_addr);
|
|
BLEdata["tempc"] = (float)((pData[0] | (pData[1] << 8)) * 0.01);
|
|
BLEdata["tempf"] = (float)(convertTemp_CtoF((pData[0] | (pData[1] << 8)) * 0.01));
|
|
BLEdata["hum"] = (float)(pData[2]);
|
|
BLEdata["volt"] = (float)(((pData[4] * 256) + pData[3]) / 1000.0);
|
|
BLEdata["batt"] = (float)(((((pData[4] * 256) + pData[3]) / 1000.0) - 2.1) * 100);
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
void LYWSD03MMC_connect::publishData() {
|
|
NimBLEUUID serviceUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6");
|
|
NimBLEUUID charUUID("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6");
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
|
|
if (pChar && pChar->canNotify()) {
|
|
THEENGS_LOG_TRACE(F("Registering notification" CR));
|
|
if (pChar->subscribe(true, std::bind(&LYWSD03MMC_connect::notifyCB, this,
|
|
std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4))) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed registering notification" CR));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------DT24 HANDLING-----------------------*/
|
|
void DT24_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
if (length == 20) {
|
|
m_data.assign(pData, pData + length);
|
|
return;
|
|
} else if (m_data.size() == 20 && length == 16) {
|
|
m_data.insert(m_data.end(), pData, pData + length);
|
|
|
|
// DT24-BLE data format
|
|
// https://github.com/NiceLabs/atorch-console/blob/master/docs/protocol-design.md#dc-meter-report
|
|
// Data comes as two packets ( 20 and 16 ), and am only processing first
|
|
THEENGS_LOG_TRACE(F("Device identified creating BLE buffer" CR));
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
auto mac_address = m_pClient->getPeerAddress().toString();
|
|
BLEdata["model"] = "DT24";
|
|
BLEdata["id"] = mac_address;
|
|
THEENGS_LOG_TRACE(F("Device identified in CB: %s" CR), mac_address);
|
|
BLEdata["volt"] = (float)(((m_data[4] * 256 * 256) + (m_data[5] * 256) + m_data[6]) / 10.0);
|
|
BLEdata["current"] = (float)(((m_data[7] * 256 * 256) + (m_data[8] * 256) + m_data[9]) / 1000.0);
|
|
BLEdata["power"] = (float)(((m_data[10] * 256 * 256) + (m_data[11] * 256) + m_data[12]) / 10.0);
|
|
BLEdata["energy"] = (float)(((m_data[13] * 256 * 256 * 256) + (m_data[14] * 256 * 256) + (m_data[15] * 256) + m_data[16]) / 100.0);
|
|
BLEdata["price"] = (float)(((m_data[17] * 256 * 256) + (m_data[18] * 256) + m_data[19]) / 100.0);
|
|
BLEdata["tempc"] = (float)(m_data[24] * 256) + m_data[25];
|
|
BLEdata["tempf"] = (float)(convertTemp_CtoF((m_data[24] * 256) + m_data[25]));
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
void DT24_connect::publishData() {
|
|
NimBLEUUID serviceUUID("ffe0");
|
|
NimBLEUUID charUUID("ffe1");
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
|
|
if (pChar && pChar->canNotify()) {
|
|
THEENGS_LOG_TRACE(F("Registering notification" CR));
|
|
if (pChar->subscribe(true, std::bind(&DT24_connect::notifyCB, this,
|
|
std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4))) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed registering notification" CR));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------BM2 HANDLING-----------------------*/
|
|
void BM2_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
if (length == 16) {
|
|
THEENGS_LOG_TRACE(F("Device identified creating BLE buffer" CR));
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["model"] = "BM2 Battery Monitor";
|
|
BLEdata["id"] = m_pClient->getPeerAddress().toString();
|
|
mbedtls_aes_context aes;
|
|
mbedtls_aes_init(&aes);
|
|
unsigned char output[16];
|
|
unsigned char iv[16] = {};
|
|
unsigned char key[16] = {
|
|
108,
|
|
101,
|
|
97,
|
|
103,
|
|
101,
|
|
110,
|
|
100,
|
|
255,
|
|
254,
|
|
49,
|
|
56,
|
|
56,
|
|
50,
|
|
52,
|
|
54,
|
|
54,
|
|
};
|
|
mbedtls_aes_setkey_dec(&aes, key, 128);
|
|
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, 16, iv, (uint8_t*)&pData[0], output);
|
|
mbedtls_aes_free(&aes);
|
|
float volt = ((output[2] | (output[1] << 8)) >> 4) / 100.0f;
|
|
BLEdata["volt"] = volt;
|
|
THEENGS_LOG_TRACE(F("volt: %F" CR), volt);
|
|
// to avoid the BM2 device tracker going offline because of the voltage MQTT message without an RSSI value
|
|
BLEdata["rssi"] = -60;
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
void BM2_connect::publishData() {
|
|
NimBLEUUID serviceUUID("fff0");
|
|
NimBLEUUID charUUID("fff4");
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
|
|
if (pChar && pChar->canNotify()) {
|
|
THEENGS_LOG_TRACE(F("Registering notification" CR));
|
|
if (pChar->subscribe(true, std::bind(&BM2_connect::notifyCB, this,
|
|
std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4))) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed registering notification" CR));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------BM6 HANDLING-----------------------*/
|
|
// BM6 AES encryption key: "leagend\xff\xfe0100009"
|
|
static const unsigned char BM6_AES_KEY[16] = {
|
|
108, // l
|
|
101, // e
|
|
97, // a
|
|
103, // g
|
|
101, // e
|
|
110, // n
|
|
100, // d
|
|
255, // 0xff
|
|
254, // 0xfe
|
|
48, // 0
|
|
49, // 1
|
|
48, // 0
|
|
48, // 0
|
|
48, // 0
|
|
48, // 0
|
|
57, // 9
|
|
};
|
|
|
|
void BM6_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
if (length == 16) {
|
|
THEENGS_LOG_TRACE(F("Device identified creating BLE buffer" CR));
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["model"] = "BM6 Battery Monitor";
|
|
BLEdata["id"] = m_pClient->getPeerAddress().toString();
|
|
mbedtls_aes_context aes;
|
|
mbedtls_aes_init(&aes);
|
|
unsigned char output[16];
|
|
unsigned char iv[16] = {};
|
|
mbedtls_aes_setkey_dec(&aes, BM6_AES_KEY, 128);
|
|
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, 16, iv, (uint8_t*)&pData[0], output);
|
|
mbedtls_aes_free(&aes);
|
|
|
|
// Check for valid message signature (should start with D15507)
|
|
if (output[0] != 0xd1 || output[1] != 0x55 || output[2] != 0x07) {
|
|
THEENGS_LOG_NOTICE(F("BM6 invalid message signature" CR));
|
|
xTaskNotifyGive(m_taskHandle);
|
|
return;
|
|
}
|
|
|
|
// Parse according to BM6 protocol (from https://github.com/JeffWDH/bm6-battery-monitor)
|
|
// The decrypted data is converted to a hex string for parsing:
|
|
// - Voltage: hex string positions 15-17 (3 chars) / 100
|
|
// - Temperature: hex string positions 8-9 (2 chars), negated if positions 6-7 == "01"
|
|
// - SoC (State of Charge): hex string positions 12-13 (2 chars)
|
|
char hexstr[33];
|
|
sprintf(hexstr, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
|
output[0], output[1], output[2], output[3], output[4], output[5], output[6], output[7],
|
|
output[8], output[9], output[10], output[11], output[12], output[13], output[14], output[15]);
|
|
|
|
// Extract voltage (positions 15-17 in hex string)
|
|
char volt_str[4] = {hexstr[15], hexstr[16], hexstr[17], 0};
|
|
int volt_raw = (int)strtol(volt_str, NULL, 16);
|
|
float volt = volt_raw / 100.0f;
|
|
|
|
// Extract SoC (positions 12-13 in hex string)
|
|
char soc_str[3] = {hexstr[12], hexstr[13], 0};
|
|
int soc = (int)strtol(soc_str, NULL, 16);
|
|
|
|
// Extract temperature (positions 8-9 in hex string)
|
|
char temp_str[3] = {hexstr[8], hexstr[9], 0};
|
|
int temp_raw = (int)strtol(temp_str, NULL, 16);
|
|
|
|
// Check temperature sign (positions 6-7 in hex string)
|
|
char temp_sign_str[3] = {hexstr[6], hexstr[7], 0};
|
|
float temp = (strcmp(temp_sign_str, "01") == 0) ? -temp_raw : temp_raw;
|
|
|
|
BLEdata["tempc"] = temp;
|
|
BLEdata["tempf"] = (float)convertTemp_CtoF(temp);
|
|
BLEdata["volt"] = volt;
|
|
BLEdata["batt"] = soc;
|
|
// to avoid the BM6 device tracker going offline because of the voltage/temp MQTT message without an RSSI value
|
|
BLEdata["rssi"] = -60;
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
void BM6_connect::publishData() {
|
|
NimBLEUUID serviceUUID("fff0");
|
|
NimBLEUUID charWriteUUID("fff3");
|
|
NimBLEUUID charNotifyUUID("fff4");
|
|
|
|
// Get write characteristic to send command
|
|
NimBLERemoteCharacteristic* pCharWrite = getCharacteristic(serviceUUID, charWriteUUID);
|
|
// Get notify characteristic to receive data
|
|
NimBLERemoteCharacteristic* pCharNotify = getCharacteristic(serviceUUID, charNotifyUUID);
|
|
|
|
if (pCharWrite && (pCharWrite->canWrite() || pCharWrite->canWriteNoResponse()) &&
|
|
pCharNotify && pCharNotify->canNotify()) {
|
|
THEENGS_LOG_TRACE(F("Registering notification" CR));
|
|
if (pCharNotify->subscribe(true, std::bind(&BM6_connect::notifyCB, this,
|
|
std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4))) {
|
|
// Encrypt the command before sending (BM6 requires encrypted commands)
|
|
uint8_t command_plain[16] = {0xd1, 0x55, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
uint8_t command_encrypted[16];
|
|
unsigned char iv[16] = {};
|
|
|
|
mbedtls_aes_context aes;
|
|
mbedtls_aes_init(&aes);
|
|
mbedtls_aes_setkey_enc(&aes, BM6_AES_KEY, 128);
|
|
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, 16, iv, command_plain, command_encrypted);
|
|
mbedtls_aes_free(&aes);
|
|
|
|
if (pCharWrite->writeValue(command_encrypted, 16, true)) { // Use write with response
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed to send command" CR));
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed registering notification" CR));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------HHCCJCY01HHCC HANDLING-----------------------*/
|
|
void HHCCJCY01HHCC_connect::publishData() {
|
|
NimBLEUUID serviceUUID("00001204-0000-1000-8000-00805f9b34fb");
|
|
NimBLEUUID charUUID("00001a00-0000-1000-8000-00805f9b34fb");
|
|
NimBLEUUID charUUID2("00001a02-0000-1000-8000-00805f9b34fb");
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
|
|
if (pChar) {
|
|
THEENGS_LOG_TRACE(F("Read mode" CR));
|
|
uint8_t buf[2] = {0xA0, 0x1F};
|
|
pChar->writeValue(buf, 2, true);
|
|
int batteryValue = -1;
|
|
NimBLERemoteCharacteristic* pChar2 = getCharacteristic(serviceUUID, charUUID2);
|
|
if (pChar2) {
|
|
std::string value;
|
|
value = pChar2->readValue();
|
|
const char* val2 = value.c_str();
|
|
batteryValue = val2[0];
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["model"] = "HHCCJCY01HHCC";
|
|
BLEdata["id"] = m_pClient->getPeerAddress().toString();
|
|
BLEdata["batt"] = (int)batteryValue;
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed getting characteristic" CR));
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed getting characteristic" CR));
|
|
}
|
|
}
|
|
|
|
/*-----------------------XMWSDJ04MMC HANDLING-----------------------*/
|
|
void XMWSDJ04MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
|
|
if (length == 6) {
|
|
THEENGS_LOG_TRACE(F("Device identified creating BLE buffer" CR));
|
|
DynamicJsonDocument BLEdataBuffer(JSON_MSG_BUFFER);
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
auto mac_address = m_pClient->getPeerAddress().toString();
|
|
BLEdata["model"] = "XMWSDJ04MMC";
|
|
BLEdata["id"] = mac_address;
|
|
THEENGS_LOG_TRACE(F("Device identified in CB: %s" CR), mac_address);
|
|
BLEdata["tempc"] = (float)((pData[0] | (pData[1] << 8)) * 0.1);
|
|
BLEdata["tempf"] = (float)(convertTemp_CtoF((pData[0] | (pData[1] << 8)) * 0.1));
|
|
BLEdata["hum"] = (float)((pData[2] | (pData[3] << 8)) * 0.1);
|
|
BLEdata["volt"] = (float)((pData[4] | (pData[5] << 8)) / 1000.0);
|
|
BLEdata["batt"] = (float)((((pData[4] | (pData[5] << 8)) / 1000.0) - 2.1) * 100);
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
void XMWSDJ04MMC_connect::publishData() {
|
|
NimBLEUUID serviceUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6");
|
|
NimBLEUUID charUUID("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6");
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
|
|
if (pChar && pChar->canNotify()) {
|
|
THEENGS_LOG_TRACE(F("Registering notification" CR));
|
|
if (pChar->subscribe(true, std::bind(&XMWSDJ04MMC_connect::notifyCB, this,
|
|
std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4))) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Failed registering notification" CR));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------SBS1 HANDLING-----------------------*/
|
|
void SBS1_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
|
|
if (length) {
|
|
m_notifyVal = *pData;
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
bool SBS1_connect::processActions(std::vector<BLEAction>& actions) {
|
|
NimBLEUUID serviceUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID charUUID("cba20002-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID notifyCharUUID("cba20003-224d-11e6-9fb8-0002a5d5c51b");
|
|
static byte ON[] = {0x57, 0x01, 0x01};
|
|
static byte OFF[] = {0x57, 0x01, 0x02};
|
|
static byte PRESS[] = {0x57, 0x01, 0x00};
|
|
static byte DOWN[] = {0x57, 0x01, 0x03};
|
|
static byte UP[] = {0x57, 0x01, 0x04};
|
|
|
|
bool result = false;
|
|
if (actions.size() > 0) {
|
|
for (auto& it : actions) {
|
|
if (it.addr == m_pClient->getPeerAddress()) {
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
NimBLERemoteCharacteristic* pNotifyChar = getCharacteristic(serviceUUID, notifyCharUUID);
|
|
|
|
if (it.write && pChar && pNotifyChar) {
|
|
THEENGS_LOG_TRACE(F("processing Switchbot %s" CR), it.value.c_str());
|
|
if (pNotifyChar->subscribe(true,
|
|
std::bind(&SBS1_connect::notifyCB,
|
|
this, std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4),
|
|
true)) {
|
|
if (it.value == "on") {
|
|
result = pChar->writeValue(ON, 3, false);
|
|
} else if (it.value == "off") {
|
|
result = pChar->writeValue(OFF, 3, false);
|
|
} else if (it.value == "press") {
|
|
result = pChar->writeValue(PRESS, 3, false);
|
|
} else if (it.value == "down") {
|
|
result = pChar->writeValue(DOWN, 3, false);
|
|
} else if (it.value == "up") {
|
|
result = pChar->writeValue(UP, 3, false);
|
|
}
|
|
|
|
if (result) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
result = m_notifyVal == 0x01;
|
|
}
|
|
}
|
|
}
|
|
|
|
it.complete = result;
|
|
if (result || it.ttl <= 1) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> BLEdataBuffer;
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["id"] = m_pClient->getPeerAddress().toString();
|
|
BLEdata["state"] = std::string(it.value);
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*-----------------------SBBT HANDLING-----------------------*/
|
|
void SBBT_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
|
|
if (length) {
|
|
m_notifyVal = *pData;
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
bool SBBT_connect::processActions(std::vector<BLEAction>& actions) {
|
|
NimBLEUUID serviceUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID charUUID("cba20002-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID notifyCharUUID("cba20003-224d-11e6-9fb8-0002a5d5c51b");
|
|
static byte OPEN[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x32};
|
|
static byte CLOSE_DOWN[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x00};
|
|
static byte CLOSE_UP[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x64};
|
|
static byte MOVE[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x00};
|
|
static byte STOP[] = {0x57, 0x0f, 0x45, 0x01, 0x00, 0x01};
|
|
|
|
bool result = false;
|
|
if (actions.size() > 0) {
|
|
for (auto& it : actions) {
|
|
if (NimBLEAddress(it.addr) == m_pClient->getPeerAddress()) {
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
NimBLERemoteCharacteristic* pNotifyChar = getCharacteristic(serviceUUID, notifyCharUUID);
|
|
int value = -99;
|
|
if (it.value_type == BLE_VAL_INT) {
|
|
value = std::stoi(it.value);
|
|
}
|
|
if (it.write && pChar && pNotifyChar) {
|
|
THEENGS_LOG_TRACE(F("processing Switchbot %s" CR), it.value.c_str());
|
|
if (pNotifyChar->subscribe(true,
|
|
std::bind(&SBBT_connect::notifyCB,
|
|
this, std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4),
|
|
true)) {
|
|
if (it.value == "open" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(OPEN, 7, false);
|
|
value = 50;
|
|
} else if (it.value == "close_down" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(CLOSE_DOWN, 7, false);
|
|
value = 0;
|
|
} else if (it.value == "close_up" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(CLOSE_UP, 7, false);
|
|
value = 100;
|
|
} else if (it.value == "stop" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(STOP, 6, false);
|
|
} else if (it.value_type == BLE_VAL_INT) {
|
|
if (value >= 0 && value <= 100) {
|
|
byte posByte = (byte)value;
|
|
MOVE[6] = posByte;
|
|
result = pChar->writeValue(MOVE, 7, false);
|
|
}
|
|
} else if (value == -1 && it.value_type == BLE_VAL_INT) {
|
|
result = pChar->writeValue(STOP, 6, false);
|
|
}
|
|
|
|
if (result) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
result = m_notifyVal == 0x01;
|
|
}
|
|
}
|
|
}
|
|
|
|
it.complete = result;
|
|
if (result || it.ttl <= 1) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> BLEdataBuffer;
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["id"] = it.addr.toString();
|
|
if (value != -99 || value != -1)
|
|
BLEdata["tilt"] = value;
|
|
if (value == 50) {
|
|
BLEdata["open"] = 100;
|
|
BLEdata["direction"] = "-";
|
|
} else if (value < 50 && value >= 0) {
|
|
BLEdata["open"] = value * 2;
|
|
BLEdata["direction"] = "down";
|
|
} else if (value > 50 && value <= 100) {
|
|
BLEdata["open"] = (100 - value) * 2;
|
|
BLEdata["direction"] = "up";
|
|
}
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*-----------------------SBCU HANDLING-----------------------*/
|
|
void SBCU_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
|
if (m_taskHandle == nullptr) {
|
|
return; // unexpected notification
|
|
}
|
|
if (!BTProcessLock) {
|
|
THEENGS_LOG_TRACE(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
|
|
|
|
if (length) {
|
|
m_notifyVal = *pData;
|
|
} else {
|
|
THEENGS_LOG_NOTICE(F("Invalid notification data" CR));
|
|
return;
|
|
}
|
|
} else {
|
|
THEENGS_LOG_TRACE(F("Callback process canceled by BTProcessLock" CR));
|
|
}
|
|
|
|
xTaskNotifyGive(m_taskHandle);
|
|
}
|
|
|
|
bool SBCU_connect::processActions(std::vector<BLEAction>& actions) {
|
|
NimBLEUUID serviceUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID charUUID("cba20002-224d-11e6-9fb8-0002a5d5c51b");
|
|
NimBLEUUID notifyCharUUID("cba20003-224d-11e6-9fb8-0002a5d5c51b");
|
|
static byte CLOSE[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x64};
|
|
static byte OPEN[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x00};
|
|
static byte MOVE[] = {0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x00};
|
|
static byte STOP[] = {0x57, 0x0f, 0x45, 0x01, 0x00, 0x01};
|
|
|
|
bool result = false;
|
|
if (actions.size() > 0) {
|
|
for (auto& it : actions) {
|
|
if (NimBLEAddress(it.addr) == m_pClient->getPeerAddress()) {
|
|
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
|
|
NimBLERemoteCharacteristic* pNotifyChar = getCharacteristic(serviceUUID, notifyCharUUID);
|
|
int value = -99;
|
|
if (it.value_type == BLE_VAL_INT) {
|
|
value = std::stoi(it.value);
|
|
}
|
|
if (it.write && pChar && pNotifyChar) {
|
|
THEENGS_LOG_TRACE(F("processing Switchbot %s" CR), it.value.c_str());
|
|
if (pNotifyChar->subscribe(true,
|
|
std::bind(&SBCU_connect::notifyCB,
|
|
this, std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4),
|
|
true)) {
|
|
if (it.value == "open" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(OPEN, 7, false);
|
|
value = 100;
|
|
} else if (it.value == "close" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(CLOSE, 7, false);
|
|
value = 0;
|
|
} else if (it.value == "stop" && it.value_type == BLE_VAL_STRING) {
|
|
result = pChar->writeValue(STOP, 6, false);
|
|
} else if (it.value_type == BLE_VAL_INT) {
|
|
if (value >= 0 && value <= 100) {
|
|
byte posByte = (byte)value;
|
|
MOVE[6] = posByte;
|
|
result = pChar->writeValue(MOVE, 7, false);
|
|
}
|
|
} else if (value == -1 && it.value_type == BLE_VAL_INT) {
|
|
result = pChar->writeValue(STOP, 6, false);
|
|
}
|
|
|
|
if (result) {
|
|
m_taskHandle = xTaskGetCurrentTaskHandle();
|
|
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
|
|
m_taskHandle = nullptr;
|
|
}
|
|
result = m_notifyVal == 0x01;
|
|
}
|
|
}
|
|
}
|
|
|
|
it.complete = result;
|
|
if (result || it.ttl <= 1) {
|
|
StaticJsonDocument<JSON_MSG_BUFFER> BLEdataBuffer;
|
|
JsonObject BLEdata = BLEdataBuffer.to<JsonObject>();
|
|
BLEdata["id"] = it.addr.toString();
|
|
if (value != -99 || value != -1)
|
|
BLEdata["position"] = value;
|
|
buildTopicFromId(BLEdata, subjectBTtoMQTT);
|
|
enqueueJsonObject(BLEdata, QueueSemaphoreTimeOutTask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
# endif //ZgatewayBT
|
|
#endif //ESP32
|