[BLE] Add BM6 Battery Monitor connection support (#2274)

* [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>
This commit is contained in:
authored and GitHub committed 2026-01-24 17:16:03 -06:00
1 parent 497dc878f5
commit fd433c220e
4 files changed
+184 -5

No files matched your search

+38 -4
View File
@@ -514,7 +514,7 @@ void BM2Discovery(const char* mac, const char* sensorModel_id) {
# define BM2parametersCount 2
THEENGS_LOG_TRACE(F("BM2Discovery" CR));
const char* BM2sensor[BM2parametersCount][9] = {
{HASS_TYPE_SENSOR, "volt", mac, HASS_CLASS_VOLTAGE, jsonVoltBM2, "", "", HASS_UNIT_VOLT, stateClassMeasurement}, // We use a json definition that retrieve only data from the BM2 decoder, as this sensor also advertize volt as an iBeacon
{HASS_TYPE_SENSOR, "volt", mac, HASS_CLASS_VOLTAGE, jsonVoltBM, "", "", HASS_UNIT_VOLT, stateClassMeasurement}, // We use a json definition that retrieve only data from the BM decoder, as this sensor also advertizes volt as an iBeacon
{HASS_TYPE_SENSOR, "batt", mac, HASS_CLASS_BATTERY, jsonBatt, "", "", HASS_UNIT_PERCENT, stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
@@ -522,6 +522,19 @@ void BM2Discovery(const char* mac, const char* sensorModel_id) {
createDiscoveryFromList(mac, BM2sensor, BM2parametersCount, "BM2", "Generic", sensorModel_id);
}
void BM6Discovery(const char* mac, const char* sensorModel_id) {
# define BM6parametersCount 3
THEENGS_LOG_TRACE(F("BM6Discovery" CR));
const char* BM6sensor[BM6parametersCount][9] = {
{HASS_TYPE_SENSOR, "volt", mac, HASS_CLASS_VOLTAGE, jsonVoltBM, "", "", HASS_UNIT_VOLT, stateClassMeasurement}, // We use a json definition that retrieve only data from the BM decoder, as this sensor also advertizes volt as an iBeacon
{HASS_TYPE_SENSOR, "temp", mac, HASS_CLASS_TEMPERATURE, jsonTempc, "", "", HASS_UNIT_CELSIUS, stateClassMeasurement},
{HASS_TYPE_SENSOR, "batt", mac, HASS_CLASS_BATTERY, jsonBatt, "", "", HASS_UNIT_PERCENT, stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, BM6sensor, BM6parametersCount, "BM6", "Generic", sensorModel_id);
}
void LYWSD03MMCDiscovery(const char* mac, const char* sensorModel) {
# define LYWSD03MMCparametersCount 4
THEENGS_LOG_TRACE(F("LYWSD03MMCDiscovery" CR));
@@ -585,6 +598,7 @@ void MHO_C401Discovery(const char* mac, const char* sensorModel) {}
void HHCCJCY01HHCCDiscovery(const char* mac, const char* sensorModel) {}
void DT24Discovery(const char* mac, const char* sensorModel_id) {}
void BM2Discovery(const char* mac, const char* sensorModel_id) {}
void BM6Discovery(const char* mac, const char* sensorModel_id) {}
void XMWSDJ04MMCDiscovery(const char* mac, const char* sensorModel_id) {}
# endif
@@ -747,6 +761,10 @@ void BLEconnect() {
BM2_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
} else if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::BM6) {
BM6_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
} else if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC) {
HHCCJCY01HHCC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
@@ -782,6 +800,7 @@ void BLEconnect() {
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC &&
p->sensorModel_id != BLEconectable::id::LYWSD03MMC &&
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::BM2 &&
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::BM6 &&
p->sensorModel_id != BLEconectable::id::MHO_C401 &&
p->sensorModel_id != BLEconectable::id::XMWSDJ04MMC) {
// if irregulary connected to and connection failed clear the connect flag.
@@ -973,7 +992,9 @@ void launchBTDiscovery(bool overrideDiscovery) {
if (!BTConfig.extDecoderEnable && // Do not decode if an external decoder is configured
p->sensorModel_id > UNKWNON_MODEL &&
p->sensorModel_id < TheengsDecoder::BLE_ID_NUM::BLE_ID_MAX &&
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC && p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::BM2) { // Exception on HHCCJCY01HHCC and BM2 as these ones are discoverable and connectable
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC &&
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::BM2 &&
p->sensorModel_id != TheengsDecoder::BLE_ID_NUM::BM6) { // Exception on HHCCJCY01HHCC and BM2/BM6 as these ones are discoverable and connectable
if (isTracker) {
String tracker_name = String(model_id.c_str()) + "-tracker";
String tracker_id = macWOdots + "-tracker";
@@ -1124,7 +1145,7 @@ void launchBTDiscovery(bool overrideDiscovery) {
} else {
if ((p->sensorModel_id > BLEconectable::id::MIN &&
p->sensorModel_id < BLEconectable::id::MAX) ||
p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC || p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::BM2) {
p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC || p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::BM2 || p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::BM6) {
// Discovery of sensors from which we retrieve data only by connect
if (p->sensorModel_id == BLEconectable::id::DT24_BLE) {
DT24Discovery(macWOdots.c_str(), "DT24-BLE");
@@ -1142,6 +1163,19 @@ void launchBTDiscovery(bool overrideDiscovery) {
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false,
stateClassNone);
}
if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::BM6) {
// Sensor discovery
BM6Discovery(macWOdots.c_str(), "BM6");
// Device tracker discovery
String tracker_id = macWOdots + "-tracker";
createDiscovery(HASS_TYPE_DEVICE_TRACKER,
discovery_topic.c_str(), "BM6-tracker", tracker_id.c_str(),
will_Topic, "occupancy", "{% if value_json.get('rssi') -%}home{%- else -%}not_home{%- endif %}",
"", "", "",
0, "", "", false, "",
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false,
stateClassNone);
}
if (p->sensorModel_id == BLEconectable::id::LYWSD03MMC) {
LYWSD03MMCDiscovery(macWOdots.c_str(), "LYWSD03MMC");
}
@@ -1429,7 +1463,7 @@ void process_bledata(JsonObject& BLEdata) {
if ((BLEdata["type"].as<string>()).compare("RMAC") != 0 && model_id != TheengsDecoder::BLE_ID_NUM::IBEACON) { // Do not store in memory the random mac devices and iBeacons
if (model_id >= 0) { // Broadcaster devices
THEENGS_LOG_TRACE(F("Decoder found device: %s" CR), BLEdata["model_id"].as<const char*>());
if (model_id == TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC || model_id == TheengsDecoder::BLE_ID_NUM::BM2) { // Device that broadcast and can be connected
if (model_id == TheengsDecoder::BLE_ID_NUM::HHCCJCY01HHCC || model_id == TheengsDecoder::BLE_ID_NUM::BM2 || model_id == TheengsDecoder::BLE_ID_NUM::BM6) { // Device that broadcast and can be connected
createOrUpdateDevice(mac, device_flags_connect, model_id, mac_type, deviceName);
} else {
createOrUpdateDevice(mac, device_flags_init, model_id, mac_type, deviceName);