Skip to content

Commit

Permalink
Change property condition check to a function, add nestng support. (#98)
Browse files Browse the repository at this point in the history
* Add property condition check function with NOT support.

* Add macros for "servicedata" and "manufacturerdata"

* Add support for nested arrays in property conditions.

* Allow for using NOT property conditions at any position.

This will allow for the NOT condition to be placed in any position within a property condition, allowing for greater efficiency.

* Minor code cleanup

* Adapt IBT_2X decode to use the added prop condition functionality.

* changes test additions

Co-authored-by: DigiH <[email protected]>
  • Loading branch information
h2zero and DigiH authored Mar 23, 2022
1 parent e66adc7 commit 273c8af
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 55 deletions.
122 changes: 79 additions & 43 deletions src/decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
static size_t peakDocSize = 0;
#endif

#define SVC_DATA "servicedata"
#define MFG_DATA "manufacturerdata"

typedef double (TheengsDecoder::*decoder_function)(const char* data_str,
int offset, int data_length,
bool reverse, bool canBeNegative);
Expand Down Expand Up @@ -146,6 +149,74 @@ int TheengsDecoder::data_length_is_valid(size_t data_len, size_t default_min,
return -1;
}

bool TheengsDecoder::checkPropCondition(const JsonArray& prop_condition,
const char* svc_data,
const char* mfg_data) {
int cond_size = prop_condition.size();
bool cond_met = prop_condition.isNull();

if (!cond_met) {
for (int i = 0; i < cond_size; i += 4) {
if (prop_condition[i].is<JsonArray>()) {
DEBUG_PRINT("found nested array\n");
cond_met = checkPropCondition(prop_condition[i], svc_data, mfg_data);

if (++i < cond_size) {
if (!cond_met && *prop_condition[i].as<const char*>() == '|') {
} else if (cond_met && *prop_condition[i].as<const char*>() == '&') {
cond_met = false;
} else {
break;
}
i++;
} else {
break;
}
}

bool inverse = *(const char*)prop_condition[i + 2] == '!';
const char* prop_data_src = prop_condition[i];
const char* data_src = nullptr;

if (svc_data && strstr(prop_data_src, SVC_DATA) != nullptr) {
data_src = svc_data;
} else if (mfg_data && strstr(prop_data_src, MFG_DATA) != nullptr) {
data_src = mfg_data;
}

if (data_src) {
size_t cond_len = strlen(prop_condition[i + 2 + inverse].as<const char*>());
if (!strncmp(&data_src[prop_condition[i + 1].as<int>()],
prop_condition[i + 2 + inverse].as<const char*>(), cond_len)) {
cond_met = inverse ? false : true;
} else if (inverse) {
cond_met = true;
}
} else {
DEBUG_PRINT("ERROR property condition data source invalid\n");
return false;
}

if (inverse) {
i++;
}

if (cond_size > (i + 3)) {
if (!cond_met && *prop_condition[i + 3].as<const char*>() == '|') {
continue;
} else if (cond_met && *prop_condition[i + 3].as<const char*>() == '&') {
cond_met = false;
continue;
} else {
break;
}
}
}
}

return cond_met;
}

/*
* @breif Compares the input json values to the known devices and
* decodes the data if a match is found.
Expand All @@ -156,8 +227,8 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
#else
DynamicJsonDocument doc(m_docMax);
#endif
const char* svc_data = jsondata["servicedata"].as<const char*>();
const char* mfg_data = jsondata["manufacturerdata"].as<const char*>();
const char* svc_data = jsondata[SVC_DATA].as<const char*>();
const char* mfg_data = jsondata[MFG_DATA].as<const char*>();
const char* dev_name = jsondata["name"].as<const char*>();
const char* svc_uuid = jsondata["servicedatauuid"].as<const char*>();
int success = -1;
Expand Down Expand Up @@ -191,7 +262,7 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
const char* cmp_str;
const char* cond_str = condition[i].as<const char*>();
int len_idx;
if (svc_data != nullptr && strstr(cond_str, "servicedata") != nullptr) {
if (svc_data != nullptr && strstr(cond_str, SVC_DATA) != nullptr) {
len_idx = data_length_is_valid(strlen(svc_data), m_minSvcDataLen, condition, i);
if (len_idx >= 0) {
i += len_idx;
Expand All @@ -201,7 +272,7 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
match = false;
break;
}
} else if (mfg_data != nullptr && strstr(cond_str, "manufacturerdata") != nullptr) {
} else if (mfg_data != nullptr && strstr(cond_str, MFG_DATA) != nullptr) {
len_idx = data_length_is_valid(strlen(mfg_data), m_minMfgDataLen, condition, i);
if (len_idx >= 0) {
i += len_idx;
Expand Down Expand Up @@ -294,48 +365,13 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
/* Loop through all the devices properties and extract the values */
for (JsonPair kv : properties) {
JsonObject prop = kv.value().as<JsonObject>();
JsonArray prop_condition = prop["condition"];

int cond_size = prop_condition.size();
bool cond_met = prop_condition.isNull();

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

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;
}
}
bool cond_met = checkPropCondition(prop["condition"], svc_data, mfg_data);

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")) {
if (strstr((const char*)decoder[1], MFG_DATA)) {
src = mfg_data;
}

