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();