refactoring, bugfixes, generic device scanning (#21603)

This commit is contained in:
Christian Baars 2024-06-10 10:50:50 +02:00 committed by GitHub
parent d046f9c25f
commit aa217e13b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 125 additions and 36 deletions

View File

@ -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 = "<p><form action='m32' method='get'><button>Mi Dashboard</button></form></p>";
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;}"
"</style>";
@ -515,6 +524,7 @@ const char HTTP_MI32_PARENT_BLE_ROLE[] PROGMEM = "None|Observer|Peripheral|Centr
const char HTTP_MI32_PARENT_START[] PROGMEM =
"<div class='parent'id='pr'>"
"<div class='box tall'><h2>MI32 Bridge</h2>"
"<span id='clock'></span><br><br>"
"Observing <span id='numDev'>%u</span> devices<br><br>"
"Uptime: <span class='Ti'>%u</span> seconds<br><br>"
"Free Heap: %u kB<br><br>"

View File

@ -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<mi_sensor_t> 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<MIBLEsensors.size(); i++){
if(memcmp(_MAC,MIBLEsensors[i].MAC,6)==0){
DEBUG_SENSOR_LOG(PSTR("%s: known sensor at slot: %u"),D_CMND_MI32, i);
MIBLEsensors[i].lastTime = Rtc.local_time;
// AddLog(LOG_LEVEL_DEBUG,PSTR("Counters: %x %x"),MIBLEsensors[i].lastCnt, counter);
if(MIBLEsensors[i].lastCnt==counter && counter!=0) {
// AddLog(LOG_LEVEL_DEBUG,PSTR("Old packet"));
@ -461,12 +469,12 @@ uint32_t MIBLEgetSensorSlot(uint8_t * _MAC, uint16_t _type, uint8_t counter){
DEBUG_SENSOR_LOG(PSTR("%s: i: %x %x %x %x %x %x"),D_CMND_MI32, MIBLEsensors[i].MAC[5], MIBLEsensors[i].MAC[4],MIBLEsensors[i].MAC[3],MIBLEsensors[i].MAC[2],MIBLEsensors[i].MAC[1],MIBLEsensors[i].MAC[0]);
DEBUG_SENSOR_LOG(PSTR("%s: n: %x %x %x %x %x %x"),D_CMND_MI32, _MAC[5], _MAC[4], _MAC[3],_MAC[2],_MAC[1],_MAC[0]);
}
if(MI32.mode.didGetConfig){
if(MI32.mode.didGetConfig || MIBLEsensors.size() > 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("<p>No leak</p>"));
}
}
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("<p>Payload:"));
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,60,240,176,_polyline,4);
WSContentSend_P(PSTR("</p><code style='word-break: break-all;'>%s</code>"),_payload);
}
}
WSContentSend_P(PSTR("<p>Timestamp: %s</p>"),GetDT(_sensor.lastTime).c_str());
WSContentSend_P(PSTR("</div>"));
}
@ -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();