Expand Down Expand Up @@ -455,13 +491,13 @@ int TheengsDecoder::decodeBLEJson(JsonObject& jsondata) {
DEBUG_PRINT("found value = %s : %.2f\n", _key.c_str(), jsondata[_key].as<double>());
} else if (strstr((const char*)decoder[0], "static_value") != nullptr) {
if (prop.containsKey("is_bool") && !decoder[1].is<std::string>()) {
decoder[1] = (bool)decoder[1] ;
decoder[1] = (bool)decoder[1];
}
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")) {
if (strstr((const char*)decoder[1], MFG_DATA)) {
src = mfg_data;
}

Expand Down
11 changes: 6 additions & 5 deletions src/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ class TheengsDecoder {
};

private:
void reverse_hex_data(const char* in, char* out, int l);
double value_from_hex_string(const char* data_str, int offset, int data_length, bool reverse, bool canBeNegative = true);
double bf_value_from_hex_string(const char* data_str, int offset, int data_length, bool reverse, bool canBeNegative = true);
bool data_index_is_valid(const char* str, size_t index, size_t len);
int data_length_is_valid(size_t data_len, size_t default_min, JsonArray& condition, int idx);
void reverse_hex_data(const char* in, char* out, int l);
double value_from_hex_string(const char* data_str, int offset, int data_length, bool reverse, bool canBeNegative = true);
double bf_value_from_hex_string(const char* data_str, int offset, int data_length, bool reverse, bool canBeNegative = true);
bool data_index_is_valid(const char* str, size_t index, size_t len);
int data_length_is_valid(size_t data_len, size_t default_min, JsonArray& condition, int idx);
bool checkPropCondition(const JsonArray& prop, const char* svc_data, const char* mfg_data);
std::string sanitizeJsonKey(const char* key_in);

size_t m_docMax = 7168;
Expand Down
10 changes: 5 additions & 5 deletions src/devices/IBT_2X_json.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const char* _IBT_2X_json = "{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"condition\":[\"name\",\"index\",0,\"iBBQ\",\"&\",\"manufacturerdata\",\">=\",24,\"index\",0,\"0000\",\"&\",\"manufacturerdata\",\"<=\",28,\"index\",0,\"0000\"],\"properties\":{\"tempc\":{\"condition\":[\"manufacturerdata\",0,\"00000000\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",16,4,true,false],\"post_proc\":[\"/\",10]},\"_tempc\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"tempc2\":{\"condition\":[\"manufacturerdata\",0,\"00000000\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"_tempc2\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",24,4,true,false],\"post_proc\":[\"/\",10]}}}";
const char* _IBT_2X_json = "{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"condition\":[\"name\",\"index\",0,\"iBBQ\",\"&\",\"manufacturerdata\",\">=\",24,\"index\",0,\"0000\",\"&\",\"manufacturerdata\",\"<=\",28,\"index\",0,\"0000\"],\"properties\":{\"tempc\":{\"condition\":[\"manufacturerdata\",0,\"!\",\"00000000\",\"&\",\"manufacturerdata\",16,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",16,4,true,false],\"post_proc\":[\"/\",10]},\"_tempc\":{\"condition\":[\"manufacturerdata\",0,\"00000000\",\"&\",\"manufacturerdata\",20,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"tempc2\":{\"condition\":[\"manufacturerdata\",0,\"!\",\"00000000\",\"&\",\"manufacturerdata\",20,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"_tempc2\":{\"condition\":[\"manufacturerdata\",0,\"00000000\",\"&\",\"manufacturerdata\",24,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",24,4,true,false],\"post_proc\":[\"/\",10]}}}";
/*R""""(
{
"brand":"Inkbird",
Expand All @@ -7,22 +7,22 @@ const char* _IBT_2X_json = "{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id
"condition":["name", "index", 0, "iBBQ","&","manufacturerdata", ">=", 24, "index", 0, "0000","&","manufacturerdata", "<=", 28, "index", 0, "0000"],
"properties":{
"tempc":{
"condition":["manufacturerdata", 0, "00000000", "&", "manufacturerdata", 16, "!", "f6ff"],
"condition":["manufacturerdata", 0, "!", "00000000", "&", "manufacturerdata", 16, "!", "f6ff"],
"decoder":["value_from_hex_data", "manufacturerdata", 16, 4, true, false],
"post_proc":["/", 10]
},
"_tempc":{
"condition":["manufacturerdata", 20, "!", "f6ff"],
"condition":["manufacturerdata", 0, "00000000", "&", "manufacturerdata", 20, "!", "f6ff"],
"decoder":["value_from_hex_data", "manufacturerdata", 20, 4, true, false],
"post_proc":["/", 10]
},
"tempc2":{
"condition":["manufacturerdata", 0, "00000000", "&", "manufacturerdata", 20, "!", "f6ff"],
"condition":["manufacturerdata", 0, "!", "00000000", "&", "manufacturerdata", 20, "!", "f6ff"],
"decoder":["value_from_hex_data", "manufacturerdata", 20, 4, true, false],
"post_proc":["/", 10]
},
"_tempc2":{
"condition":["manufacturerdata", 24, "!", "f6ff"],
"condition":["manufacturerdata", 0, "00000000", "&", "manufacturerdata", 24, "!", "f6ff"],
"decoder":["value_from_hex_data", "manufacturerdata", 24, 4, true, false],
"post_proc":["/", 10]
}
Expand Down
3 changes: 1 addition & 2 deletions src/devices/IBT_4XS_json.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const char* _IBT_4XS_json = "{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-4XS\",\"condition\":[\"name\",\"index\",0,\"iBBQ\",\"&\",\"manufacturerdata\",\"=\",36,\"index\",0,\"00000000\"],\"properties\":{\"tempc\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"tempc2\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",24,4,true,false],\"post_proc\":[\"/\",10]},\"tempc3\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",28,4,true,false],\"post_proc\":[\"/\",10]},\"tempc4\":{\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",32,4,true,false],\"post_proc\":[\"/\",10]}}}";

const char* _IBT_4XS_json = "{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-4XS\",\"condition\":[\"name\",\"index\",0,\"iBBQ\",\"&\",\"manufacturerdata\",\"=\",36,\"index\",0,\"00000000\"],\"properties\":{\"tempc\":{\"condition\":[\"manufacturerdata\",20,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",20,4,true,false],\"post_proc\":[\"/\",10]},\"tempc2\":{\"condition\":[\"manufacturerdata\",24,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",24,4,true,false],\"post_proc\":[\"/\",10]},\"tempc3\":{\"condition\":[\"manufacturerdata\",28,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",28,4,true,false],\"post_proc\":[\"/\",10]},\"tempc4\":{\"condition\":[\"manufacturerdata\",32,\"!\",\"f6ff\"],\"decoder\":[\"value_from_hex_data\",\"manufacturerdata\",32,4,true,false],\"post_proc\":[\"/\",10]}}}";
/*R""""(
{
"brand":"Inkbird",
Expand Down
12 changes: 12 additions & 0 deletions tests/BLE/test_ble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ const char* expected_mfg[] = {
"{\"brand\":\"Govee\",\"model\":\"Thermo Hygrometer\",\"model_id\":\"H5072\",\"tempc\":27.5,\"tempf\":81.5,\"hum\":53.1,\"batt\":100}",
"{\"brand\":\"Govee\",\"model\":\"Smart Thermo Hygrometer\",\"model_id\":\"H5102\",\"tempc\":21.9,\"tempf\":71.42,\"hum\":40.6,\"batt\":100}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-4XS\",\"tempc\":26,\"tempf\":78.8,\"tempc2\":26,\"tempf2\":78.8,\"tempc3\":25,\"tempf3\":77,\"tempc4\":25,\"tempf4\":77}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-4XS\",\"tempc\":26,\"tempf\":78.8,\"tempc3\":60,\"tempf3\":140,\"tempc4\":53,\"tempf4\":127.4}",
"{\"brand\":\"Inkbird\",\"model\":\"T Sensor\",\"model_id\":\"IBS-TH2\",\"tempc\":26.62,\"tempf\":79.916,\"batt\":89}",
"{\"brand\":\"Inkbird\",\"model\":\"T Sensor\",\"model_id\":\"IBS-TH2\",\"tempc\":-11.62,\"tempf\":11.084,\"batt\":89}",
"{\"brand\":\"iNode\",\"model\":\"Energy Meter\",\"model_id\":\"INEM\",\"power\":2376,\"energy\":21.2928,\"batt\":80}",
"{\"brand\":\"iNode\",\"model\":\"Energy Meter\",\"model_id\":\"INEM\",\"power\":5304,\"energy\":18.8804,\"batt\":80}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"tempc\":23,\"tempf\":73.4,\"tempc2\":23,\"tempf2\":73.4}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"tempc\":28,\"tempf\":82.4,\"tempc2\":32,\"tempf2\":89.6}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"tempc2\":65,\"tempf2\":149}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"tempc\":22,\"tempf\":71.6,\"tempc2\":21,\"tempf2\":69.8}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-2X\",\"tempc2\":58,\"tempf2\":136.4}",
"{\"brand\":\"Inkbird\",\"model\":\"iBBQ\",\"model_id\":\"IBT-6XS\",\"tempc\":21,\"tempf\":69.8,\"tempc2\":20,\"tempf2\":68,\"tempc4\":21,\"tempf4\":69.8}",
"{\"brand\":\"Ruuvi\",\"model\":\"RuuviTag\",\"model_id\":\"RuuviTag_RAWv1\",\"hum\":20.5,\"tempc\":26.3,\"tempf\":79.34,\"pres\":1027.66,\"accx\":-1,\"accy\":-1.726,\"accz\":0.714,\"volt\":2.899}",
"{\"brand\":\"Ruuvi\",\"model\":\"RuuviTag\",\"model_id\":\"RuuviTag_RAWv1\",\"hum\":127.5,\"tempc\":127.99,\"tempf\":262.382,\"pres\":1155.35,\"accx\":32.767,\"accy\":32.767,\"accz\":32.767,\"volt\":65.535}",
Expand Down Expand Up @@ -189,12 +193,16 @@ const char* test_mfgdata[][3] = {
{"H5072", "GVH5072_1234", "88ec0004344b6400"},
{"H5102", "GVH5102_1234", "0100010103590e64"},
{"IBT-4XS", "iBBQ", "0000000010082c40abe604010401fa00fa00"},
{"IBT-4XS", "iBBQ", "0000000010082c40abe60401f6ff58021202"},
{"Inkbird TH2", "tps", "660a03150110805908"},
{"Inkbird TH2", "tps", "76fb03150110805908"},
{"iNode", "test1", "90826300f0cf0000c409a20080"},
{"iNode", "test2", "9082dd0061b80000c409a00080"},
{"IBT-2X", "iBBQ", "0000fc45c30c458ee600e600"},
{"IBT-2X", "iBBQ", "0000fc45c30c458e18014001"},
{"IBT-2X", "iBBQ", "0000fc45c30c458ef6ff8a02"},
{"IBT-2X", "iBBQ", "00000000fc45c30d38a8dc00d200"},
{"IBT-2X", "iBBQ", "00000000fc45c30d38a8f6ff4402"},
{"IBT-6XS", "iBBQ", "00003403de2745cdd200c800f6ffd200f6fff6ff"},
{"RuuviTag RAWv1", "RuuviTag", "990403291A1ECE1EFC18F94202CA0B53"},
{"RuuviTag RAWv1", "RuuviTag maximum values", "990403FF7F63FFFF7FFF7FFF7FFFFFFF"},
Expand All @@ -218,12 +226,16 @@ TheengsDecoder::BLE_ID_NUM test_mfgdata_id_num[]{
TheengsDecoder::BLE_ID_NUM::H5072,
TheengsDecoder::BLE_ID_NUM::H5102,
TheengsDecoder::BLE_ID_NUM::IBT4XS,
TheengsDecoder::BLE_ID_NUM::IBT4XS,
TheengsDecoder::BLE_ID_NUM::IBSTH2,
TheengsDecoder::BLE_ID_NUM::IBSTH2,
TheengsDecoder::BLE_ID_NUM::INODE_EM,
TheengsDecoder::BLE_ID_NUM::INODE_EM,
TheengsDecoder::BLE_ID_NUM::IBT_2X,
TheengsDecoder::BLE_ID_NUM::IBT_2X,
TheengsDecoder::BLE_ID_NUM::IBT_2X,
TheengsDecoder::BLE_ID_NUM::IBT_2X,
TheengsDecoder::BLE_ID_NUM::IBT_2X,
TheengsDecoder::BLE_ID_NUM::IBT6XS,
TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV1,
TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV1,
Expand Down

0 comments on commit 273c8af

Please sign in to comment.