mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							c680b437f5
						
					
				
				
					commit
					0c87a9ad2c
				
			| @@ -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) { | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user