From 540c62061d51ccd55d46810e739a00bafa4b4789 Mon Sep 17 00:00:00 2001 From: Alexander Leisentritt Date: Tue, 13 Oct 2020 04:06:09 +0200 Subject: [PATCH] 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 --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 165 +++++++++++-------- esphome/components/xiaomi_ble/xiaomi_ble.h | 1 + 2 files changed, 97 insertions(+), 69 deletions(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 2ca27bf51a..202620c06d 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -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 &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 &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 parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data) { diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index daa71787a5..ad73226159 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -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 &message, XiaomiParseResult &result); optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address);