Fix Xiaomi merged packet parsing (#1293)

* Fix Xiaomi merged packet parsing

solves #1500

* renamed variables and updated payload and value checking

* renamed function and parameter

* add function to header

* changed log message
This commit is contained in:
Alexander Leisentritt 2020-10-13 04:06:09 +02:00 committed by Guillermo Ruffino
parent 221ef07c8b
commit 540c62061d
2 changed files with 97 additions and 69 deletions

View file

@ -12,6 +12,75 @@ namespace xiaomi_ble {
static const char *TAG = "xiaomi_ble";
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
// motion detection, 1 byte, 8-bit unsigned integer
if ((value_type == 0x03) && (value_length == 1)) {
result.has_motion = (data[0]) ? true : false;
}
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
else if ((value_type == 0x04) && (value_length == 2)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.temperature = temperature / 10.0f;
}
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
else if ((value_type == 0x06) && (value_length == 2)) {
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.humidity = humidity / 10.0f;
}
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) {
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
result.illuminance = illuminance;
result.is_light = (illuminance == 100) ? true : false;
if (value_type == 0x0F)
result.has_motion = true;
}
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x08) && (value_length == 1)) {
result.moisture = data[0];
}
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
else if ((value_type == 0x09) && (value_length == 2)) {
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.conductivity = conductivity;
}
// battery, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x0A) && (value_length == 1)) {
result.battery_level = data[0];
}
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
else if ((value_type == 0x0D) && (value_length == 4)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
result.temperature = temperature / 10.0f;
result.humidity = humidity / 10.0f;
}
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
else if ((value_type == 0x10) && (value_length == 2)) {
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.formaldehyde = formaldehyde / 100.0f;
}
// on/off state, 1 byte, 8-bit unsigned integer
else if ((value_type == 0x12) && (value_length == 1)) {
result.is_active = (data[0]) ? true : false;
}
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x13) && (value_length == 1)) {
result.tablet = data[0];
}
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
else if ((value_type == 0x17) && (value_length == 4)) {
const uint32_t idle_time =
uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) | (uint32_t(data[2]) << 24);
result.idle_time = idle_time / 60.0f;
result.has_motion = (idle_time) ? false : true;
} else {
return false;
}
return true;
}
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result) {
result.has_encryption = (message[0] & 0x08) ? true : false; // update encryption status
if (result.has_encryption) {
@ -25,81 +94,39 @@ bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult
// Byte 2: length
// Byte 3..3+len-1: data point value
const uint8_t *raw = message.data() + result.raw_offset;
const uint8_t *data = raw + 3;
const uint8_t data_length = raw[2];
const uint8_t *payload = message.data() + result.raw_offset;
uint8_t payload_length = message.size() - result.raw_offset;
uint8_t payload_offset = 0;
bool success = false;
if ((data_length < 1) || (data_length > 4)) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): payload has wrong size (%d)!", data_length);
if (payload_length < 4) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): payload has wrong size (%d)!", payload_length);
return false;
}
// motion detection, 1 byte, 8-bit unsigned integer
if ((raw[0] == 0x03) && (data_length == 1)) {
result.has_motion = (data[0]) ? true : false;
}
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
else if ((raw[0] == 0x04) && (data_length == 2)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.temperature = temperature / 10.0f;
}
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
else if ((raw[0] == 0x06) && (data_length == 2)) {
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.humidity = humidity / 10.0f;
}
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
else if (((raw[0] == 0x07) || (raw[0] == 0x0F)) && (data_length == 3)) {
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
result.illuminance = illuminance;
result.is_light = (illuminance == 100) ? true : false;
if (raw[0] == 0x0F)
result.has_motion = true;
}
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x08) && (data_length == 1)) {
result.moisture = data[0];
}
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
else if ((raw[0] == 0x09) && (data_length == 2)) {
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.conductivity = conductivity;
}
// battery, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x0A) && (data_length == 1)) {
result.battery_level = data[0];
}
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
else if ((raw[0] == 0x0D) && (data_length == 4)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
result.temperature = temperature / 10.0f;
result.humidity = humidity / 10.0f;
}
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
else if ((raw[0] == 0x10) && (data_length == 2)) {
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.formaldehyde = formaldehyde / 100.0f;
}
// on/off state, 1 byte, 8-bit unsigned integer
else if ((raw[0] == 0x12) && (data_length == 1)) {
result.is_active = (data[0]) ? true : false;
}
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x13) && (data_length == 1)) {
result.tablet = data[0];
}
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
else if ((raw[0] == 0x17) && (data_length == 4)) {
const uint32_t idle_time =
uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) | (uint32_t(data[2]) << 24);
result.idle_time = idle_time / 60.0f;
result.has_motion = (idle_time) ? false : true;
} else {
return false;
while (payload_length > 0) {
if (payload[payload_offset + 1] != 0x10) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): fixed byte not found, stop parsing residual data.");
break;
}
const uint8_t value_length = payload[payload_offset + 2];
if ((value_length < 1) || (value_length > 4) || (payload_length < (3 + value_length))) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): value has wrong size (%d)!", value_length);
break;
}
const uint8_t value_type = payload[payload_offset + 0];
const uint8_t *data = &payload[payload_offset + 3];
if (parse_xiaomi_value(value_type, data, value_length, result))
success = true;
payload_length -= 3 + value_length;
payload_offset += 3 + value_length;
}
return true;
return success;
}
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data) {

View file

@ -57,6 +57,7 @@ struct XiaomiAESVector {
size_t ivsize;
};
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result);
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data);
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address);