Skip to content

Commit

Permalink
Extended XMTZC05HM decoder (#74)
Browse files Browse the repository at this point in the history
* Extension of XMTZC05HM decoder

- Pulled back _XMTZC05HM_json_props into XMTZC05HM_json.h
- Renamed "model" to more informative "Mi_Body_Scale_2" and amended description in the docs
- Implemented 'stabilised' only decoding for XMTZC05HM
- Recognises 'stabilised' 2 (scale display active) and 'stabilised' a (scale display deactivated) to guarantee published 'stabilised' reading without the need for very short TimeBtwRead
- New 'mode' attribute to indicate if the weighing was of a person or a small object (if this option is activated in the scale's settings). This allows filtering to only record person readings in HA or openHAB
- Only publish 'impedance' attribute if the weighing of a person was done barefoot and the impedance value could be recorded correctly

I suspect that the 1st version of the Mi Body Composition Scale (XMTZCO2HM) is also being recognised by this decoder, and should therefor be mentioned in the docs and published model_id, but without confirmation of XMTZCO2HM owners this is just a guess.

* Extend properties conditions to use  "OR" and "AND" params.

* XMTZC05HM final adjustments

Small final changes
• abbreviated property names for discovery inclusion
• decoder back to original _XMTZC05HM_json

Co-authored-by: h2zero <[email protected]>
  • Loading branch information
DigiH and h2zero authored Feb 21, 2022
1 parent fe3b268 commit 0f31a2b
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 157 deletions.
10 changes: 5 additions & 5 deletions docs/devices/XMTZC05HM.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Xiaomi MiScale V2
# Xiaomi Mi Body Composition Scale 2

|Model Id|[XMTZC05HM](https://github.com/theengs/decoder/blob/development/src/devices/XMTZC05HM_json.h)|
|-|-|
|Brand|Xiaomi|
|Model|MiScale V2|
|Short Description|Second version of the scale|
|Model|Mi Body Scale 2|
|Short Description|Second version of the Mi Body Composition Scale|
|Communication|BLE broadcast|
|Frequency|2.4Ghz|
|Power source|3 AAA|
|Exchanged data|unit, weight, impedance|
|Power source|4 AAA|
|Exchanged data|weighing_mode, unit, weight, impedance|
|Encrypted|No|
261 changes: 139 additions & 122 deletions src/decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,145 +281,162 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
JsonObject prop = kv.value().as<JsonObject>();
JsonArray prop_condition = prop["condition"];

if (prop_condition.isNull() || strstr((const char*)prop_condition[0], "servicedata") != nullptr ||
strstr((const char*)prop_condition[0], "manufacturerdata") != nullptr) {
if (prop_condition.isNull() ||
(svc_data && svc_data[prop_condition[1].as<int>()] == *prop_condition[2].as<const char*>()) ||
(mfg_data && mfg_data[prop_condition[1].as<int>()] == *prop_condition[2].as<const char*>())) {
JsonArray decoder = prop["decoder"];
if (strstr((const char*)decoder[0], "value_from_hex_data") != nullptr) {
const char* src = svc_data;
if (strstr((const char*)decoder[1], "manufacturerdata")) {
src = mfg_data;
}
int cond_size = prop_condition.size();
bool cond_met = prop_condition.isNull();

for (int i = 0; i < cond_size; i += 4) {
if (svc_data &&
strstr((const char*)prop_condition[i], "servicedata") != nullptr &&
svc_data[prop_condition[i + 1].as<int>()] == *prop_condition[i + 2].as<const char*>()) {
cond_met = true;
} else if (mfg_data &&
strstr((const char*)prop_condition[i], "manufacturerdata") != nullptr &&
mfg_data[prop_condition[i + 1].as<int>()] == *prop_condition[i + 2].as<const char*>()) {
cond_met = true;
}

/* use a double for all values and cast later if required */
double temp_val;
static long cal_val = 0;
if (!cond_met && cond_size > (i + 3) && *prop_condition[i + 3].as<const char*>() == '|') {
continue;
} else if (cond_met && cond_size > (i + 3) && *prop_condition[i + 3].as<const char*>() == '&') {
cond_met = false;
continue;
}
}

if (data_index_is_valid(src, decoder[2].as<int>(), decoder[3].as<int>())) {
decoder_function dec_fun = &TheengsDecoder::value_from_hex_string;
if (cond_met) {
JsonArray decoder = prop["decoder"];
if (strstr((const char*)decoder[0], "value_from_hex_data") != nullptr) {
const char* src = svc_data;
if (strstr((const char*)decoder[1], "manufacturerdata")) {
src = mfg_data;
}

if (strstr((const char*)decoder[0], "bf") != nullptr) {
dec_fun = &TheengsDecoder::bf_value_from_hex_string;
}
/* use a double for all values and cast later if required */
double temp_val;
static long cal_val = 0;

temp_val = (this->*dec_fun)(src, decoder[2].as<int>(),
decoder[3].as<int>(),
decoder[4].as<bool>(),
decoder[5].isNull() ? true : decoder[5].as<bool>());
if (data_index_is_valid(src, decoder[2].as<int>(), decoder[3].as<int>())) {
decoder_function dec_fun = &TheengsDecoder::value_from_hex_string;

} else {
break;
if (strstr((const char*)decoder[0], "bf") != nullptr) {
dec_fun = &TheengsDecoder::bf_value_from_hex_string;
}

/* Do any required post processing of the value */
if (prop.containsKey("post_proc")) {
JsonArray post_proc = prop["post_proc"];
for (unsigned int i = 0; i < post_proc.size(); i += 2) {
if (cal_val && post_proc[i + 1].as<const char*>() != NULL &&
strncmp(post_proc[i + 1].as<const char*>(), ".cal", 4) == 0) {
switch (*post_proc[i].as<const char*>()) {
case '/':
temp_val /= cal_val;
break;
case '*':
temp_val *= cal_val;
break;
case '-':
temp_val -= cal_val;
break;
case '+':
temp_val += cal_val;
break;
temp_val = (this->*dec_fun)(src, decoder[2].as<int>(),
decoder[3].as<int>(),
decoder[4].as<bool>(),
decoder[5].isNull() ? true : decoder[5].as<bool>());

} else {
break;
}

/* Do any required post processing of the value */
if (prop.containsKey("post_proc")) {
JsonArray post_proc = prop["post_proc"];
for (unsigned int i = 0; i < post_proc.size(); i += 2) {
if (cal_val && post_proc[i + 1].as<const char*>() != NULL &&
strncmp(post_proc[i + 1].as<const char*>(), ".cal", 4) == 0) {
switch (*post_proc[i].as<const char*>()) {
case '/':
temp_val /= cal_val;
break;
case '*':
temp_val *= cal_val;
break;
case '-':
temp_val -= cal_val;
break;
case '+':
temp_val += cal_val;
break;
}
} else {
switch (*post_proc[i].as<const char*>()) {
case '/':
temp_val /= post_proc[i + 1].as<double>();
break;
case '*':
temp_val *= post_proc[i + 1].as<double>();
break;
case '-':
temp_val -= post_proc[i + 1].as<double>();
break;
case '+':
temp_val += post_proc[i + 1].as<double>();
break;
case '%': {
long val = (long)temp_val;
temp_val = val % post_proc[i + 1].as<long>();
break;
}
case '<': {
long val = (long)temp_val;
temp_val = val << post_proc[i + 1].as<unsigned int>();
break;
}
case '>': {
long val = (long)temp_val;
temp_val = val >> post_proc[i + 1].as<unsigned int>();
break;
}
} else {
switch (*post_proc[i].as<const char*>()) {
case '/':
temp_val /= post_proc[i + 1].as<double>();
break;
case '*':
temp_val *= post_proc[i + 1].as<double>();
break;
case '-':
temp_val -= post_proc[i + 1].as<double>();
break;
case '+':
temp_val += post_proc[i + 1].as<double>();
break;
case '%': {
long val = (long)temp_val;
temp_val = val % post_proc[i + 1].as<long>();
break;
}
case '<': {
long val = (long)temp_val;
temp_val = val << post_proc[i + 1].as<unsigned int>();
break;
}
case '>': {
long val = (long)temp_val;
temp_val = val >> post_proc[i + 1].as<unsigned int>();
break;
}
case '!': {
bool val = (bool)temp_val;
temp_val = !val;
break;
}
case '&': {
long val = (long)temp_val;
temp_val = val & post_proc[i + 1].as<unsigned int>();
break;
}
case '!': {
bool val = (bool)temp_val;
temp_val = !val;
break;
}
case '&': {
long val = (long)temp_val;
temp_val = val & post_proc[i + 1].as<unsigned int>();
break;
}
}
}
}
}

/* If there is any underscores at the beginning of the property name, there is multiple
* properties of this type, we need remove the underscores for creating the key.
*/
std::string _key = sanitizeJsonKey(kv.key().c_str());

/* calculation values extracted from data are not added to the deocded outupt
* instead we store them teporarily to use with the next data properties.
*/
if (_key == ".cal") {
cal_val = (long)temp_val;
continue;
}

/* Cast to a differnt value type if specified */
if (prop.containsKey("is_bool")) {
jsondata[_key] = (bool)temp_val;
} else {
jsondata[_key] = temp_val;
}
/* If there is any underscores at the beginning of the property name, there is multiple
* properties of this type, we need remove the underscores for creating the key.
*/
std::string _key = sanitizeJsonKey(kv.key().c_str());

/* calculation values extracted from data are not added to the deocded outupt
* instead we store them teporarily to use with the next data properties.
*/
if (_key == ".cal") {
cal_val = (long)temp_val;
continue;
}

/* If the property is temp in C, make sure to convert and add temp in F */
if (_key.find("tempc", 0, 5) != std::string::npos) {
double tc = jsondata[_key];
_key[4] = 'f';
jsondata[_key] = tc * 1.8 + 32;
_key[4] = 'c';
}
/* Cast to a differnt value type if specified */
if (prop.containsKey("is_bool")) {
jsondata[_key] = (bool)temp_val;
} else {
jsondata[_key] = temp_val;
}

success = i_main;
DEBUG_PRINT("found value = %s : %.2f\n", _key.c_str(), jsondata[_key].as<double>());
} else if (strstr((const char*)decoder[0], "static_value") != nullptr) {
jsondata[sanitizeJsonKey(kv.key().c_str())] = decoder[1];
success = i_main;
} else if (strstr((const char*)decoder[0], "string_from_hex_data") != nullptr) {
const char* src = svc_data;
if (strstr((const char*)decoder[1], "manufacturerdata")) {
src = mfg_data;
}
/* If the property is temp in C, make sure to convert and add temp in F */
if (_key.find("tempc", 0, 5) != std::string::npos) {
double tc = jsondata[_key];
_key[4] = 'f';
jsondata[_key] = tc * 1.8 + 32;
_key[4] = 'c';
}

std::string value(src + decoder[2].as<int>(), decoder[3].as<int>());
jsondata[sanitizeJsonKey(kv.key().c_str())] = value;
success = i_main;
success = i_main;
DEBUG_PRINT("found value = %s : %.2f\n", _key.c_str(), jsondata[_key].as<double>());
} else if (strstr((const char*)decoder[0], "static_value") != nullptr) {
jsondata[sanitizeJsonKey(kv.key().c_str())] = decoder[1];
success = i_main;
} else if (strstr((const char*)decoder[0], "string_from_hex_data") != nullptr) {
const char* src = svc_data;
if (strstr((const char*)decoder[1], "manufacturerdata")) {
src = mfg_data;
}

std::string value(src + decoder[2].as<int>(), decoder[3].as<int>());
jsondata[sanitizeJsonKey(kv.key().c_str())] = value;
success = i_main;
}
}
}
Expand Down
50 changes: 37 additions & 13 deletions src/devices/XMTZC05HM_json.h
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
#include "common_props.h"

const char* _XMTZC05HM_json = "{\"brand\":\"Xiaomi\",\"model\":\"Miscale_v2\",\"model_id\":\"XMTZC05HM\",\"condition\":[\"uuid\",\"contain\",\"181b\"],\"properties\":{\"unit\":{\"condition\":[\"servicedata\",1,\"2\"],\"decoder\":[\"static_value\",\"kg\"]},\"weight\":{\"condition\":[\"servicedata\",1,\"2\"],\"decoder\":[\"value_from_hex_data\",\"servicedata\",22,4,true,false],\"post_proc\":[\"/\",200]},\"_unit\":{\"condition\":[\"servicedata\",1,\"3\"],\"decoder\":[\"static_value\",\"lbs\"]},\"_weight\":{\"condition\":[\"servicedata\",1,\"3\"],\"decoder\":[\"value_from_hex_data\",\"servicedata\",22,4,true,false],\"post_proc\":[\"/\",100]},\"impedance\":{\"decoder\":[\"value_from_hex_data\",\"servicedata\",18,4,true,false]}}}";
const char* _XMTZC05HM_json = "{\"brand\":\"Xiaomi\",\"model\":\"Mi_Body_Scale_2\",\"model_id\":\"XMTZC05HM\",\"condition\":[\"servicedata\",\"index\",2,\"2\",\"|\",\"servicedata\",\"index\",2,\"a\",\"&\",\"uuid\",\"contain\",\"181b\"],\"properties\":{\"weighing_mode\":{\"condition\":[\"servicedata\",1,\"2\",\"|\",\"servicedata\",1,\"3\"],\"decoder\":[\"static_value\",\"person\"]},\"_weighing_mode\":{\"condition\":[\"servicedata\",1,\"6\",\"|\",\"servicedata\",1,\"7\"],\"decoder\":[\"static_value\",\"object\"]},\"unit\":{\"condition\":[\"servicedata\",1,\"2\",\"|\",\"servicedata\",1,\"6\"],\"decoder\":[\"static_value\",\"kg\"]},\"_unit\":{\"condition\":[\"servicedata\",1,\"3\",\"|\",\"servicedata\",1,\"7\"],\"decoder\":[\"static_value\",\"lbs\"]},\"weight\":{\"condition\":[\"servicedata\",1,\"2\",\"|\",\"servicedata\",1,\"6\"],\"decoder\":[\"value_from_hex_data\",\"servicedata\",22,4,true,false],\"post_proc\":[\"/\",200]},\"_weight\":{\"condition\":[\"servicedata\",1,\"3\",\"|\",\"servicedata\",1,\"7\"],\"decoder\":[\"value_from_hex_data\",\"servicedata\",22,4,true,false],\"post_proc\":[\"/\",100]},\"impedance\":{\"condition\":[\"servicedata\",3,\"6\"],\"decoder\":[\"value_from_hex_data\",\"servicedata\",18,4,true,false]}}}";
/*R""""(
{
"brand":"Xiaomi",
"model":"Miscale_v2",
"model":"Mi_Body_Scale_2",
"model_id":"XMTZC05HM",
"condition":["uuid", "contain", "181b"],
"condition":["servicedata", "index", 2, "2", "|", "servicedata", "index", 2, "a", "&", "uuid", "contain", "181b"],
"properties":{
"weighing_mode":{
"condition":["servicedata", 1, "2", "|", "servicedata", 1, "3"],
"decoder":["static_value", "person"]
},
"_weighing_mode":{
"condition":["servicedata", 1, "6", "|", "servicedata", 1, "7"],
"decoder":["static_value", "object"]
},
"unit":{
"condition":["servicedata", 1, "2"],
"condition":["servicedata", 1, "2", "|", "servicedata", 1, "6"],
"decoder":["static_value", "kg"]
},
"_unit":{
"condition":["servicedata", 1, "3", "|", "servicedata", 1, "7"],
"decoder":["static_value", "lbs"]
},
"weight":{
"condition":["servicedata", 1, "2"],
"condition":["servicedata", 1, "2", "|", "servicedata", 1, "6"],
"decoder":["value_from_hex_data", "servicedata", 22, 4, true, false],
"post_proc":["/", 200]
},
"_unit":{
"condition":["servicedata", 1, "3"],
"decoder":["static_value", "lbs"]
},
"_weight":{
"condition":["servicedata", 1, "3"],
"condition":["servicedata", 1, "3", "|", "servicedata", 1, "7"],
"decoder":["value_from_hex_data", "servicedata", 22, 4, true, false],
"post_proc":["/", 100]
},
"impedance":{
"condition":["servicedata", 3, "6"],
"decoder":["value_from_hex_data", "servicedata", 18, 4, true, false]
}
}
})"""";*/

const char* _XMTZC05HM_json_props = _common_weight_impedance_props;
const char* _XMTZC05HM_json_props = "{\"properties\":{\"weighing_mode\":{\"unit\":\"string\",\"name\":\"weighing_mode\"},\"weight\":{\"unit\":\"kg\",\"name\":\"weight\"},\"impedance\":{\"unit\":\"Ohm\",\"name\":\"impedance\"}}}";
/*R""""(
{
"properties":{
"weighing_mode":{
"unit":"string",
"name":"weighing_mode"
},
"weight":{
"unit":"kg",
"name":"weight"
},
"impedance":{
"unit":"Ohm",
"name":"impedance"
}
}
})"""";*/
15 changes: 0 additions & 15 deletions src/devices/common_props.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,4 @@ const char* _common_weight_props = "{\"properties\":{\"weight\":{\"unit\":\"kg\"
}
})"""";*/

const char* _common_weight_impedance_props = "{\"properties\":{\"weight\":{\"unit\":\"kg\",\"name\":\"weight\"},\"impedance\":{\"unit\":\"Ohm\",\"name\":\"impedance\"}}}";
/*R""""(
{
"properties":{
"weight":{
"unit":"kg",
"name":"weight"
}
"impedance":{
"unit":"Ohm",
"name":"impedance"
}
}
})"""";*/

#endif
Loading

0 comments on commit 0f31a2b

Please sign in to comment.