diff --git a/tasmota/xsns_62_esp32_mi_ble.ino b/tasmota/xsns_62_esp32_mi_ble.ino index 277f98096..6b899da17 100644 --- a/tasmota/xsns_62_esp32_mi_ble.ino +++ b/tasmota/xsns_62_esp32_mi_ble.ino @@ -261,6 +261,36 @@ struct PVVXPacket_t { uint8_t flags; }; +struct MiScaleV1Packet_t { + //uint8_t size; // = 14 + //uint8_t uid; // = 0x16, 16-bit UUID + //uint16_t UUID; // = 0x181D + uint8_t status; // bit 0 lbs, 4 jin, 5, stabilized, 7, weight removed + uint16_t weight; + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct MiScaleV2Packet_t { + //uint8_t size; // = 17 + //uint8_t uid; // = 0x16, 16-bit UUID + //uint16_t UUID; // = 0x181B + uint8_t weight_unit; + uint8_t status; // bit 14 impedance stabilized + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint16_t impedance; + uint16_t weight; +}; + #pragma pack(0) struct mi_sensor_t{ @@ -286,6 +316,8 @@ struct mi_sensor_t{ uint32_t events:1; uint32_t pairing:1; uint32_t light:1; // binary light sensor + uint32_t scale:1; + uint32_t impedance:1; }; uint32_t raw; } feature; @@ -304,6 +336,7 @@ struct mi_sensor_t{ uint32_t Btn:1; uint32_t PairBtn:1; uint32_t light:1; // binary light sensor + uint32_t scale:1; }; uint32_t raw; } eventType; @@ -333,6 +366,16 @@ struct mi_sensor_t{ union { uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) }; + union { + struct { + uint8_t weight_stabilized; + uint8_t weight_removed; + char weight_unit[4]; // kg, lbs, jin or empty when unknown + float weight; + uint8_t impedance_stabilized; + uint16_t impedance; + }; + }; }; struct MAC_t { @@ -380,8 +423,10 @@ void (*const MI32_Commands[])(void) PROGMEM = { #define MI_MHOC303 12 #define MI_ATC 13 #define MI_DOOR 14 +#define MI_SCALE_V1 15 +#define MI_SCALE_V2 16 -#define MI_MI32_TYPES 14 //count this manually +#define MI_MI32_TYPES 16 //count this manually const uint16_t kMI32DeviceID[MI_MI32_TYPES]={ 0x0000, // Unkown @@ -396,8 +441,10 @@ const uint16_t kMI32DeviceID[MI_MI32_TYPES]={ 0x0153, // yee-rc 0x0387, // MHO-C401 0x06d3, // MHO-C303 - 0x0a1c, // ATC -> this is a fake ID - 0x098b // door/window sensor + 0x0a1c, // ATC -> this is a fake ID + 0x098b, // door/window sensor + 0x181d, // Mi Scale V1 + 0x181b // Mi Scale V2 }; const char kMI32DeviceType0[] PROGMEM = "Unknown"; @@ -414,7 +461,9 @@ const char kMI32DeviceType10[] PROGMEM ="MHOC401"; const char kMI32DeviceType11[] PROGMEM ="MHOC303"; const char kMI32DeviceType12[] PROGMEM ="ATC"; const char kMI32DeviceType13[] PROGMEM ="DOOR"; -const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType0,kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12,kMI32DeviceType13}; +const char kMI32DeviceType14[] PROGMEM ="MISCALEV1"; +const char kMI32DeviceType15[] PROGMEM ="MISCALEV2"; +const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType0,kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12,kMI32DeviceType13,kMI32DeviceType14,kMI32DeviceType15}; typedef int BATREAD_FUNCTION(int slot); typedef int UNITWRITE_FUNCTION(int slot, int unit); @@ -987,6 +1036,11 @@ int MI32advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct) case 0x181a: { //ATC MI32ParseATCPacket(ServiceData, ServiceDataLength, addr, RSSI); } break; + case 0x181d: // Mi Scale V1 + case 0x181b: // Mi Scale V2 + { + MI32ParseMiScalePacket(ServiceData, ServiceDataLength, addr, RSSI, UUID); + } break; default:{ } break; @@ -1438,6 +1492,13 @@ uint32_t MIBLEgetSensorSlot(const uint8_t *mac, uint16_t _type, uint8_t counter) _newSensor.feature.light=1; _newSensor.feature.bat=1; break; + case MI_SCALE_V1: + _newSensor.feature.scale=1; + break; + case MI_SCALE_V2: + _newSensor.feature.scale=1; + _newSensor.feature.impedance=1; + break; default: _newSensor.hum=NAN; _newSensor.feature.temp=1; @@ -1643,6 +1704,107 @@ void MI32ParseATCPacket(const uint8_t * _buf, uint32_t length, const uint8_t *ad } } +void MI32ParseMiScalePacket(const uint8_t * _buf, uint32_t length, const uint8_t *addr, int RSSI, int UUID) { + MiScaleV1Packet_t *_packetV1 = (MiScaleV1Packet_t*)_buf; + MiScaleV2Packet_t *_packetV2 = (MiScaleV2Packet_t*)_buf; + uint8_t weight_stabilized = 0; + uint8_t weight_removed = 0; + uint8_t impedance_stabilized = 0; + + // Mi Scale V1 + if (length == 10 && UUID == 0x181d) { // 14-1-1-2 + weight_stabilized = (_packetV1->status & (1 << 5)) ? 1 : 0; + weight_removed = (_packetV1->status & (1 << 7)) ? 1 : 0; + if (!MI32.option.directBridgeMode && (!weight_stabilized || weight_removed)) + return; + + uint32_t _slot = MIBLEgetSensorSlot(addr, UUID, 0); + if (_slot==0xff) return; + + if ((_slot >= 0) && (_slot < MIBLEsensors.size())) { + if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); + MIBLEsensors[_slot].RSSI = RSSI; + MIBLEsensors[_slot].needkey = KEY_NOT_REQUIRED; + MIBLEsensors[_slot].eventType.scale = 1; + + MIBLEsensors[_slot].weight_stabilized = weight_stabilized; + MIBLEsensors[_slot].weight_removed = weight_removed; + + if (_packetV1->status & (1 << 0)) { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs")); + MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f; + } else if(_packetV1->status & (1 << 4)) { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin")); + MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f; + } else { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg")); + MIBLEsensors[_slot].weight = (float)_packetV1->weight / 200.0f; + } + + if (MIBLEsensors[_slot].weight_removed) { + MIBLEsensors[_slot].weight = 0.0f; + } + + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + } + + // Mi Scale V2 + else if (length == 13 && UUID == 0x181b) { // 17-1-1-2 + weight_stabilized = (_packetV2->status & (1 << 5)) ? 1 : 0; + weight_removed = (_packetV2->status & (1 << 7)) ? 1 : 0; + impedance_stabilized = (_packetV2->status & (1 << 1)) ? 1 : 0; + if (!MI32.option.directBridgeMode && (!weight_stabilized || weight_removed)) + return; + + uint32_t _slot = MIBLEgetSensorSlot(addr, UUID, 0); + if (_slot==0xff) return; + + if ((_slot >= 0) && (_slot < MIBLEsensors.size())) { + if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); + MIBLEsensors[_slot].RSSI = RSSI; + MIBLEsensors[_slot].needkey = KEY_NOT_REQUIRED; + MIBLEsensors[_slot].eventType.scale = 1; + + MIBLEsensors[_slot].weight_stabilized = weight_stabilized; + MIBLEsensors[_slot].weight_removed = weight_removed; + + if (_packetV2->weight_unit & (1 << 4)) { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin")); + MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; + } else if(_packetV2->weight_unit == 3) { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs")); + MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; + } else if(_packetV2->weight_unit == 2) { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg")); + MIBLEsensors[_slot].weight = (float)_packetV2->weight / 200.0f; + } else { + strcpy(MIBLEsensors[_slot].weight_unit, PSTR("")); + MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; + } + + if (weight_removed) { + MIBLEsensors[_slot].weight = 0.0f; + } + + MIBLEsensors[_slot].impedance = 0; + if (MI32.option.directBridgeMode || impedance_stabilized) + { + MIBLEsensors[_slot].impedance_stabilized = impedance_stabilized; + MIBLEsensors[_slot].impedance = _packetV2->impedance; + + if (weight_removed) { + MIBLEsensors[_slot].impedance = 0; + } + } + + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + } +} + //////////////////////////////////////////////////////////// // this SHOULD parse any MI payload. int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ @@ -2384,6 +2546,11 @@ 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}
{m}
{e}"; const char HTTP_MI32_LIGHT[] PROGMEM = "{s}%s" " Light" "{m}%d{e}"; +const char HTTP_MISCALE_WEIGHT[] PROGMEM = "{s}%s" " Weight" "{m}%*_f %s{e}"; +const char HTTP_MISCALE_WEIGHT_REMOVED[] PROGMEM = "{s}%s" " Weight removed" "{m}%s{e}"; +const char HTTP_MISCALE_WEIGHT_STABILIZED[] PROGMEM = "{s}%s" " Weight stabilized" "{m}%s{e}"; +const char HTTP_MISCALE_IMPEDANCE[] PROGMEM = "{s}%s" " Impedance" "{m}%u{e}"; +const char HTTP_MISCALE_IMPEDANCE_STABILIZED[] PROGMEM = "{s}%s" " Impedance stabilized" "{m}%s{e}"; //const char HTTP_NEEDKEY[] PROGMEM = "{s}%s feature.scale){ + if(p->eventType.scale || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate +#ifdef USE_HOME_ASSISTANT + ||(hass_mode==2) +#endif //USE_HOME_ASSISTANT + ){ + if (MI32.option.directBridgeMode) { + ResponseAppend_P(PSTR(",\"WeightRemoved\":%u"), p->weight_removed); + ResponseAppend_P(PSTR(",\"WeightStabilized\":%u"), p->weight_stabilized); + } + ResponseAppend_P(PSTR(",\"WeightUnit\":\"%s\""), p->weight_unit); + ResponseAppend_P(PSTR(",\"Weight\":%*_f"), + Settings->flag2.weight_resolution, &p->weight); + if (p->feature.impedance) { + if (MI32.option.directBridgeMode) { + ResponseAppend_P(PSTR(",\"ImpedanceStabilized\":%u"), p->impedance_stabilized); + } + ResponseAppend_P(PSTR(",\"Impedance\":%u"), p->impedance); + } + } + } if (p->feature.Btn){ if(p->eventType.Btn || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate #ifdef USE_HOME_ASSISTANT @@ -2835,6 +3023,16 @@ const char *classes[] = { "", //- empty device class "Firmware", "", + + // 11 + "", //- empty device class + "Weight", + "", // Will be set to p->weight_unit + + // 12 + "", //- empty device class + "Impedance", + "Ohm", }; @@ -2946,6 +3144,16 @@ void MI32DiscoveryOneMISensor(){ continue; } break; + case 11: // weight + if (!p->feature.scale){ // Mi Scale V1 and V2 only + continue; + } + break; + case 12: // impedance + if (!p->feature.impedance){ // Mi Scale V2 only + continue; + } + break; } /* @@ -2980,9 +3188,9 @@ void MI32DiscoveryOneMISensor(){ //"\"uniq_id\":\"%s_%s\"," - unique for this data, id, classes[i+1], //"\"unit_of_meas\":\"%s\"," - the measure of this type of data - (classes[i+2][0]?"\"unit_of_meas\":\"":""), - classes[i+2], - (classes[i+2][0]?"\",":""), + ((i/3==11)||classes[i+2][0]?"\"unit_of_meas\":\"":""), + (i/3==11)?p->weight_unit:classes[i+2], + ((i/3==11)||classes[i+2][0]?"\",":""), //"\"val_tpl\":\"{{ %s%s }}") // e.g. Temperature // inverted binary - {{ 'off' if value_json.posn else 'on' }} // binary - {{ 'on' if value_json.posn else 'off' }} @@ -3216,7 +3424,19 @@ void MI32Show(bool json) if (p->feature.light){ WSContentSend_PD(HTTP_MI32_LIGHT, typeName, p->light); } - + if (p->feature.scale){ + WSContentSend_PD(HTTP_MISCALE_WEIGHT, typeName, Settings->flag2.weight_resolution, &p->weight, p->weight_unit); + if (MI32.option.directBridgeMode) { + WSContentSend_PD(HTTP_MISCALE_WEIGHT_REMOVED, typeName, p->weight_removed? PSTR("yes") : PSTR("no")); + WSContentSend_PD(HTTP_MISCALE_WEIGHT_STABILIZED, typeName, p->weight_stabilized ? PSTR("yes") : PSTR("no")); + } + if (p->feature.impedance) { + WSContentSend_PD(HTTP_MISCALE_IMPEDANCE, typeName, p->impedance); + if (MI32.option.directBridgeMode) { + WSContentSend_PD(HTTP_MISCALE_IMPEDANCE_STABILIZED, typeName, p->impedance_stabilized? PSTR("yes") : PSTR("no")); + } + } + } if(p->bat!=0x00){ WSContentSend_PD(HTTP_BATTERY, typeName, p->bat); } @@ -3282,4 +3502,4 @@ bool Xsns62(uint8_t function) #endif // CONFIG_IDF_TARGET_ESP32 or CONFIG_IDF_TARGET_ESP32C3 #endif // ESP32 -#endif +#endif \ No newline at end of file