From aa217e13b9f8616cd806d8c200f6d511378be9e7 Mon Sep 17 00:00:00 2001
From: Christian Baars
Date: Mon, 10 Jun 2024 10:50:50 +0200
Subject: [PATCH] refactoring, bugfixes, generic device scanning (#21603)
---
tasmota/include/xsns_62_esp32_mi.h | 18 ++-
.../tasmota_xsns_sensor/xsns_62_esp32_mi.ino | 143 ++++++++++++++----
2 files changed, 125 insertions(+), 36 deletions(-)
diff --git a/tasmota/include/xsns_62_esp32_mi.h b/tasmota/include/xsns_62_esp32_mi.h
index 004d9413a3fe..787c32fc283b 100644
--- a/tasmota/include/xsns_62_esp32_mi.h
+++ b/tasmota/include/xsns_62_esp32_mi.h
@@ -199,6 +199,7 @@ struct {
uint32_t updateScan:1;
uint32_t deleteScanTask:1;
+
uint32_t canConnect:1;
uint32_t willConnect:1;
uint32_t readingDone:1;
@@ -230,7 +231,7 @@ struct {
uint32_t directBridgeMode:1; // send every received BLE-packet as a MQTT-message in real-time
uint32_t activeScan:1;
uint32_t ignoreBogusBattery:1;
- uint32_t minimalSummary:1; // DEPRECATED!!
+ uint32_t handleEveryDevice:1;
} option;
#ifdef USE_MI_EXT_GUI
uint32_t widgetSlot;
@@ -271,6 +272,7 @@ struct mi_sensor_t{
uint32_t knob:1;
uint32_t door:1;
uint32_t leak:1;
+ uint32_t payload:1;
};
uint32_t raw;
} feature;
@@ -291,6 +293,7 @@ struct mi_sensor_t{
uint32_t longpress:1; //needs no extra feature bit, because knob is sufficient
uint32_t door:1;
uint32_t leak:1;
+ uint32_t payload:1;
};
uint32_t raw;
} eventType;
@@ -331,10 +334,15 @@ struct mi_sensor_t{
uint8_t longpress; // dimmer knob pressed without rotating
};
uint8_t door;
+
};
union {
uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1)
};
+ struct {
+ uint8_t * payload = nullptr;
+ uint8_t payload_len;
+ };
};
/*********************************************************************************************\
@@ -475,8 +483,9 @@ enum MI32_BLEInfoMsg {
const char HTTP_BTN_MENU_MI32[] PROGMEM = "
";
const char HTTP_MI32_SCRIPT_1[] PROGMEM =
- "function setUp(){setInterval(countUp,1000); setInterval(update,1000);}"
- "function countUp(){let ti=document.querySelectorAll('.Ti');"
+ "function setUp(){setInterval(countUp,1000); setInterval(update,200);}"
+ "function countUp(){let ti=document.querySelectorAll('.Ti');eb('clock').innerText=Date().slice(4,24);"
+ "eb('numDev').innerText=eb('pr').childElementCount-1;"
"for(const el of ti){var t=parseInt(el.innerText);el.innerText=t+1;}}"
"function update(){"
"fetch('/m32?wi=1').then(r=>r.text())"
@@ -499,7 +508,7 @@ const char HTTP_MI32_STYLE[] PROGMEM =
".parent{display:grid;grid-template-columns:repeat(auto-fill,350px);grid-template-rows:repeat(auto-fill,220px);"
"grid-auto-rows:220px;grid-auto-columns:350px;gap:1rem;justify-content:center;}"
"svg{float:inline-end;}"
- ".box{padding:10px;border-radius:0.8rem;background-color:rgba(221, 221, 221, 0.2);}"
+ ".box{padding:10px;border-radius:0.8rem;background-color:rgba(221, 221, 221, 0.2);overflow-y:auto;}"
"@media screen and (min-width: 720px){.wide{grid-column:span 2;grid-row:span 1;}.big {grid-column:span 2;grid-row:span 2;}}"
".tall {grid-column:span 1;grid-row:span 2;}"
"";
@@ -515,6 +524,7 @@ const char HTTP_MI32_PARENT_BLE_ROLE[] PROGMEM = "None|Observer|Peripheral|Centr
const char HTTP_MI32_PARENT_START[] PROGMEM =
""
"
MI32 Bridge
"
+ "
"
"Observing
%u devices
"
"Uptime:
%u seconds
"
"Free Heap: %u kB
"
diff --git a/tasmota/tasmota_xsns_sensor/xsns_62_esp32_mi.ino b/tasmota/tasmota_xsns_sensor/xsns_62_esp32_mi.ino
index 924267e558e4..e4249095edf8 100644
--- a/tasmota/tasmota_xsns_sensor/xsns_62_esp32_mi.ino
+++ b/tasmota/tasmota_xsns_sensor/xsns_62_esp32_mi.ino
@@ -69,6 +69,7 @@
void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
void MI32AddKey(mi_bindKey_t keyMAC);
+void MI32HandleEveryDevice(NimBLEAdvertisedDevice* advertisedDevice, uint8_t addr[6], int RSSI);
std::vector
MIBLEsensors;
RingbufHandle_t BLERingBufferQueue = nullptr;
@@ -128,6 +129,9 @@ class MI32AdvCallbacks: public NimBLEScanCallbacks {
}
if (advertisedDevice->getServiceDataCount() == 0) {
+ if(MI32.option.handleEveryDevice == 1) {
+ MI32HandleEveryDevice(advertisedDevice, addr, RSSI);
+ }
_mutex = false;
return;
}
@@ -148,6 +152,9 @@ class MI32AdvCallbacks: public NimBLEScanCallbacks {
else if(UUID==0x181a) { //ATC and PVVX - deprecated, change FW setting of these devices to BTHome V2
MI32ParseATCPacket((char*)advertisedDevice->getServiceData(0).data(),ServiceDataLength, addr, RSSI);
}
+ else if(MI32.option.handleEveryDevice == 1) {
+ MI32HandleEveryDevice(advertisedDevice, addr, RSSI);
+ }
_mutex = false;
};
};
@@ -451,6 +458,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t * _MAC, uint16_t _type, uint8_t counter){
for(uint32_t i=0; i 31){ // web UI is currently limited to 32
DEBUG_SENSOR_LOG(PSTR("M32: ignore new sensor, because of loaded config"));
return 0xff; //discard the data
}
DEBUG_SENSOR_LOG(PSTR("%s: found new sensor"),D_CMND_MI32);
- mi_sensor_t _newSensor;
+ mi_sensor_t _newSensor{};
memcpy(_newSensor.MAC,_MAC, 6);
_newSensor.PID = _pid;
_newSensor.type = _type;
@@ -478,6 +486,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t * _MAC, uint16_t _type, uint8_t counter){
_newSensor.RSSI=0;
_newSensor.lux = 0x00ffffff;
_newSensor.key = nullptr;
+ _newSensor.lastTime = Rtc.local_time;
switch (_type)
{
case UNKNOWN_MI: case BTHOME:
@@ -548,16 +557,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t * _MAC, uint16_t _type, uint8_t counter){
*/
void MI32triggerTele(void){
MI32.mode.triggeredTele = 1;
- MqttPublishTeleperiodSensor();
-}
-
-/**
- * @brief Is called after every finding of new BLE sensor
- *
- */
-void MI32StatusInfo() {
- MI32.mode.shallShowStatusInfo = 0;
- Response_P(PSTR("{\"M32\":{\"found\":%u}}"), MIBLEsensors.size());
+ MqttPublishSensor();
XdrvRulesProcess(0);
}
@@ -584,6 +584,13 @@ void MI32addHistory(uint8_t history[24], float value, const uint32_t type){
history[_hour] = (((value/5.0f) + 1) + 0b10000000); //lux
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history lux: %u in hour:%u"),history[_hour], _hour);
break;
+ case 3: //BLE device sighting
+ uint16_t sightings = history[_hour] & 0b01111111;
+ if(sightings<20){
+ history[_hour] = (sightings | 0b10000000) + 1;
+ // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history sighting: %u in hour:%u"),history[_hour], _hour);
+ }
+ break;
#ifdef USE_MI_ESP32_ENERGY
case 100: // energy
if(value == 0.0f) value = 1.0f;
@@ -620,7 +627,7 @@ void Mi32invalidateOldHistory(){
return;
}
uint32_t _nextHour = (_hour>22)?0:_hour+1;
- for(auto _sensor:MIBLEsensors){
+ for(auto &_sensor:MIBLEsensors){
if(_sensor.feature.temp == 1){
bitClear(_sensor.temp_history[_nextHour],7);
}
@@ -630,6 +637,9 @@ void Mi32invalidateOldHistory(){
if(_sensor.feature.lux == 1){
bitClear(_sensor.lux_history[_nextHour],7);
}
+ if(_sensor.feature.payload == 1){
+ bitClear(_sensor.temp_history[_nextHour],7);
+ }
}
_lastInvalidatedHour = _hour;
}
@@ -646,9 +656,9 @@ void MI32PreInit(void) {
//test section for options
MI32.option.allwaysAggregate = 1;
MI32.option.noSummary = 0;
- MI32.option.minimalSummary = 0;
MI32.option.directBridgeMode = 0;
MI32.option.ignoreBogusBattery = 1; // from advertisements
+ MI32.option.handleEveryDevice = 0; // scan for every BLE device with a public address
MI32loadCfg();
if(MIBLEsensors.size()>0){
@@ -878,7 +888,11 @@ extern "C" {
}
static char _name[12];
if( MIBLEsensors[slot].type == UNKNOWN_MI){
- snprintf_P(_name,8,PSTR("MI_%04X"),MIBLEsensors[slot].PID);
+ if(MIBLEsensors[slot].PID == 0){
+ snprintf_P(_name,8,PSTR("BLE_%02u"),slot);
+ } else {
+ snprintf_P(_name,8,PSTR("MI_%04X"),MIBLEsensors[slot].PID);
+ }
}
else{
GetTextIndexed(_name, sizeof(_name), MIBLEsensors[slot].type-1, kMI32DeviceType);
@@ -994,7 +1008,7 @@ void MI32saveConfig(){
else{
_name_feat[0] = 0;
}
- uint32_t _inc = snprintf_P(_filebuf+_pos,200,PSTR("{\"MAC\":\"%s\",\"PID\":\"%04x\",\"key\":\"%s\"%s},"),_MAC,kMI32DeviceID[_sensor.type - 1],_key,_name_feat);
+ uint32_t _inc = snprintf_P(_filebuf+_pos,200,PSTR("{\"MAC\":\"%s\",\"PID\":\"%04x\",\"key\":\"%s\"%s},"),_MAC,_sensor.PID,_key,_name_feat);
_pos += _inc;
}
_filebuf[_pos-1] = ']';
@@ -1668,7 +1682,6 @@ if(decryptRet!=0){
}
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u with payload type: %02x"), MI32getDeviceName(_slot),_slot,_payload.type);
- MIBLEsensors[_slot].lastTime = millis();
switch(_payload.type){
case 0x01:
MIBLEsensors[_slot].feature.Btn = 1;
@@ -1836,7 +1849,7 @@ if(decryptRet!=0){
}
if(MIBLEsensors[_slot].eventType.raw == 0) return;
MIBLEsensors[_slot].shallSendMQTT = 1;
- if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1;
+ if(MI32.option.directBridgeMode == 1) MI32.mode.shallTriggerTele = 1;
}
void MI32parseBTHomePacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI, const char* optionalName){
@@ -1848,7 +1861,7 @@ void MI32parseBTHomePacket(char * _buf, uint32_t length, uint8_t addr[6], int RS
}
const auto _sensor = &MIBLEsensors[_slot];
_sensor->RSSI = RSSI;
- _sensor->lastTime = millis();
+ // _sensor->lastTime = Rtc.local_time;
BTHome_info_t info;
info.byte_value = _buf[0];
@@ -1907,7 +1920,7 @@ void MI32parseBTHomePacket(char * _buf, uint32_t length, uint8_t addr[6], int RS
bitSet(MI32.widgetSlot,_slot);
#endif //USE_MI_EXT_GUI
_sensor->shallSendMQTT = 1;
- if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1;
+ if(MI32.option.directBridgeMode == 1) MI32.mode.shallTriggerTele = 1;
}
void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI){
@@ -1923,7 +1936,7 @@ void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI)
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), MI32getDeviceName(_slot),_slot);
MIBLEsensors[_slot].RSSI=RSSI;
- MIBLEsensors[_slot].lastTime = millis();
+ // MIBLEsensors[_slot].lastTime = Rtc.local_time;
if(isATC){
MIBLEsensors[_slot].temp = (float)(int16_t(__builtin_bswap16(_packet->A.temp)))/10.0f;
MIBLEsensors[_slot].hum = (float)_packet->A.hum;
@@ -1943,7 +1956,7 @@ void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI)
MI32addHistory(MIBLEsensors[_slot].hum_history, (float)MIBLEsensors[_slot].hum, 1);
#endif //USE_MI_EXT_GUI
MIBLEsensors[_slot].shallSendMQTT = 1;
- if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1;
+ if(MI32.option.directBridgeMode == 1) MI32.mode.shallTriggerTele = 1;
}
void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI){ // no MiBeacon
@@ -1953,7 +1966,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
if(_slot==0xff) return;
// AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), MI32getDeviceName(_slot),_slot);
MIBLEsensors[_slot].RSSI=RSSI;
- MIBLEsensors[_slot].lastTime = millis();
+ // MIBLEsensors[_slot].lastTime = Rtc.local_time;
cg_packet_t _packet;
memcpy((char*)&_packet,_buf,sizeof(_packet));
switch (_packet.mode){
@@ -1991,7 +2004,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
}
if(MIBLEsensors[_slot].eventType.raw == 0) return;
MIBLEsensors[_slot].shallSendMQTT = 1;
- if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1;
+ if(MI32.option.directBridgeMode == 1) MI32.mode.shallTriggerTele = 1;
#ifdef USE_MI_EXT_GUI
bitSet(MI32.widgetSlot,_slot);
#endif //USE_MI_EXT_GUI
@@ -2003,15 +2016,47 @@ void MI32ParseResponse(char *buf, uint16_t bufsize, uint8_t addr[6], int RSSI) {
}
uint16_t _type= buf[3]*256 + buf[2];
// AddLog(LOG_LEVEL_INFO, PSTR("%02x %02x %02x %02x"),(uint8_t)buf[0], (uint8_t)buf[1],(uint8_t)buf[2],(uint8_t)buf[3]);
- uint8_t _addr[6];
- memcpy(_addr,addr,6);
- uint16_t _slot = MIBLEgetSensorSlot(_addr, _type, buf[4]);
+ uint16_t _slot = MIBLEgetSensorSlot(addr, _type, buf[4]);
if(_slot!=0xff) {
MIBLEsensors[_slot].RSSI=RSSI;
MI32parseMiBeacon(buf,_slot,bufsize);
}
}
+void MI32HandleEveryDevice(NimBLEAdvertisedDevice* advertisedDevice, uint8_t addr[6], int RSSI) {
+ if(advertisedDevice->getAddressType() != BLE_ADDR_PUBLIC) {
+ return;
+ }
+ uint16_t _slot = MIBLEgetSensorSlot(addr, 0, 0);
+ if(_slot==0xff) {
+ return;
+ }
+ auto &_sensor = MIBLEsensors[_slot];
+ if (advertisedDevice->haveName()){
+ if(_sensor.name == nullptr){
+ std::string name = advertisedDevice->getName();
+ _sensor.name = new char[name.length() + 1];
+ strcpy(_sensor.name, name.c_str());
+ }
+ }
+ if(_sensor.payload == nullptr) {
+ _sensor.payload = new uint8_t[64]();
+ }
+ if(_sensor.payload != nullptr) {
+ memcpy(_sensor.payload, advertisedDevice->getPayload(), advertisedDevice->getPayloadLength());
+ _sensor.payload_len = advertisedDevice->getPayloadLength();
+ bitSet(MI32.widgetSlot,_slot);
+ MI32addHistory(_sensor.temp_history, 0.0f, 3); // reuse temp_history as sighting history
+ _sensor.RSSI=RSSI;
+ _sensor.feature.payload = 1;
+ _sensor.eventType.payload = 1;
+ if(MI32.option.directBridgeMode == 1){
+ MI32.mode.shallTriggerTele = 1;
+ _sensor.shallSendMQTT = 1;
+ }
+ }
+}
+
/**
* Called automatically every 50 milliseconds or can be triggered from Berry with BLE.loop() - useful from fast_loop
*/
@@ -2106,7 +2151,7 @@ void MI32BLELoop()
*/
void MI32Every50mSecond(){
- if(MI32.mode.shallTriggerTele){
+ if(MI32.mode.shallTriggerTele == 1){
MI32.mode.shallTriggerTele = 0;
MI32triggerTele();
}
@@ -2128,7 +2173,7 @@ void MI32EverySecond(bool restart){
// should not be needed with a stable BLE stack
if(MI32.role == 1 && MI32.mode.runningScan == 0){
- AddLog(LOG_LEVEL_INFO,PSTR("BLE: probably controller error ... restart scan"));
+ AddLog(LOG_LEVEL_INFO,PSTR("BLE: restart scan"));
MI32StartTask(MI32_TASK_SCAN);
}
}
@@ -2226,6 +2271,14 @@ void CmndMi32Option(void){
onOff = MI32.option.activeScan;
}
break;
+ case 5:
+ if(XdrvMailbox.data_len>0){
+ MI32.option.handleEveryDevice = onOff;
+ }
+ else{
+ onOff = MI32.option.handleEveryDevice;
+ }
+ break;
#ifdef CONFIG_BT_NIMBLE_NVS_PERSIST
case 99: // TODO: should be moved to some reset command, i.e. "reset 6"
NimBLEDevice::deleteAllBonds();
@@ -2423,6 +2476,18 @@ void MI32sendWidget(uint32_t slot){
WSContentSend_P(PSTR("No leak
"));
}
}
+ if(_sensor.feature.payload == 1){
+ if(_sensor.payload != nullptr){
+ char _payload[128];
+ char _polyline[176];
+ ToHex_P((const unsigned char*)_sensor.payload,_sensor.payload_len,_payload, (_sensor.payload_len * 2) + 1);
+ MI32createPolyline(_polyline,_sensor.temp_history);
+ WSContentSend_P(PSTR("Payload:"));
+ WSContentSend_P(HTTP_MI32_GRAPH,_polyline,60,240,176,_polyline,4);
+ WSContentSend_P(PSTR("
%s
"),_payload);
+ }
+ }
+ WSContentSend_P(PSTR("Timestamp: %s
"),GetDT(_sensor.lastTime).c_str());
WSContentSend_P(PSTR(""));
}
@@ -2435,6 +2500,7 @@ void MI32InitGUI(void){
WSContentSend_P(HTTP_MI32_STYLE_SVG,1,185,124,124,185,124,124);
WSContentSend_P(HTTP_MI32_STYLE_SVG,2,151,190,216,151,190,216);
WSContentSend_P(HTTP_MI32_STYLE_SVG,3,242,240,176,242,240,176);
+ WSContentSend_P(HTTP_MI32_STYLE_SVG,4,60,240,176,60,240,176);
char _role[16];
GetTextIndexed(_role, sizeof(_role), MI32.role, HTTP_MI32_PARENT_BLE_ROLE);
@@ -2508,7 +2574,7 @@ void MI32Show(bool json)
MI32getDeviceName(i));
}
- if((MI32.mode.triggeredTele == 1 && MI32.option.minimalSummary == 0)||MI32.mode.triggeredTele == 1){
+ if(MI32.mode.triggeredTele == 1){
bool tempHumSended = false;
if(MIBLEsensors[i].feature.tempHum){
if(MIBLEsensors[i].eventType.tempHum || MI32.mode.triggeredTele == 0 || MI32.option.allwaysAggregate == 1){
@@ -2622,16 +2688,29 @@ void MI32Show(bool json)
ResponseAppend_P(PSTR("\"NMT\":%u"), MIBLEsensors[i].NMT);
}
}
- if (MIBLEsensors[i].feature.bat){
- if(MIBLEsensors[i].eventType.bat || MI32.mode.triggeredTele == 0 || MI32.option.allwaysAggregate == 1){
+ if (MIBLEsensors[i].feature.bat == 1){
+ if(MIBLEsensors[i].eventType.bat == 1 || MI32.mode.triggeredTele == 0 || MI32.option.allwaysAggregate == 1){
if ((MIBLEsensors[i].bat != 0x00)) {
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"Battery\":%u"), MIBLEsensors[i].bat);
}
}
}
+ if (MIBLEsensors[i].feature.payload == 1){
+ if(MIBLEsensors[i].eventType.payload == 1 || MI32.mode.triggeredTele == 0 || MI32.option.allwaysAggregate == 1){
+ if ((MIBLEsensors[i].payload != nullptr)) {
+ MI32ShowContinuation(&commaflg);
+ char _payload[128];
+ ToHex_P((const unsigned char*)MIBLEsensors[i].payload,MIBLEsensors[i].payload_len,_payload, (MIBLEsensors[i].payload_len * 2) + 1);
+ ResponseAppend_P(PSTR("\"Payload\":\"%s\""),_payload);
+ }
+ }
+ }
MI32ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"RSSI\":%d,"), MIBLEsensors[i].RSSI);
+ if(MI32.mode.triggeredTele == 0){
+ ResponseAppend_P(PSTR("\"Time\":%d,"), MIBLEsensors[i].lastTime);
+ }
ResponseAppend_P(PSTR("\"MAC\":\"%02X%02X%02X%02X%02X%02X\""),MIBLEsensors[i].MAC[0],MIBLEsensors[i].MAC[1],MIBLEsensors[i].MAC[2],MIBLEsensors[i].MAC[3],MIBLEsensors[i].MAC[4],MIBLEsensors[i].MAC[5]);
ResponseJsonEnd();