8 #include "mbedtls/ccm.h" 11 namespace xiaomi_ble {
13 static const char *
const TAG =
"xiaomi_ble";
17 if ((value_type == 0x1001) && (value_length == 3)) {
22 else if ((value_type == 0x0003) && (value_length == 1)) {
26 else if ((value_type == 0x1004) && (value_length == 2)) {
31 else if ((value_type == 0x1006) && (value_length == 2)) {
36 else if (((value_type == 0x1007) || (value_type == 0x000F)) && (value_length == 3)) {
37 const uint32_t illuminance =
encode_uint24(data[2], data[1], data[0]);
39 result.
is_light = illuminance >= 100;
40 if (value_type == 0x0F)
44 else if ((value_type == 0x1008) && (value_length == 1)) {
48 else if ((value_type == 0x1009) && (value_length == 2)) {
49 const uint16_t conductivity =
encode_uint16(data[1], data[0]);
53 else if ((value_type == 0x100A || value_type == 0x4803) && (value_length == 1)) {
57 else if ((value_type == 0x100D) && (value_length == 4)) {
64 else if ((value_type == 0x1010) && (value_length == 2)) {
65 const uint16_t formaldehyde =
encode_uint16(data[1], data[0]);
69 else if ((value_type == 0x1012) && (value_length == 1)) {
73 else if ((value_type == 0x1013) && (value_length == 1)) {
77 else if ((value_type == 0x1017) && (value_length == 4)) {
78 const uint32_t idle_time =
encode_uint32(data[3], data[2], data[1], data[0]);
81 }
else if ((value_type == 0x1018) && (value_length == 1)) {
85 else if ((value_type == 0x4C01) && (value_length == 4)) {
86 const uint32_t int_number =
encode_uint32(data[3], data[2], data[1], data[0]);
88 std::memcpy(&temperature, &int_number,
sizeof(temperature));
92 else if ((value_type == 0x4C02) && (value_length == 1)) {
104 ESP_LOGVV(TAG,
"parse_xiaomi_message(): payload is encrypted, stop reading message.");
114 const uint8_t *payload = message.data() + result.
raw_offset;
115 uint8_t payload_length = message.size() - result.
raw_offset;
116 uint8_t payload_offset = 0;
117 bool success =
false;
119 if (payload_length < 4) {
120 ESP_LOGVV(TAG,
"parse_xiaomi_message(): payload has wrong size (%d)!", payload_length);
124 while (payload_length > 3) {
125 if (payload[payload_offset + 1] != 0x10 && payload[payload_offset + 1] != 0x00 &&
126 payload[payload_offset + 1] != 0x4C && payload[payload_offset + 1] != 0x48) {
127 ESP_LOGVV(TAG,
"parse_xiaomi_message(): fixed byte not found, stop parsing residual data.");
131 const uint8_t value_length = payload[payload_offset + 2];
132 if ((value_length < 1) || (value_length > 4) || (payload_length < (3 + value_length))) {
133 ESP_LOGVV(TAG,
"parse_xiaomi_message(): value has wrong size (%d)!", value_length);
137 const uint16_t value_type =
encode_uint16(payload[payload_offset + 1], payload[payload_offset + 0]);
138 const uint8_t *data = &payload[payload_offset + 3];
143 payload_length -= 3 + value_length;
144 payload_offset += 3 + value_length;
153 ESP_LOGVV(TAG,
"parse_xiaomi_header(): no service data UUID magic bytes.");
163 ESP_LOGVV(TAG,
"parse_xiaomi_header(): service data has no DATA flag.");
167 static uint8_t last_frame_count = 0;
168 if (last_frame_count ==
raw[4]) {
169 ESP_LOGVV(TAG,
"parse_xiaomi_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
173 last_frame_count =
raw[4];
179 if (device_uuid == 0x0098) {
181 result.
name =
"HHCCJCY01";
182 }
else if (device_uuid == 0x01aa) {
184 result.
name =
"LYWSDCGQ";
185 }
else if (device_uuid == 0x015d) {
187 result.
name =
"HHCCPOT002";
188 }
else if (device_uuid == 0x02df) {
190 result.
name =
"JQJCY01YM";
191 }
else if (device_uuid == 0x03dd) {
193 result.
name =
"MUE4094RT";
195 }
else if (device_uuid == 0x0347 ||
196 device_uuid == 0x0B48) {
198 result.
name =
"CGG1";
199 }
else if (device_uuid == 0x03bc) {
201 result.
name =
"GCLS002";
202 }
else if (device_uuid == 0x045b) {
204 result.
name =
"LYWSD02";
205 }
else if (device_uuid == 0x2542) {
207 result.
name =
"LYWSD02MMC";
208 if (
raw.size() == 19)
210 }
else if (device_uuid == 0x040a) {
212 result.
name =
"WX08ZM";
213 }
else if (device_uuid == 0x0576) {
215 result.
name =
"CGD1";
216 }
else if (device_uuid == 0x066F) {
218 result.
name =
"CGDK2";
219 }
else if (device_uuid == 0x055b) {
221 result.
name =
"LYWSD03MMC";
222 }
else if (device_uuid == 0x07f6) {
224 result.
name =
"MJYD02YLA";
225 if (
raw.size() == 19)
227 }
else if (device_uuid == 0x06d3) {
229 result.
name =
"MHOC303";
230 }
else if (device_uuid == 0x0387) {
232 result.
name =
"MHOC401";
233 }
else if (device_uuid == 0x0A83) {
235 result.
name =
"CGPR1";
236 if (
raw.size() == 19)
238 }
else if (device_uuid == 0x0A8D) {
240 result.
name =
"RTCGQ02LM";
241 if (
raw.size() == 19)
244 ESP_LOGVV(TAG,
"parse_xiaomi_header(): unknown device, no magic bytes.");
252 if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) {
253 ESP_LOGVV(TAG,
"decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size());
254 ESP_LOGVV(TAG,
" Packet : %s",
format_hex_pretty(raw.data(), raw.size()).c_str());
258 uint8_t mac_reverse[6] = {0};
259 mac_reverse[5] = (uint8_t) (address >> 40);
260 mac_reverse[4] = (uint8_t) (address >> 32);
261 mac_reverse[3] = (uint8_t) (address >> 24);
262 mac_reverse[2] = (uint8_t) (address >> 16);
263 mac_reverse[1] = (uint8_t) (address >> 8);
264 mac_reverse[0] = (uint8_t) (address >> 0);
278 vector.datasize = (raw.size() == 19) ? raw.size() - 12 : raw.size() - 18;
279 int cipher_pos = (raw.size() == 19) ? 5 : 11;
281 const uint8_t *v = raw.data();
283 memcpy(vector.key, bindkey, vector.keysize);
284 memcpy(vector.ciphertext, v + cipher_pos, vector.datasize);
285 memcpy(vector.tag, v + raw.size() - vector.tagsize, vector.tagsize);
286 memcpy(vector.iv, mac_reverse, 6);
287 memcpy(vector.iv + 6, v + 2, 3);
288 memcpy(vector.iv + 9, v + raw.size() - 7, 3);
290 mbedtls_ccm_context ctx;
291 mbedtls_ccm_init(&ctx);
293 int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, vector.key, vector.keysize * 8);
295 ESP_LOGVV(TAG,
"decrypt_xiaomi_payload(): mbedtls_ccm_setkey() failed.");
296 mbedtls_ccm_free(&ctx);
300 ret = mbedtls_ccm_auth_decrypt(&ctx, vector.datasize, vector.iv, vector.ivsize, vector.authdata, vector.authsize,
301 vector.ciphertext, vector.plaintext, vector.tag, vector.tagsize);
303 uint8_t mac_address[6] = {0};
304 memcpy(mac_address, mac_reverse + 5, 1);
305 memcpy(mac_address + 1, mac_reverse + 4, 1);
306 memcpy(mac_address + 2, mac_reverse + 3, 1);
307 memcpy(mac_address + 3, mac_reverse + 2, 1);
308 memcpy(mac_address + 4, mac_reverse + 1, 1);
309 memcpy(mac_address + 5, mac_reverse, 1);
310 ESP_LOGVV(TAG,
"decrypt_xiaomi_payload(): authenticated decryption failed.");
312 ESP_LOGVV(TAG,
" Packet : %s",
format_hex_pretty(raw.data(), raw.size()).c_str());
313 ESP_LOGVV(TAG,
" Key : %s",
format_hex_pretty(vector.key, vector.keysize).c_str());
315 ESP_LOGVV(TAG,
" Cipher : %s",
format_hex_pretty(vector.ciphertext, vector.datasize).c_str());
316 ESP_LOGVV(TAG,
" Tag : %s",
format_hex_pretty(vector.tag, vector.tagsize).c_str());
317 mbedtls_ccm_free(&ctx);
322 uint8_t *p = vector.plaintext;
323 for (std::vector<uint8_t>::iterator it = raw.begin() + cipher_pos; it != raw.begin() + cipher_pos + vector.datasize;
331 ESP_LOGVV(TAG,
"decrypt_xiaomi_payload(): authenticated decryption passed.");
332 ESP_LOGVV(TAG,
" Plaintext : %s, Packet : %d",
format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(),
333 static_cast<int>(raw[4]));
335 mbedtls_ccm_free(&ctx);
341 ESP_LOGVV(TAG,
"report_xiaomi_results(): no results available.");
345 ESP_LOGD(TAG,
"Got Xiaomi %s (%s):", result->name.c_str(), address.c_str());
348 ESP_LOGD(TAG,
" Temperature: %.1f°C", *result->temperature);
351 ESP_LOGD(TAG,
" Humidity: %.1f%%", *result->humidity);
354 ESP_LOGD(TAG,
" Battery Level: %.0f%%", *result->battery_level);
357 ESP_LOGD(TAG,
" Conductivity: %.0fµS/cm", *result->conductivity);
360 ESP_LOGD(TAG,
" Illuminance: %.0flx", *result->illuminance);
363 ESP_LOGD(TAG,
" Moisture: %.0f%%", *result->moisture);
366 ESP_LOGD(TAG,
" Mosquito tablet: %.0f%%", *result->tablet);
369 ESP_LOGD(TAG,
" Repellent: %s", (*result->is_active) ?
"on" :
"off");
372 ESP_LOGD(TAG,
" Motion: %s", (*result->has_motion) ?
"yes" :
"no");
375 ESP_LOGD(TAG,
" Light: %s", (*result->is_light) ?
"on" :
"off");
378 ESP_LOGD(TAG,
" Button: %s", (*result->button_press) ?
"pressed" :
"");
optional< float > illuminance
optional< float > temperature
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result)
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
optional< float > formaldehyde
bool decrypt_xiaomi_payload(std::vector< uint8_t > &raw, const uint8_t *bindkey, const uint64_t &address)
optional< bool > button_press
optional< float > humidity
optional< bool > is_active
constexpr uint32_t encode_uint24(uint8_t byte1, uint8_t byte2, uint8_t byte3)
Encode a 24-bit value given three bytes in most to least significant byte order.
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
optional< float > battery_level
bool parse_xiaomi_message(const std::vector< uint8_t > &message, XiaomiParseResult &result)
optional< XiaomiParseResult > parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data)
optional< float > idle_time
bool report_xiaomi_results(const optional< XiaomiParseResult > &result, const std::string &address)
bool contains(uint8_t data1, uint8_t data2) const
optional< bool > is_light
optional< bool > has_motion
Implementation of SPI Controller mode.
enum esphome::xiaomi_ble::XiaomiParseResult::@142 type
optional< float > moisture
optional< float > conductivity