diff --git a/tasmota/xsns_62_esp32_mi_ble.ino b/tasmota/xsns_62_esp32_mi_ble.ino index 8b7b760ba..27198c961 100644 --- a/tasmota/xsns_62_esp32_mi_ble.ino +++ b/tasmota/xsns_62_esp32_mi_ble.ino @@ -182,8 +182,7 @@ struct mi_beacon_mac_data_t{ // e.g. 28/08 uint8_t mac[6]; }; struct mi_beacon_payload_data_t{ // - uint8_t type; - uint8_t ten; + uint16_t type; uint8_t size; uint8_t data[16]; }; @@ -286,6 +285,7 @@ struct mi_sensor_t{ uint32_t Btn:1; uint32_t events:1; uint32_t pairing:1; + uint32_t light:1; // binary light sensor }; uint32_t raw; } feature; @@ -303,12 +303,16 @@ struct mi_sensor_t{ uint32_t noMotion:1; uint32_t Btn:1; uint32_t PairBtn:1; + uint32_t light:1; // binary light sensor }; uint32_t raw; } eventType; int RSSI; uint8_t pairing; + int8_t light; // binary light sensor - initialise to -1 + int16_t Btn; // moved so we can initialise to -1 + uint32_t lastTime; uint32_t lux; float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx @@ -325,7 +329,6 @@ struct mi_sensor_t{ uint16_t events; //"alarms" since boot uint32_t NMT; // no motion time in seconds for the MJYD2S }; - uint16_t Btn; }; union { uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) @@ -1152,6 +1155,21 @@ int MIDecryptPayload(const uint8_t *macin, const uint8_t *nonce, uint32_t tag, u // xxyy FFEEDDCCBBAA 0104 TTTTHHHH // xxyy FFEEDDCCBBAA 0201 BB +const char *MIaddrStr(const uint8_t *addr, int useAlias = 0){ + static char addrstr[32]; + + const char *id = nullptr; + if (useAlias){ + id = BLE_ESP32::getAlias(addr); + } + if (!id || !(*id)){ + id = addrstr; + BLE_ESP32::dump(addrstr, 13, addr, 6); + } else { + } + + return id; +} int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const uint8_t *datain, int len){ uint8_t data[32]; @@ -1189,6 +1207,10 @@ int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const parsed->framedata.bindingvalidreq = (data[byteindex] & 0x02)>>1; //Byte 0: ......x. parsed->framedata.registeredflag = (data[byteindex] & 0x01); //Byte 0: .......x + // note: + // if bindingvalidreq, we should connect and establish a key. + // However, how do we determine WHICH TAS should do this? + byteindex++; parsed->devicetype = *((uint16_t *)(data + byteindex)); @@ -1243,17 +1265,17 @@ int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const break; case 0: // suceeded parsed->needkey = KEY_REQUIRED_AND_FOUND; - if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Payload decrypted")); + if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload decrypted"), MIaddrStr(slotmac)); break; case -1: // key failed to work parsed->needkey = KEY_REQUIRED_AND_INVALID; - AddLog(LOG_LEVEL_ERROR,PSTR("M32: Payload decrypt failed")); + AddLog(LOG_LEVEL_ERROR,PSTR("M32 %s: Payload decrypt failed"), MIaddrStr(slotmac)); parsed->payloadpresent = 0; return 0; break; case -2: // key not present parsed->needkey = KEY_REQUIRED_BUT_NOT_FOUND; - AddLog(LOG_LEVEL_ERROR,PSTR("M32: Payload encrypted but no key")); + AddLog(LOG_LEVEL_ERROR,PSTR("M32 %s: Payload encrypted but no key"), MIaddrStr(slotmac)); parsed->payloadpresent = 0; return 0; break; @@ -1288,7 +1310,7 @@ int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const } if ((len - byteindex) == 0){ - if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: No payload")); + if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: No payload"), MIaddrStr(slotmac)); parsed->payload.size = 0; parsed->payloadpresent = 0; return 0; @@ -1297,14 +1319,14 @@ int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const // we have payload which did not need decrypt. if (decres == 1){ parsed->needkey = KEY_NOT_REQUIRED; - if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Payload unencrypted")); + if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload unencrypted"), MIaddrStr(slotmac)); } // already decrypted if required parsed->payloadpresent = 1; memcpy(&parsed->payload, (data + byteindex), (len - byteindex)); if (parsed->payload.size != (len - byteindex) - 3){ - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Payload length mismatch")); + AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload length mismatch"), MIaddrStr(slotmac)); } return 1; @@ -1399,6 +1421,8 @@ uint32_t MIBLEgetSensorSlot(const uint8_t *mac, uint16_t _type, uint8_t counter) _newSensor.bat = 0x00; _newSensor.RSSI = 0xffff; _newSensor.lux = 0x00ffffff; + _newSensor.light = -1; + _newSensor.Btn = -1; switch (_type) { @@ -1428,9 +1452,13 @@ uint32_t MIBLEgetSensorSlot(const uint8_t *mac, uint16_t _type, uint8_t counter) _newSensor.feature.events=1; break; case MI_YEERC: - case MI_DOOR: _newSensor.feature.Btn=1; break; + case MI_DOOR: // MCCGQ02HL + _newSensor.feature.Btn=1; + _newSensor.feature.light=1; + _newSensor.feature.bat=1; + break; default: _newSensor.hum=NAN; _newSensor.feature.temp=1; @@ -1651,26 +1679,49 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ MIBLEsensors[_slot].pairing = 0; MIBLEsensors[_slot].eventType.PairBtn = 0; + //https://iot.mi.com/new/doc/embedded-development/ble/object-definition + switch(parsed->payload.type){ - case 0x01: // button press - MIBLEsensors[_slot].Btn = pld->Btn.num + (pld->Btn.longPress/2)*6; - MIBLEsensors[_slot].feature.Btn = 1; - MIBLEsensors[_slot].eventType.Btn = 1; - MI32.mode.shallTriggerTele = 1; - MIBLEsensors[_slot].shallSendMQTT = 1; - break; - case 0x02: // related to pair button? + case 0x0002: // related to pair button? 'easypairing' MIBLEsensors[_slot].pairing = 1; MIBLEsensors[_slot].eventType.PairBtn = 1; MIBLEsensors[_slot].feature.pairing = 1; MI32.mode.shallTriggerTele = 1; MIBLEsensors[_slot].shallSendMQTT = 1; break; - case 0x03: {// motion? 1 byte + case 0x0003: {// motion? 1 byte 'near' uint8_t motion = parsed->payload.data[0]; res = 0; }break; - case 0x04:{ + case 0x000f: // 'Someone is moving (with light)' + MIBLEsensors[_slot].eventType.motion = 1; + MIBLEsensors[_slot].lastTime = millis(); + MIBLEsensors[_slot].events++; + MIBLEsensors[_slot].lux = pld->lux; + MIBLEsensors[_slot].eventType.lux = 1; + MIBLEsensors[_slot].NMT = 0; + MI32.mode.shallTriggerTele = 1; + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].feature.lux = 1; + MIBLEsensors[_slot].feature.NMT = 1; + MIBLEsensors[_slot].feature.events=1; + + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: PIR: primary"),MIBLEsensors[_slot].lux ); + break; + + + + case 0x1001: // button press + MIBLEsensors[_slot].Btn = pld->Btn.num + (pld->Btn.longPress/2)*6; + MIBLEsensors[_slot].feature.Btn = 1; + MIBLEsensors[_slot].eventType.Btn = 1; + MI32.mode.shallTriggerTele = 1; + MIBLEsensors[_slot].shallSendMQTT = 1; + break; + //case 0x1002: // 'sleep' + //case 0x1003: // 'RSSI' + + case 0x1004:{ // 'temperature' float _tempFloat=(float)(pld->temp)/10.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; @@ -1682,7 +1733,8 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ } // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 4: U16: %u Temp"), _beacon.temp ); } break; - case 0x06: { + // 0x1005 - not documented + case 0x1006: { // 'humidity' float _tempFloat=(float)(pld->hum)/10.0f; if(_tempFloat<101){ MIBLEsensors[_slot].hum=_tempFloat; @@ -1694,7 +1746,7 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ } // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 6: U16: %u Hum"), _beacon.hum); } break; - case 0x07: + case 0x1007: // 'Light illuminance' MIBLEsensors[_slot].lux=pld->lux & 0x00ffffff; if(MIBLEsensors[_slot].type==MI_MJYD2S){ MIBLEsensors[_slot].eventType.noMotion = 1; @@ -1703,21 +1755,21 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ MIBLEsensors[_slot].feature.lux = 1; // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); break; - case 0x08: + case 0x1008: //'Soil moisture' MIBLEsensors[_slot].moisture=pld->moist; MIBLEsensors[_slot].eventType.moist = 1; MIBLEsensors[_slot].feature.moist = 1; if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 8: moisture updated")); // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 8: U8: %u Moisture"), _beacon.moist); break; - case 0x09: // 'conductivity' + case 0x1009: // 'conductivity' / 'Soil EC value' MIBLEsensors[_slot].fertility=pld->fert; MIBLEsensors[_slot].eventType.fert = 1; MIBLEsensors[_slot].feature.fert = 1; if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 9: fertility updated")); // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 9: U16: %u Fertility"), _beacon.fert); break; - case 0x0a: + case 0x100a:// 'Electricity' if(MI32.option.ignoreBogusBattery){ if(MIBLEsensors[_slot].type==MI_LYWSD03MMC || MIBLEsensors[_slot].type==MI_MHOC401){ res = 0; @@ -1736,7 +1788,8 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ } // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode a: U8: %u %%"), _beacon.bat); break; - case 0x0d:{ + // 100b-100d -> undefioend in docs. + case 0x100d:{ // is this right???? MIBLEsensors[_slot].feature.tempHum = 1; float _tempFloat=(float)(pld->HT.temp)/10.0f; if(_tempFloat < 60){ @@ -1755,36 +1808,26 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ MIBLEsensors[_slot].eventType.tempHum = 1; // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); } break; - case 0x0f: - if (parsed->payload.ten != 0) break; - MIBLEsensors[_slot].eventType.motion = 1; - MIBLEsensors[_slot].lastTime = millis(); - MIBLEsensors[_slot].events++; - MIBLEsensors[_slot].lux = pld->lux; - MIBLEsensors[_slot].eventType.lux = 1; - MIBLEsensors[_slot].NMT = 0; - MI32.mode.shallTriggerTele = 1; - MIBLEsensors[_slot].shallSendMQTT = 1; - MIBLEsensors[_slot].feature.lux = 1; - MIBLEsensors[_slot].feature.NMT = 1; - MIBLEsensors[_slot].feature.events=1; - - // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: PIR: primary"),MIBLEsensors[_slot].lux ); - break; - case 0x10:{ // 'formaldehide' + // 100e = 'lock' + // 100f = 'door' + case 0x1010:{ // 'formaldehide' const uint16_t f = uint16_t(parsed->payload.data[0]) | (uint16_t(parsed->payload.data[1]) << 8); float formaldehyde = (float)f / 100.0f; res = 0; } break; - case 0x12:{ // 'active' + // 1011 = 'bind' + case 0x1012:{ // 'switch' int active = parsed->payload.data[0]; res = 0; } break; - case 0x13:{ //mosquito tablet + case 0x1013:{ // 'Remaining amount of consumables' - mosquito tablet int tablet = parsed->payload.data[0]; res = 0; } break; - case 0x17:{ + //Flooding 0x1014 1 1 + //smoke 0x1015 1 1 + //Gas 0x1016 + case 0x1017:{ // 'No one moves' const uint32_t idle_time = uint32_t(parsed->payload.data[0]) | (uint32_t(parsed->payload.data[1]) << 8) | (uint32_t(parsed->payload.data[2]) << 16) | (uint32_t(parsed->payload.data[2]) << 24); float idlemins = (float)idle_time / 60.0f; @@ -1798,17 +1841,29 @@ int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 17: NMT: %u seconds"), _beacon.NMT); } break; - - case 0x19:{ - MIBLEsensors[_slot].Btn = uint8_t(parsed->payload.data[0]); // just an 8 bit value in a union. + //Light intensity 0x1018 + case 0x1018:{ //'Light intensity' - 0=dark, 1=light? - MCCGQ02HL + MIBLEsensors[_slot].light = parsed->payload.data[0]; + MIBLEsensors[_slot].eventType.light = 1; + MI32.mode.shallTriggerTele = 1; + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].feature.light = 1; + } break; + case 0x1019:{ //'Door sensor' - 0=open, 1=closed, 2=timeout? - MCCGQ02HL + MIBLEsensors[_slot].Btn = (uint8_t) parsed->payload.data[0]; // just an 8 bit value in a union. MIBLEsensors[_slot].eventType.Btn = 1; MI32.mode.shallTriggerTele = 1; MIBLEsensors[_slot].shallSendMQTT = 1; MIBLEsensors[_slot].feature.Btn = 1; } break; + //Weight attributes 0x101A 600 0 + //No one moves over time 0x101B 1 1 + //Smart pillow 0x101C 60 1 + //Formaldehyde (new) 0x101D + default: { - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Unknown MI pld")); + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Unknown MI pld type %x %s"), parsed->payload.type, tmp); res = 0; } break; } @@ -2179,45 +2234,65 @@ void CmndMi32Block(void){ } void CmndMi32Option(void){ - bool onOff = atoi(XdrvMailbox.data); + bool set = false; + if (strlen(XdrvMailbox.data)){ + set = true; + } + int onOff = atoi(XdrvMailbox.data); switch(XdrvMailbox.index) { case 0: - MI32.option.allwaysAggregate = onOff; - ResponseCmndIdxNumber(onOff); - return; + if (set){ + MI32.option.allwaysAggregate = onOff; + } else { + onOff = MI32.option.allwaysAggregate; + } break; case 1: - MI32.option.noSummary = onOff; - ResponseCmndIdxNumber(onOff); - return; + if (set){ + MI32.option.noSummary = onOff; + } else { + onOff = MI32.option.noSummary; + } break; case 2: - MI32.option.directBridgeMode = onOff; - ResponseCmndIdxNumber(onOff); - return; + if (set){ + MI32.option.directBridgeMode = onOff; + } else { + onOff = MI32.option.directBridgeMode; + } break; case 4:{ - MI32.option.ignoreBogusBattery = onOff; - ResponseCmndIdxNumber(onOff); - return; + if (set){ + MI32.option.ignoreBogusBattery = onOff; + } else { + onOff = MI32.option.ignoreBogusBattery; + } } break; case 5:{ - MI32.option.onlyAliased = onOff; - if (MI32.option.onlyAliased){ - // discard all sensors for a restart - MIBLEsensors.clear(); + if (set){ + MI32.option.onlyAliased = onOff; + if (MI32.option.onlyAliased){ + // discard all sensors for a restart + MIBLEsensors.clear(); + } + } else { + onOff = MI32.option.onlyAliased; } - ResponseCmndIdxNumber(onOff); - return; } break; case 6:{ - MI32.option.MQTTType = onOff; - ResponseCmndIdxNumber(onOff); + if (set){ + MI32.option.MQTTType = onOff; + } else { + onOff = MI32.option.MQTTType; + } + } break; + default:{ + ResponseCmndIdxError(); return; } break; - } - ResponseCmndIdxError(); + ResponseCmndIdxNumber(onOff); + return; } void MI32KeyListResp(){ @@ -2325,6 +2400,7 @@ const char HTTP_EVENTS[] PROGMEM = "{s}%s Events{m}%u {e}"; const char HTTP_NMT[] PROGMEM = "{s}%s No motion{m}> %u seconds{e}"; const char HTTP_MI32_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}"; const char HTTP_MI32_HL[] PROGMEM = "{s}