MI_HM10 added support Mi Scale

1. Added Mi Scale v1/Mi Scale v2 support via BLE advertising packets
2. Added features "scale" and "impedance" to features list
This commit is contained in:
Max 2021-12-09 11:26:54 +03:00
parent 2f359e5039
commit 696e33a677
1 changed files with 239 additions and 7 deletions

View File

@ -131,6 +131,37 @@ struct cg_packet_t {
uint8_t bat;
};
};
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;
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 scan_entry_t {
@ -175,6 +206,8 @@ struct mi_sensor_t{
uint32_t NMT:1;
uint32_t PIR:1;
uint32_t Btn:1;
uint32_t scale:1;
uint32_t impedance:1;
};
uint32_t raw;
} feature;
@ -191,6 +224,7 @@ struct mi_sensor_t{
uint32_t motion:1;
uint32_t noMotion:1;
uint32_t Btn:1;
uint32_t scale:1;
};
uint32_t raw;
} eventType;
@ -213,10 +247,46 @@ struct mi_sensor_t{
uint32_t NMT; // no motion time in seconds for the MJYD2S
};
uint16_t Btn;
struct {
uint8_t has_impedance;
uint8_t impedance_stabilized;
uint8_t weight_stabilized;
uint8_t weight_removed;
char weight_unit[4]; // kg, lbs, jin or empty when unknown
float weight;
uint16_t impedance;
struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} datetime;
};
};
union {
uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1)
};
/* union {
struct {
uint8_t has_impedance;
uint8_t impedance_stabilized;
uint8_t weight_stabilized;
uint8_t weight_removed;
char weight_unit[4]; // kg, lbs, jin or empty when unknown
float weight;
uint16_t impedance;
struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} datetime;
};
} scale;*/
};
struct {
@ -284,6 +354,12 @@ const char S_JSON_HM10_COMMAND[] PROGMEM = "{\"" D_CMND_HM10 "%s%s\"}";
const char kHM10_Commands[] PROGMEM = D_CMND_HM10"|"
"Scan|AT|Period|Baud|Time|Auto|Page|Beacon|Block|Option";
const char HTTP_MISCALE_WEIGHT[] PROGMEM = "{s}%s" " Weight" "{m}%*_f %s{e}";
const char HTTP_MISCALE_IMPEDANCE[] PROGMEM = "{s}%s" " Impedance" "{m}%u{e}";
const char HTTP_MISCALE_WEIGHT_REMOVED[] PROGMEM = "{s}%s" " Weight removed" "{m}%s{e}";
const char HTTP_MISCALE_STABILIZED[] PROGMEM = "{s}%s" " Stabilized" "{m}%s{e}";
void (*const HM10_Commands[])(void) PROGMEM = { &CmndHM10Scan, &CmndHM10AT, &CmndHM10Period, &CmndHM10Baud, &CmndHM10Time, &CmndHM10Auto, &CmndHM10Page, &CmndHM10Beacon, &CmndHM10Block, &CmndHM10Option };
@ -299,8 +375,10 @@ void (*const HM10_Commands[])(void) PROGMEM = { &CmndHM10Scan, &CmndHM10AT, &Cmn
#define MHOC401 10
#define MHOC303 11
#define ATC 12
#define MI_SCALE_V1 13
#define MI_SCALE_V2 14
#define HM10_TYPES 12 //count this manually
#define HM10_TYPES 14 //count this manually
const uint16_t kHM10SlaveID[HM10_TYPES]={
0x0098, // Flora
@ -314,7 +392,9 @@ const uint16_t kHM10SlaveID[HM10_TYPES]={
0x0153, // yee-rc
0x0387, // MHO-C401
0x06d3, // MHO-C303
0x0a1c // ATC -> this is a fake ID
0x0a1c, // ATC -> this is a fake ID
0x181d, // Mi Scale V1
0x181b // Mi Scale V2
};
const char kHM10DeviceType1[] PROGMEM = "Flora";
@ -329,8 +409,10 @@ const char kHM10DeviceType9[] PROGMEM = "YEERC";
const char kHM10DeviceType10[] PROGMEM ="MHOC401";
const char kHM10DeviceType11[] PROGMEM ="MHOC303";
const char kHM10DeviceType12[] PROGMEM ="ATC";
const char kHM10DeviceType13[] PROGMEM ="MISCALEV1";
const char kHM10DeviceType14[] PROGMEM ="MISCALEV2";
const char * kHM10DeviceType[] PROGMEM = {kHM10DeviceType1,kHM10DeviceType2,kHM10DeviceType3,kHM10DeviceType4,kHM10DeviceType5,kHM10DeviceType6,kHM10DeviceType7,kHM10DeviceType8,kHM10DeviceType9,kHM10DeviceType10,kHM10DeviceType11,kHM10DeviceType12};
const char * kHM10DeviceType[] PROGMEM = {kHM10DeviceType1,kHM10DeviceType2,kHM10DeviceType3,kHM10DeviceType4,kHM10DeviceType5,kHM10DeviceType6,kHM10DeviceType7,kHM10DeviceType8,kHM10DeviceType9,kHM10DeviceType10,kHM10DeviceType11,kHM10DeviceType12,kHM10DeviceType13,kHM10DeviceType14};
/*********************************************************************************************\
* enumerations
@ -599,6 +681,13 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, int _rssi){
case YEERC:
_newSensor.feature.Btn=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;
@ -804,6 +893,102 @@ void HM10parseCGD1Packet(char * _buf, uint32_t _slot){ // no MiBeacon
if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1;
}
void HM10ParseMiScalePacket(char * _buf, uint32_t _slot, uint16_t _type){
//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;
// Mi Scale V1
if (_type == 0x181d){ // 14-1-1-2
if ((_slot >= 0) && (_slot < MIBLEsensors.size())){
DEBUG_SENSOR_LOG(PSTR("HM10: %s: at slot %u"), kHM10DeviceType[MIBLEsensors[_slot].type-1],_slot);
MIBLEsensors[_slot].eventType.scale = 1;
MIBLEsensors[_slot].weight_stabilized = (_packetV1->status & (1 << 5)) ? 1 : 0;
MIBLEsensors[_slot].weight_removed = (_packetV1->status & (1 << 7)) ? 1 : 0;
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;
}
// Can be changed to memcpy or smthng else ?
MIBLEsensors[_slot].datetime.year = _packetV2->year;
MIBLEsensors[_slot].datetime.month = _packetV2->month;
MIBLEsensors[_slot].datetime.day = _packetV2->day;
MIBLEsensors[_slot].datetime.hour = _packetV2->hour;
MIBLEsensors[_slot].datetime.minute = _packetV2->minute;
MIBLEsensors[_slot].datetime.second = _packetV2->second;
MIBLEsensors[_slot].shallSendMQTT = 1;
bool triggerTele = MIBLEsensors[_slot].weight_stabilized && ! MIBLEsensors[_slot].weight_removed && MIBLEsensors[_slot].weight > 0;
if(HM10.option.directBridgeMode || triggerTele) HM10.mode.shallTriggerTele = 1;
}
}
// Mi Scale V2
else if (_type == 0x181b){ // 17-1-1-2
if ((_slot >= 0) && (_slot < MIBLEsensors.size())){
DEBUG_SENSOR_LOG(PSTR("HM10: %s: at slot %u"), kHM10DeviceType[MIBLEsensors[_slot].type-1],_slot);
MIBLEsensors[_slot].eventType.scale = 1;
MIBLEsensors[_slot].has_impedance = (_packetV2->status & (1 << 1)) ? 1 : 0;
MIBLEsensors[_slot].weight_stabilized = (_packetV2->status & (1 << 5)) ? 1 : 0;
MIBLEsensors[_slot].impedance_stabilized = (_packetV2->status & (1 << 1)) ? 1 : 0;
MIBLEsensors[_slot].weight_removed = (_packetV2->status & (1 << 7)) ? 1 : 0;
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 (MIBLEsensors[_slot].weight_removed) {
MIBLEsensors[_slot].weight = 0.0f;
MIBLEsensors[_slot].impedance = 0;
}
else if (MIBLEsensors[_slot].has_impedance) {
MIBLEsensors[_slot].impedance = _packetV2->impedance;
}
// Can be changed to memcpy or smthng else ?
MIBLEsensors[_slot].datetime.year = _packetV2->year;
MIBLEsensors[_slot].datetime.month = _packetV2->month;
MIBLEsensors[_slot].datetime.day = _packetV2->day;
MIBLEsensors[_slot].datetime.hour = _packetV2->hour;
MIBLEsensors[_slot].datetime.minute = _packetV2->minute;
MIBLEsensors[_slot].datetime.second = _packetV2->second;
MIBLEsensors[_slot].shallSendMQTT = 1;
bool triggerTele = MIBLEsensors[_slot].weight_stabilized && ! MIBLEsensors[_slot].weight_removed && MIBLEsensors[_slot].weight > 0;
if(HM10.option.directBridgeMode || triggerTele) HM10.mode.shallTriggerTele = 1;
}
}
}
void HM10ParseResponse(char *buf, uint16_t bufsize) {
if (!strncmp(buf,"HMSoft",6)) { //8
const char* _fw = "000";
@ -1168,12 +1353,15 @@ bool HM10SerialHandleFeedback(){ // every 50 milliseconds
}
uint16_t _type = (uint8_t)HM10.rxAdvertisement.svcData[5]*256 + (uint8_t)HM10.rxAdvertisement.svcData[4];
// AddLog(LOG_LEVEL_DEBUG, PSTR("%04x %02x %04x %04x %04x"),HM10.rxAdvertisement.UUID,HM10.rxAdvertisement.TX,HM10.rxAdvertisement.CID,HM10.rxAdvertisement.SVC, _type);
DEBUG_SENSOR_LOG(PSTR("HM10: UUID %04x, TX: %02x, CID: %04x, SVC: %04x"), HM10.rxAdvertisement.UUID,HM10.rxAdvertisement.TX,HM10.rxAdvertisement.CID,HM10.rxAdvertisement.SVC);
if(HM10.rxAdvertisement.SVC==0x181a) _type = 0xa1c;
else if(HM10.rxAdvertisement.SVC==0xfdcd) _type = 0x0576;
else if(HM10.rxAdvertisement.SVC==0x181b || HM10.rxAdvertisement.SVC==0x181d) _type = 0x181b;
uint16_t _slot = MIBLEgetSensorSlot(HM10.rxAdvertisement.MAC, _type, HM10.rxAdvertisement.RSSI);
if(_slot!=0xff){
if (_type==0xa1c) HM10parseATC((char*)HM10.rxAdvertisement.svcData+2,_slot);
else if (_type==0x0576) HM10parseCGD1Packet((char*)HM10.rxAdvertisement.svcData+2,_slot);
else if (_type==0x181b) HM10ParseMiScalePacket((char*)HM10.rxAdvertisement.svcData+2,_slot, _type);
else HM10parseMiBeacon((char*)HM10.rxAdvertisement.svcData+2,_slot);
}
else{
@ -1425,10 +1613,10 @@ void HM10_TaskEvery100ms(){
// AddLog(LOG_LEVEL_DEBUG, PSTR("%sFound done HM10_TASK"),D_CMND_HM10);
// AddLog(LOG_LEVEL_DEBUG, PSTR("%snext slot:%u, i: %u"),D_CMND_HM10, HM10_TASK_LIST[i+1][0],i);
if(HM10_TASK_LIST[i+1][0] == TASK_HM10_NOTASK) { // check the next entry and if there is none
DEBUG_SENSOR_LOG(PSTR("%sno Tasks left"),D_CMND_HM10);
DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK_DONE current slot %u"),D_CMND_HM10, i);
DEBUG_SENSOR_LOG(PSTR("%s: no Tasks left"),D_CMND_HM10);
DEBUG_SENSOR_LOG(PSTR("%s: HM10_TASK_DONE current slot %u"),D_CMND_HM10, i);
for (uint8_t j = 0; j < HM10_MAX_TASK_NUMBER+1; j++) { // do a clean-up:
DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK cleanup slot %u"),D_CMND_HM10, j);
DEBUG_SENSOR_LOG(PSTR("%s: HM10_TASK cleanup slot %u"),D_CMND_HM10, j);
HM10_TASK_LIST[j][0] = TASK_HM10_NOTASK; // reset all task entries
HM10_TASK_LIST[j][1] = 0; // reset all delays
}
@ -1927,6 +2115,39 @@ void HM10Show(bool json)
}
}
}
//Scale
if (MIBLEsensors[i].feature.scale){
if(MIBLEsensors[i].eventType.scale || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate
#ifdef USE_HOME_ASSISTANT
||(hass_mode==2)
#endif //USE_HOME_ASSISTANT
){
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"weight_removed\":%u"), MIBLEsensors[i].weight_removed);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"weight_stabilized\":%u"), MIBLEsensors[i].weight_stabilized);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"weight_unit\":\"%s\""), MIBLEsensors[i].weight_unit);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"" D_JSON_WEIGHT "\":%*_f"),Settings->flag2.weight_resolution, &MIBLEsensors[i].weight);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"datetime\":\"%02u/%02u/%04u %02u:%02u:%02u\"")
, MIBLEsensors[i].datetime.day
, MIBLEsensors[i].datetime.month
, MIBLEsensors[i].datetime.year
, MIBLEsensors[i].datetime.hour
, MIBLEsensors[i].datetime.minute
, MIBLEsensors[i].datetime.second
);
}
}
if (MIBLEsensors[i].feature.impedance){
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"impedance\":%u"), MIBLEsensors[i].has_impedance ? MIBLEsensors[i].impedance : 0);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"impedance_stabilized\":%u"), MIBLEsensors[i].impedance_stabilized);
}
if (HM10.option.showRSSI) {
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"RSSI\":%d"), MIBLEsensors[i].rssi);
@ -1992,7 +2213,18 @@ void HM10Show(bool json)
WSContentSend_PD(HTTP_HM10_FLORA_DATA, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].fertility);
}
}
if (MIBLEsensors[i].type>FLORA){ // everything "above" Flora
if (MIBLEsensors[i].type==MI_SCALE_V1 || MIBLEsensors[i].type==MI_SCALE_V2){
if (MIBLEsensors[i].feature.scale){
WSContentSend_PD(HTTP_MISCALE_WEIGHT, kHM10DeviceType[MIBLEsensors[i].type-1], Settings->flag2.weight_resolution, &MIBLEsensors[i].weight, MIBLEsensors[i].weight_unit);
WSContentSend_PD(HTTP_MISCALE_WEIGHT_REMOVED, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].weight_removed ? PSTR("yes") : PSTR("no"));
WSContentSend_PD(HTTP_MISCALE_STABILIZED, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].weight_stabilized ? PSTR("yes") : PSTR("no"));
}
if (MIBLEsensors[i].feature.impedance){
WSContentSend_PD(HTTP_MISCALE_IMPEDANCE, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].has_impedance ? MIBLEsensors[i].impedance : 0);
}
}
else if (MIBLEsensors[i].type>FLORA){ // everything "above" Flora
if(!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)){
WSContentSend_THD(kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp, MIBLEsensors[i].hum);
}