mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	| @@ -1,187 +1,322 @@ | ||||
| #include "xiaomi_ble.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| #include <vector> | ||||
| #include "mbedtls/ccm.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_ble { | ||||
|  | ||||
| static const char *TAG = "xiaomi_ble"; | ||||
|  | ||||
| bool parse_xiaomi_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data_length, XiaomiParseResult &result) { | ||||
|   switch (data_type) { | ||||
|     case 0x0D: {  // temperature+humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 % | ||||
|       if (data_length != 4) | ||||
|         return false; | ||||
|       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; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x0A: {  // battery, 1 byte, 8-bit unsigned integer, 1 % | ||||
|       if (data_length != 1) | ||||
|         return false; | ||||
|       result.battery_level = data[0]; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x06: {  // humidity, 2 bytes, 16-bit signed integer (LE), 0.1 % | ||||
|       if (data_length != 2) | ||||
|         return false; | ||||
|       const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8); | ||||
|       result.humidity = humidity / 10.0f; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x04: {  // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C | ||||
|       if (data_length != 2) | ||||
|         return false; | ||||
|       const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8); | ||||
|       result.temperature = temperature / 10.0f; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x09: {  // conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm | ||||
|       if (data_length != 2) | ||||
|         return false; | ||||
|       const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8); | ||||
|       result.conductivity = conductivity; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x07: {  // illuminance, 3 bytes, 24-bit unsigned integer (LE), 1 lx | ||||
|       if (data_length != 3) | ||||
|         return false; | ||||
|       const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16); | ||||
|       result.illuminance = illuminance; | ||||
|       return true; | ||||
|     } | ||||
|     case 0x08: {  // soil moisture, 1 byte, 8-bit unsigned integer, 1 % | ||||
|       if (data_length != 1) | ||||
|         return false; | ||||
|       result.moisture = data[0]; | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool parse_xiaomi_service_data(XiaomiParseResult &result, const esp32_ble_tracker::ServiceData &service_data) { | ||||
|   if (!service_data.uuid.contains(0x95, 0xFE)) { | ||||
|     // ESP_LOGVV(TAG, "Xiaomi no service data UUID magic bytes"); | ||||
| 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) { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_message(): payload is encrypted, stop reading message."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   const auto raw = service_data.data; | ||||
|  | ||||
|   if (raw.size() < 14) { | ||||
|     // ESP_LOGVV(TAG, "Xiaomi service data too short!"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   bool is_lywsdcgq = (raw[1] & 0x20) == 0x20 && raw[2] == 0xAA && raw[3] == 0x01; | ||||
|   bool is_hhccjcy01 = (raw[1] & 0x20) == 0x20 && raw[2] == 0x98 && raw[3] == 0x00; | ||||
|   bool is_lywsd02 = (raw[1] & 0x20) == 0x20 && raw[2] == 0x5b && raw[3] == 0x04; | ||||
|   bool is_cgg1 = ((raw[1] & 0x30) == 0x30 || (raw[1] & 0x20) == 0x20) && raw[2] == 0x47 && raw[3] == 0x03; | ||||
|  | ||||
|   if (!is_lywsdcgq && !is_hhccjcy01 && !is_lywsd02 && !is_cgg1) { | ||||
|     // ESP_LOGVV(TAG, "Xiaomi no magic bytes"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   result.type = XiaomiParseResult::TYPE_HHCCJCY01; | ||||
|   if (is_lywsdcgq) { | ||||
|     result.type = XiaomiParseResult::TYPE_LYWSDCGQ; | ||||
|   } else if (is_lywsd02) { | ||||
|     result.type = XiaomiParseResult::TYPE_LYWSD02; | ||||
|   } else if (is_cgg1) { | ||||
|     result.type = XiaomiParseResult::TYPE_CGG1; | ||||
|   } | ||||
|  | ||||
|   uint8_t raw_offset = is_lywsdcgq || is_cgg1 ? 11 : 12; | ||||
|  | ||||
|   // Data point specs | ||||
|   // Byte 0: type | ||||
|   // Byte 1: fixed 0x10 | ||||
|   // Byte 2: length | ||||
|   // Byte 3..3+len-1: data point value | ||||
|  | ||||
|   const uint8_t *raw_data = &raw[raw_offset]; | ||||
|   uint8_t data_offset = 0; | ||||
|   uint8_t data_length = raw.size() - raw_offset; | ||||
|   bool success = false; | ||||
|   const uint8_t *raw = message.data() + result.raw_offset; | ||||
|   const uint8_t *data = raw + 3; | ||||
|   const uint8_t data_length = raw[2]; | ||||
|  | ||||
|   while (true) { | ||||
|     if (data_length < 4) | ||||
|       // at least 4 bytes required | ||||
|       // type, fixed 0x10, length, 1 byte value | ||||
|       break; | ||||
|  | ||||
|     const uint8_t datapoint_type = raw_data[data_offset + 0]; | ||||
|     const uint8_t datapoint_length = raw_data[data_offset + 2]; | ||||
|  | ||||
|     if (data_length < 3 + datapoint_length) | ||||
|       // 3 fixed bytes plus value length | ||||
|       break; | ||||
|  | ||||
|     const uint8_t *datapoint_data = &raw_data[data_offset + 3]; | ||||
|  | ||||
|     if (parse_xiaomi_data_byte(datapoint_type, datapoint_data, datapoint_length, result)) | ||||
|       success = true; | ||||
|  | ||||
|     data_length -= data_offset + 3 + datapoint_length; | ||||
|     data_offset += 3 + datapoint_length; | ||||
|   } | ||||
|  | ||||
|   return success; | ||||
| } | ||||
| optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   XiaomiParseResult result; | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     if (parse_xiaomi_service_data(result, service_data)) | ||||
|       success = true; | ||||
|   } | ||||
|   if (!success) | ||||
|     return {}; | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   auto res = parse_xiaomi(device); | ||||
|   if (!res.has_value()) | ||||
|   if ((data_length < 1) || (data_length > 4)) { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_message(): payload has wrong size (%d)!", data_length); | ||||
|     return false; | ||||
|  | ||||
|   const char *name = "HHCCJCY01"; | ||||
|   if (res->type == XiaomiParseResult::TYPE_LYWSDCGQ) { | ||||
|     name = "LYWSDCGQ"; | ||||
|   } else if (res->type == XiaomiParseResult::TYPE_LYWSD02) { | ||||
|     name = "LYWSD02"; | ||||
|   } else if (res->type == XiaomiParseResult::TYPE_CGG1) { | ||||
|     name = "CGG1"; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got Xiaomi %s (%s):", name, device.address_str().c_str()); | ||||
|  | ||||
|   if (res->temperature.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Temperature: %.1f°C", *res->temperature); | ||||
|   // motion detection, 1 byte, 8-bit unsigned integer | ||||
|   if ((raw[0] == 0x03) && (data_length == 1)) { | ||||
|     result.has_motion = (data[0]) ? true : false; | ||||
|   } | ||||
|   if (res->humidity.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Humidity: %.1f%%", *res->humidity); | ||||
|   // 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; | ||||
|   } | ||||
|   if (res->battery_level.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Battery Level: %.0f%%", *res->battery_level); | ||||
|   // 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; | ||||
|   } | ||||
|   if (res->conductivity.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Conductivity: %.0fµS/cm", *res->conductivity); | ||||
|   // 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; | ||||
|   } | ||||
|   if (res->illuminance.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Illuminance: %.0flx", *res->illuminance); | ||||
|   // soil moisture, 1 byte, 8-bit unsigned integer, 1 % | ||||
|   else if ((raw[0] == 0x08) && (data_length == 1)) { | ||||
|     result.moisture = data[0]; | ||||
|   } | ||||
|   if (res->moisture.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Moisture: %.0f%%", *res->moisture); | ||||
|   // 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; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data) { | ||||
|   XiaomiParseResult result; | ||||
|   if (!service_data.uuid.contains(0x95, 0xFE)) { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_header(): no service data UUID magic bytes."); | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   auto raw = service_data.data; | ||||
|   result.has_data = (raw[0] & 0x40) ? true : false; | ||||
|   result.has_capability = (raw[0] & 0x20) ? true : false; | ||||
|   result.has_encryption = (raw[0] & 0x08) ? true : false; | ||||
|  | ||||
|   if (!result.has_data) { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_header(): service data has no DATA flag."); | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   static uint8_t last_frame_count = 0; | ||||
|   if (last_frame_count == raw[4]) { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count)); | ||||
|     result.is_duplicate = true; | ||||
|     return {}; | ||||
|   } | ||||
|   last_frame_count = raw[4]; | ||||
|   result.is_duplicate = false; | ||||
|   result.raw_offset = result.has_capability ? 12 : 11; | ||||
|  | ||||
|   if ((raw[2] == 0x98) && (raw[3] == 0x00)) {  // MiFlora | ||||
|     result.type = XiaomiParseResult::TYPE_HHCCJCY01; | ||||
|     result.name = "HHCCJCY01"; | ||||
|   } else if ((raw[2] == 0xaa) && (raw[3] == 0x01)) {  // round body, segment LCD | ||||
|     result.type = XiaomiParseResult::TYPE_LYWSDCGQ; | ||||
|     result.name = "LYWSDCGQ"; | ||||
|   } else if ((raw[2] == 0x5d) && (raw[3] == 0x01)) {  // FlowerPot, RoPot | ||||
|     result.type = XiaomiParseResult::TYPE_HHCCPOT002; | ||||
|     result.name = "HHCCPOT002"; | ||||
|   } else if ((raw[2] == 0xdf) && (raw[3] == 0x02)) {  // Xiaomi (Honeywell) formaldehyde sensor, OLED display | ||||
|     result.type = XiaomiParseResult::TYPE_JQJCY01YM; | ||||
|     result.name = "JQJCY01YM"; | ||||
|   } else if ((raw[2] == 0xdd) && (raw[3] == 0x03)) {  // Philips/Xiaomi BLE nightlight | ||||
|     result.type = XiaomiParseResult::TYPE_MUE4094RT; | ||||
|     result.name = "MUE4094RT"; | ||||
|     result.raw_offset -= 6; | ||||
|   } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) {  // round body, e-ink display | ||||
|     result.type = XiaomiParseResult::TYPE_CGG1; | ||||
|     result.name = "CGG1"; | ||||
|   } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) {  // VegTrug Grow Care Garden | ||||
|     result.type = XiaomiParseResult::TYPE_GCLS002; | ||||
|     result.name = "GCLS002"; | ||||
|   } else if ((raw[2] == 0x5b) && (raw[3] == 0x04)) {  // rectangular body, e-ink display | ||||
|     result.type = XiaomiParseResult::TYPE_LYWSD02; | ||||
|     result.name = "LYWSD02"; | ||||
|   } else if ((raw[2] == 0x0a) && (raw[3] == 0x04)) {  // Mosquito Repellent Smart Version | ||||
|     result.type = XiaomiParseResult::TYPE_WX08ZM; | ||||
|     result.name = "WX08ZM"; | ||||
|   } else if ((raw[2] == 0x76) && (raw[3] == 0x05)) {  // Cleargrass (Qingping) alarm clock, segment LCD | ||||
|     result.type = XiaomiParseResult::TYPE_CGD1; | ||||
|     result.name = "CGD1"; | ||||
|   } else if ((raw[2] == 0x5b) && (raw[3] == 0x05)) {  // small square body, segment LCD, encrypted | ||||
|     result.type = XiaomiParseResult::TYPE_LYWSD03MMC; | ||||
|     result.name = "LYWSD03MMC"; | ||||
|   } else if ((raw[2] == 0xf6) && (raw[3] == 0x07)) {  // Xiaomi-Yeelight BLE nightlight | ||||
|     result.type = XiaomiParseResult::TYPE_MJYD02YLA; | ||||
|     result.name = "MJYD02YLA"; | ||||
|     if (raw.size() == 19) | ||||
|       result.raw_offset -= 6; | ||||
|   } else { | ||||
|     ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes."); | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) { | ||||
|   if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { | ||||
|     ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); | ||||
|     ESP_LOGVV(TAG, "  Packet : %s", hexencode(raw.data(), raw.size()).c_str()); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   uint8_t mac_reverse[6] = {0}; | ||||
|   mac_reverse[5] = (uint8_t)(address >> 40); | ||||
|   mac_reverse[4] = (uint8_t)(address >> 32); | ||||
|   mac_reverse[3] = (uint8_t)(address >> 24); | ||||
|   mac_reverse[2] = (uint8_t)(address >> 16); | ||||
|   mac_reverse[1] = (uint8_t)(address >> 8); | ||||
|   mac_reverse[0] = (uint8_t)(address >> 0); | ||||
|  | ||||
|   XiaomiAESVector vector{.key = {0}, | ||||
|                          .plaintext = {0}, | ||||
|                          .ciphertext = {0}, | ||||
|                          .authdata = {0x11}, | ||||
|                          .iv = {0}, | ||||
|                          .tag = {0}, | ||||
|                          .keysize = 16, | ||||
|                          .authsize = 1, | ||||
|                          .datasize = 0, | ||||
|                          .tagsize = 4, | ||||
|                          .ivsize = 12}; | ||||
|  | ||||
|   vector.datasize = (raw.size() == 19) ? raw.size() - 12 : raw.size() - 18; | ||||
|   int cipher_pos = (raw.size() == 19) ? 5 : 11; | ||||
|  | ||||
|   const uint8_t *v = raw.data(); | ||||
|  | ||||
|   memcpy(vector.key, bindkey, vector.keysize); | ||||
|   memcpy(vector.ciphertext, v + cipher_pos, vector.datasize); | ||||
|   memcpy(vector.tag, v + raw.size() - vector.tagsize, vector.tagsize); | ||||
|   memcpy(vector.iv, mac_reverse, 6);             // MAC address reverse | ||||
|   memcpy(vector.iv + 6, v + 2, 3);               // sensor type (2) + packet id (1) | ||||
|   memcpy(vector.iv + 9, v + raw.size() - 7, 3);  // payload counter | ||||
|  | ||||
|   mbedtls_ccm_context ctx; | ||||
|   mbedtls_ccm_init(&ctx); | ||||
|  | ||||
|   int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, vector.key, vector.keysize * 8); | ||||
|   if (ret) { | ||||
|     ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): mbedtls_ccm_setkey() failed."); | ||||
|     mbedtls_ccm_free(&ctx); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   ret = mbedtls_ccm_auth_decrypt(&ctx, vector.datasize, vector.iv, vector.ivsize, vector.authdata, vector.authsize, | ||||
|                                  vector.ciphertext, vector.plaintext, vector.tag, vector.tagsize); | ||||
|   if (ret) { | ||||
|     uint8_t mac_address[6] = {0}; | ||||
|     memcpy(mac_address, mac_reverse + 5, 1); | ||||
|     memcpy(mac_address + 1, mac_reverse + 4, 1); | ||||
|     memcpy(mac_address + 2, mac_reverse + 3, 1); | ||||
|     memcpy(mac_address + 3, mac_reverse + 2, 1); | ||||
|     memcpy(mac_address + 4, mac_reverse + 1, 1); | ||||
|     memcpy(mac_address + 5, mac_reverse, 1); | ||||
|     ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); | ||||
|     ESP_LOGVV(TAG, "  MAC address : %s", hexencode(mac_address, 6).c_str()); | ||||
|     ESP_LOGVV(TAG, "       Packet : %s", hexencode(raw.data(), raw.size()).c_str()); | ||||
|     ESP_LOGVV(TAG, "          Key : %s", hexencode(vector.key, vector.keysize).c_str()); | ||||
|     ESP_LOGVV(TAG, "           Iv : %s", hexencode(vector.iv, vector.ivsize).c_str()); | ||||
|     ESP_LOGVV(TAG, "       Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str()); | ||||
|     ESP_LOGVV(TAG, "          Tag : %s", hexencode(vector.tag, vector.tagsize).c_str()); | ||||
|     mbedtls_ccm_free(&ctx); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // replace encrypted payload with plaintext | ||||
|   uint8_t *p = vector.plaintext; | ||||
|   for (std::vector<uint8_t>::iterator it = raw.begin() + cipher_pos; it != raw.begin() + cipher_pos + vector.datasize; | ||||
|        ++it) { | ||||
|     *it = *(p++); | ||||
|   } | ||||
|  | ||||
|   // clear encrypted flag | ||||
|   raw[0] &= ~0x08; | ||||
|  | ||||
|   ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); | ||||
|   ESP_LOGVV(TAG, "  Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(), | ||||
|             static_cast<int>(raw[4])); | ||||
|  | ||||
|   mbedtls_ccm_free(&ctx); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool report_xiaomi_results(const optional<XiaomiParseResult> &result, const std::string &address) { | ||||
|   if (!result.has_value()) { | ||||
|     ESP_LOGVV(TAG, "report_xiaomi_results(): no results available."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address.c_str()); | ||||
|  | ||||
|   if (result->temperature.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Temperature: %.1f°C", *result->temperature); | ||||
|   } | ||||
|   if (result->humidity.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Humidity: %.1f%%", *result->humidity); | ||||
|   } | ||||
|   if (result->battery_level.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Battery Level: %.0f%%", *result->battery_level); | ||||
|   } | ||||
|   if (result->conductivity.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Conductivity: %.0fµS/cm", *result->conductivity); | ||||
|   } | ||||
|   if (result->illuminance.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Illuminance: %.0flx", *result->illuminance); | ||||
|   } | ||||
|   if (result->moisture.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Moisture: %.0f%%", *result->moisture); | ||||
|   } | ||||
|   if (result->tablet.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Mosquito tablet: %.0f%%", *result->tablet); | ||||
|   } | ||||
|   if (result->is_active.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Repellent: %s", (*result->is_active) ? "on" : "off"); | ||||
|   } | ||||
|   if (result->has_motion.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Motion: %s", (*result->has_motion) ? "yes" : "no"); | ||||
|   } | ||||
|   if (result->is_light.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Light: %s", (*result->is_light) ? "on" : "off"); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   // Previously the message was parsed twice per packet, once by XiaomiListener::parse_device() | ||||
|   // and then again by the respective device class's parse_device() function. Parsing the header | ||||
|   // here and then for each device seems to be unneccessary and complicates the duplicate packet filtering. | ||||
|   // Hence I disabled the call to parse_xiaomi_header() here and the message parsing is done entirely | ||||
|   // in the respecive device instance. The XiaomiListener class is defined in __init__.py and I was not | ||||
|   // able to remove it entirely. | ||||
|  | ||||
|   return false;  // with true it's not showing device scans | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_ble | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -9,18 +9,58 @@ namespace esphome { | ||||
| namespace xiaomi_ble { | ||||
|  | ||||
| struct XiaomiParseResult { | ||||
|   enum { TYPE_LYWSDCGQ, TYPE_HHCCJCY01, TYPE_LYWSD02, TYPE_CGG1 } type; | ||||
|   enum { | ||||
|     TYPE_HHCCJCY01, | ||||
|     TYPE_GCLS002, | ||||
|     TYPE_HHCCPOT002, | ||||
|     TYPE_LYWSDCGQ, | ||||
|     TYPE_LYWSD02, | ||||
|     TYPE_CGG1, | ||||
|     TYPE_LYWSD03MMC, | ||||
|     TYPE_CGD1, | ||||
|     TYPE_JQJCY01YM, | ||||
|     TYPE_MUE4094RT, | ||||
|     TYPE_WX08ZM, | ||||
|     TYPE_MJYD02YLA | ||||
|   } type; | ||||
|   std::string name; | ||||
|   optional<float> temperature; | ||||
|   optional<float> humidity; | ||||
|   optional<float> battery_level; | ||||
|   optional<float> moisture; | ||||
|   optional<float> conductivity; | ||||
|   optional<float> illuminance; | ||||
|   optional<float> moisture; | ||||
|   optional<float> formaldehyde; | ||||
|   optional<float> battery_level; | ||||
|   optional<float> tablet; | ||||
|   optional<float> idle_time; | ||||
|   optional<bool> is_active; | ||||
|   optional<bool> has_motion; | ||||
|   optional<bool> is_light; | ||||
|   bool has_data;        // 0x40 | ||||
|   bool has_capability;  // 0x20 | ||||
|   bool has_encryption;  // 0x08 | ||||
|   bool is_duplicate; | ||||
|   int raw_offset; | ||||
| }; | ||||
|  | ||||
| bool parse_xiaomi_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data_length, XiaomiParseResult &result); | ||||
| struct XiaomiAESVector { | ||||
|   uint8_t key[16]; | ||||
|   uint8_t plaintext[16]; | ||||
|   uint8_t ciphertext[16]; | ||||
|   uint8_t authdata[16]; | ||||
|   uint8_t iv[16]; | ||||
|   uint8_t tag[16]; | ||||
|   size_t keysize; | ||||
|   size_t authsize; | ||||
|   size_t datasize; | ||||
|   size_t tagsize; | ||||
|   size_t ivsize; | ||||
| }; | ||||
|  | ||||
| optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device); | ||||
| 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); | ||||
| bool report_xiaomi_results(const optional<XiaomiParseResult> &result, const std::string &address); | ||||
|  | ||||
| class XiaomiListener : public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_cgd1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_cgd1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										43
									
								
								esphome/components/xiaomi_cgd1/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/xiaomi_cgd1/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ | ||||
|     CONF_BINDKEY | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_cgd1_ns = cg.esphome_ns.namespace('xiaomi_cgd1') | ||||
| XiaomiCGD1 = xiaomi_cgd1_ns.class_('XiaomiCGD1', esp32_ble_tracker.ESPBTDeviceListener, | ||||
|                                    cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiCGD1), | ||||
|     cv.Required(CONF_BINDKEY): cv.bind_key, | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|     cg.add(var.set_bindkey(config[CONF_BINDKEY])) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
|  | ||||
|     cg.add_library("mbedtls", "cdf462088d") | ||||
							
								
								
									
										77
									
								
								esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #include "xiaomi_cgd1.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_cgd1 { | ||||
|  | ||||
| static const char *TAG = "xiaomi_cgd1"; | ||||
|  | ||||
| void XiaomiCGD1::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi CGD1"); | ||||
|   ESP_LOGCONFIG(TAG, "  Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption && | ||||
|         (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast<std::vector<uint8_t> &>(service_data.data), this->bindkey_, | ||||
|                                               this->address_)))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void XiaomiCGD1::set_bindkey(const std::string &bindkey) { | ||||
|   memset(bindkey_, 0, 16); | ||||
|   if (bindkey.size() != 32) { | ||||
|     return; | ||||
|   } | ||||
|   char temp[3] = {0}; | ||||
|   for (int i = 0; i < 16; i++) { | ||||
|     strncpy(temp, &(bindkey.c_str()[i * 2]), 2); | ||||
|     bindkey_[i] = std::strtoul(temp, NULL, 16); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_cgd1 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										36
									
								
								esphome/components/xiaomi_cgd1/xiaomi_cgd1.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/xiaomi_cgd1/xiaomi_cgd1.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_cgd1 { | ||||
|  | ||||
| class XiaomiCGD1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; }; | ||||
|   void set_bindkey(const std::string &bindkey); | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   uint8_t bindkey_[16]; | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_cgd1 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -15,6 +15,48 @@ void XiaomiCGG1::dump_config() { | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_cgg1 | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -14,22 +14,7 @@ class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListen | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||
|     if (device.address_uint64() != this->address_) | ||||
|       return false; | ||||
|  | ||||
|     auto res = xiaomi_ble::parse_xiaomi(device); | ||||
|     if (!res.has_value()) | ||||
|       return false; | ||||
|  | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     return true; | ||||
|   } | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_gcls002/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_gcls002/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										45
									
								
								esphome/components/xiaomi_gcls002/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/xiaomi_gcls002/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ | ||||
|     CONF_MOISTURE, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_LUX, CONF_CONDUCTIVITY, \ | ||||
|     UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_gcls002_ns = cg.esphome_ns.namespace('xiaomi_gcls002') | ||||
| XiaomiGCLS002 = xiaomi_gcls002_ns.class_('XiaomiGCLS002', | ||||
|                                          esp32_ble_tracker.ESPBTDeviceListener, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiGCLS002), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), | ||||
|     cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), | ||||
|     cv.Optional(CONF_CONDUCTIVITY): | ||||
|         sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_MOISTURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_MOISTURE]) | ||||
|         cg.add(var.set_moisture(sens)) | ||||
|     if CONF_ILLUMINANCE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) | ||||
|         cg.add(var.set_illuminance(sens)) | ||||
|     if CONF_CONDUCTIVITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_CONDUCTIVITY]) | ||||
|         cg.add(var.set_conductivity(sens)) | ||||
							
								
								
									
										66
									
								
								esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #include "xiaomi_gcls002.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_gcls002 { | ||||
|  | ||||
| static const char *TAG = "xiaomi_gcls002"; | ||||
|  | ||||
| void XiaomiGCLS002::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi GCLS002"); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||
|   LOG_SENSOR("  ", "Moisture", this->moisture_); | ||||
|   LOG_SENSOR("  ", "Conductivity", this->conductivity_); | ||||
|   LOG_SENSOR("  ", "Illuminance", this->illuminance_); | ||||
| } | ||||
|  | ||||
| bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->moisture.has_value() && this->moisture_ != nullptr) | ||||
|       this->moisture_->publish_state(*res->moisture); | ||||
|     if (res->conductivity.has_value() && this->conductivity_ != nullptr) | ||||
|       this->conductivity_->publish_state(*res->conductivity); | ||||
|     if (res->illuminance.has_value() && this->illuminance_ != nullptr) | ||||
|       this->illuminance_->publish_state(*res->illuminance); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_gcls002 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										37
									
								
								esphome/components/xiaomi_gcls002/xiaomi_gcls002.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/xiaomi_gcls002/xiaomi_gcls002.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_gcls002 { | ||||
|  | ||||
| class XiaomiGCLS002 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_moisture(sensor::Sensor *moisture) { moisture_ = moisture; } | ||||
|   void set_conductivity(sensor::Sensor *conductivity) { conductivity_ = conductivity; } | ||||
|   void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
|   sensor::Sensor *moisture_{nullptr}; | ||||
|   sensor::Sensor *conductivity_{nullptr}; | ||||
|   sensor::Sensor *illuminance_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_gcls002 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -1,8 +1,8 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ | ||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ | ||||
|     CONF_MOISTURE, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_LUX, CONF_CONDUCTIVITY, \ | ||||
|     UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER | ||||
|  | ||||
| @@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), | ||||
|     cv.Optional(CONF_CONDUCTIVITY): | ||||
|         sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @@ -44,6 +43,3 @@ def to_code(config): | ||||
|     if CONF_CONDUCTIVITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_CONDUCTIVITY]) | ||||
|         cg.add(var.set_conductivity(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
|   | ||||
| @@ -14,7 +14,50 @@ void XiaomiHHCCJCY01::dump_config() { | ||||
|   LOG_SENSOR("  ", "Moisture", this->moisture_); | ||||
|   LOG_SENSOR("  ", "Conductivity", this->conductivity_); | ||||
|   LOG_SENSOR("  ", "Illuminance", this->illuminance_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->moisture.has_value() && this->moisture_ != nullptr) | ||||
|       this->moisture_->publish_state(*res->moisture); | ||||
|     if (res->conductivity.has_value() && this->conductivity_ != nullptr) | ||||
|       this->conductivity_->publish_state(*res->conductivity); | ||||
|     if (res->illuminance.has_value() && this->illuminance_ != nullptr) | ||||
|       this->illuminance_->publish_state(*res->illuminance); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_hhccjcy01 | ||||
|   | ||||
| @@ -14,26 +14,7 @@ class XiaomiHHCCJCY01 : public Component, public esp32_ble_tracker::ESPBTDeviceL | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||
|     if (device.address_uint64() != this->address_) | ||||
|       return false; | ||||
|  | ||||
|     auto res = xiaomi_ble::parse_xiaomi(device); | ||||
|     if (!res.has_value()) | ||||
|       return false; | ||||
|  | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->moisture.has_value() && this->moisture_ != nullptr) | ||||
|       this->moisture_->publish_state(*res->moisture); | ||||
|     if (res->conductivity.has_value() && this->conductivity_ != nullptr) | ||||
|       this->conductivity_->publish_state(*res->conductivity); | ||||
|     if (res->illuminance.has_value() && this->illuminance_ != nullptr) | ||||
|       this->illuminance_->publish_state(*res->illuminance); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     return true; | ||||
|   } | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
| @@ -41,7 +22,6 @@ class XiaomiHHCCJCY01 : public Component, public esp32_ble_tracker::ESPBTDeviceL | ||||
|   void set_moisture(sensor::Sensor *moisture) { moisture_ = moisture; } | ||||
|   void set_conductivity(sensor::Sensor *conductivity) { conductivity_ = conductivity; } | ||||
|   void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
| @@ -49,7 +29,6 @@ class XiaomiHHCCJCY01 : public Component, public esp32_ble_tracker::ESPBTDeviceL | ||||
|   sensor::Sensor *moisture_{nullptr}; | ||||
|   sensor::Sensor *conductivity_{nullptr}; | ||||
|   sensor::Sensor *illuminance_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_hhccjcy01 | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_hhccpot002/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_hhccpot002/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										35
									
								
								esphome/components/xiaomi_hhccpot002/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/xiaomi_hhccpot002/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_MAC_ADDRESS, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ | ||||
|     CONF_MOISTURE, CONF_CONDUCTIVITY, UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_hhccpot002_ns = cg.esphome_ns.namespace('xiaomi_hhccpot002') | ||||
| XiaomiHHCCPOT002 = xiaomi_hhccpot002_ns.class_('XiaomiHHCCPOT002', | ||||
|                                                esp32_ble_tracker.ESPBTDeviceListener, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiHHCCPOT002), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), | ||||
|     cv.Optional(CONF_CONDUCTIVITY): | ||||
|         sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|  | ||||
|     if CONF_MOISTURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_MOISTURE]) | ||||
|         cg.add(var.set_moisture(sens)) | ||||
|     if CONF_CONDUCTIVITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_CONDUCTIVITY]) | ||||
|         cg.add(var.set_conductivity(sens)) | ||||
							
								
								
									
										60
									
								
								esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| #include "xiaomi_hhccpot002.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_hhccpot002 { | ||||
|  | ||||
| static const char *TAG = "xiaomi_hhccpot002"; | ||||
|  | ||||
| void XiaomiHHCCPOT002 ::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi HHCCPOT002"); | ||||
|   LOG_SENSOR("  ", "Moisture", this->moisture_); | ||||
|   LOG_SENSOR("  ", "Conductivity", this->conductivity_); | ||||
| } | ||||
|  | ||||
| bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->moisture.has_value() && this->moisture_ != nullptr) | ||||
|       this->moisture_->publish_state(*res->moisture); | ||||
|     if (res->conductivity.has_value() && this->conductivity_ != nullptr) | ||||
|       this->conductivity_->publish_state(*res->conductivity); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_hhccpot002 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										33
									
								
								esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_hhccpot002 { | ||||
|  | ||||
| class XiaomiHHCCPOT002 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_moisture(sensor::Sensor *moisture) { moisture_ = moisture; } | ||||
|   void set_conductivity(sensor::Sensor *conductivity) { conductivity_ = conductivity; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   sensor::Sensor *moisture_{nullptr}; | ||||
|   sensor::Sensor *conductivity_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_hhccpot002 | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_jqjcy01ym/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_jqjcy01ym/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										44
									
								
								esphome/components/xiaomi_jqjcy01ym/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/xiaomi_jqjcy01ym/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ | ||||
|     CONF_HUMIDITY, UNIT_MILLIGRAMS_PER_CUBIC_METER, ICON_FLASK_OUTLINE, CONF_FORMALDEHYDE | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_jqjcy01ym_ns = cg.esphome_ns.namespace('xiaomi_jqjcy01ym') | ||||
| XiaomiJQJCY01YM = xiaomi_jqjcy01ym_ns.class_('XiaomiJQJCY01YM', | ||||
|                                              esp32_ble_tracker.ESPBTDeviceListener, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiJQJCY01YM), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), | ||||
|     cv.Optional(CONF_FORMALDEHYDE): | ||||
|         sensor.sensor_schema(UNIT_MILLIGRAMS_PER_CUBIC_METER, ICON_FLASK_OUTLINE, 2), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_FORMALDEHYDE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_FORMALDEHYDE]) | ||||
|         cg.add(var.set_formaldehyde(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
							
								
								
									
										66
									
								
								esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #include "xiaomi_jqjcy01ym.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_jqjcy01ym { | ||||
|  | ||||
| static const char *TAG = "xiaomi_jqjcy01ym"; | ||||
|  | ||||
| void XiaomiJQJCY01YM::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi JQJCY01YM"); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||
|   LOG_SENSOR("  ", "Formaldehyde", this->formaldehyde_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->formaldehyde.has_value() && this->formaldehyde_ != nullptr) | ||||
|       this->formaldehyde_->publish_state(*res->formaldehyde); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_jqjcy01ym | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										37
									
								
								esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_jqjcy01ym { | ||||
|  | ||||
| class XiaomiJQJCY01YM : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   void set_formaldehyde(sensor::Sensor *formaldehyde) { formaldehyde_ = formaldehyde; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
|   sensor::Sensor *formaldehyde_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_jqjcy01ym | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -14,6 +14,46 @@ void XiaomiLYWSD02::dump_config() { | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||
| } | ||||
|  | ||||
| bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_lywsd02 | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -14,20 +14,7 @@ class XiaomiLYWSD02 : public Component, public esp32_ble_tracker::ESPBTDeviceLis | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||
|     if (device.address_uint64() != this->address_) | ||||
|       return false; | ||||
|  | ||||
|     auto res = xiaomi_ble::parse_xiaomi(device); | ||||
|     if (!res.has_value()) | ||||
|       return false; | ||||
|  | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     return true; | ||||
|   } | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_lywsd03mmc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_lywsd03mmc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										44
									
								
								esphome/components/xiaomi_lywsd03mmc/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/xiaomi_lywsd03mmc/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ | ||||
|     CONF_BINDKEY | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_lywsd03mmc_ns = cg.esphome_ns.namespace('xiaomi_lywsd03mmc') | ||||
| XiaomiLYWSD03MMC = xiaomi_lywsd03mmc_ns.class_('XiaomiLYWSD03MMC', | ||||
|                                                esp32_ble_tracker.ESPBTDeviceListener, | ||||
|                                                cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiLYWSD03MMC), | ||||
|     cv.Required(CONF_BINDKEY): cv.bind_key, | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|     cg.add(var.set_bindkey(config[CONF_BINDKEY])) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
|  | ||||
|     cg.add_library("mbedtls", "cdf462088d") | ||||
							
								
								
									
										81
									
								
								esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| #include "xiaomi_lywsd03mmc.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_lywsd03mmc { | ||||
|  | ||||
| static const char *TAG = "xiaomi_lywsd03mmc"; | ||||
|  | ||||
| void XiaomiLYWSD03MMC::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC"); | ||||
|   ESP_LOGCONFIG(TAG, "  Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption && | ||||
|         (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast<std::vector<uint8_t> &>(service_data.data), this->bindkey_, | ||||
|                                               this->address_)))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) { | ||||
|       // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 | ||||
|       *res->humidity = trunc(*res->humidity); | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { | ||||
|   memset(bindkey_, 0, 16); | ||||
|   if (bindkey.size() != 32) { | ||||
|     return; | ||||
|   } | ||||
|   char temp[3] = {0}; | ||||
|   for (int i = 0; i < 16; i++) { | ||||
|     strncpy(temp, &(bindkey.c_str()[i * 2]), 2); | ||||
|     bindkey_[i] = std::strtoul(temp, NULL, 16); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_lywsd03mmc | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										36
									
								
								esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_lywsd03mmc { | ||||
|  | ||||
| class XiaomiLYWSD03MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; }; | ||||
|   void set_bindkey(const std::string &bindkey); | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   uint8_t bindkey_[16]; | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_lywsd03mmc | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -15,6 +15,48 @@ void XiaomiLYWSDCGQ::dump_config() { | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_lywsdcgq | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -14,22 +14,7 @@ class XiaomiLYWSDCGQ : public Component, public esp32_ble_tracker::ESPBTDeviceLi | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||
|     if (device.address_uint64() != this->address_) | ||||
|       return false; | ||||
|  | ||||
|     auto res = xiaomi_ble::parse_xiaomi(device); | ||||
|     if (!res.has_value()) | ||||
|       return false; | ||||
|  | ||||
|     if (res->temperature.has_value() && this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(*res->temperature); | ||||
|     if (res->humidity.has_value() && this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(*res->humidity); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     return true; | ||||
|   } | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_mjyd02yla/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_mjyd02yla/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										47
									
								
								esphome/components/xiaomi_mjyd02yla/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/xiaomi_mjyd02yla/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, binary_sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY, \ | ||||
|     CONF_DEVICE_CLASS, CONF_LIGHT, CONF_BATTERY_LEVEL, UNIT_PERCENT, ICON_BATTERY, \ | ||||
|     CONF_IDLE_TIME, UNIT_MINUTE, ICON_TIMELAPSE | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_mjyd02yla_ns = cg.esphome_ns.namespace('xiaomi_mjyd02yla') | ||||
| XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_('XiaomiMJYD02YLA', binary_sensor.BinarySensor, | ||||
|                                              cg.Component, esp32_ble_tracker.ESPBTDeviceListener) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiMJYD02YLA), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Required(CONF_BINDKEY): cv.bind_key, | ||||
|     cv.Optional(CONF_DEVICE_CLASS, default='motion'): binary_sensor.device_class, | ||||
|     cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema(UNIT_MINUTE, ICON_TIMELAPSE, 0), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
|     cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|         cv.Optional(CONF_DEVICE_CLASS, default='light'): binary_sensor.device_class, | ||||
|     }), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|     yield binary_sensor.register_binary_sensor(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|     cg.add(var.set_bindkey(config[CONF_BINDKEY])) | ||||
|  | ||||
|     if CONF_IDLE_TIME in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_IDLE_TIME]) | ||||
|         cg.add(var.set_idle_time(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
|     if CONF_LIGHT in config: | ||||
|         sens = yield binary_sensor.new_binary_sensor(config[CONF_LIGHT]) | ||||
|         cg.add(var.set_light(sens)) | ||||
|  | ||||
|     cg.add_library("mbedtls", "cdf462088d") | ||||
							
								
								
									
										79
									
								
								esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| #include "xiaomi_mjyd02yla.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_mjyd02yla { | ||||
|  | ||||
| static const char *TAG = "xiaomi_mjyd02yla"; | ||||
|  | ||||
| void XiaomiMJYD02YLA::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi MJYD02YL-A"); | ||||
|   LOG_BINARY_SENSOR("  ", "Motion", this); | ||||
|   LOG_BINARY_SENSOR("  ", "Light", this->is_light_); | ||||
|   LOG_SENSOR("  ", "Idle Time", this->idle_time_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption && | ||||
|         (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast<std::vector<uint8_t> &>(service_data.data), this->bindkey_, | ||||
|                                               this->address_)))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->idle_time.has_value() && this->idle_time_ != nullptr) | ||||
|       this->idle_time_->publish_state(*res->idle_time); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     if (res->is_light.has_value() && this->is_light_ != nullptr) | ||||
|       this->is_light_->publish_state(*res->is_light); | ||||
|     if (res->has_motion.has_value()) | ||||
|       this->publish_state(*res->has_motion); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { | ||||
|   memset(bindkey_, 0, 16); | ||||
|   if (bindkey.size() != 32) { | ||||
|     return; | ||||
|   } | ||||
|   char temp[3] = {0}; | ||||
|   for (int i = 0; i < 16; i++) { | ||||
|     strncpy(temp, &(bindkey.c_str()[i * 2]), 2); | ||||
|     bindkey_[i] = std::strtoul(temp, NULL, 16); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_mjyd02yla | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										40
									
								
								esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_mjyd02yla { | ||||
|  | ||||
| class XiaomiMJYD02YLA : public Component, | ||||
|                         public binary_sensor::BinarySensorInitiallyOff, | ||||
|                         public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|   void set_bindkey(const std::string &bindkey); | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_idle_time(sensor::Sensor *idle_time) { idle_time_ = idle_time; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|   void set_light(binary_sensor::BinarySensor *light) { is_light_ = light; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   uint8_t bindkey_[16]; | ||||
|   sensor::Sensor *idle_time_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
|   binary_sensor::BinarySensor *is_light_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_mjyd02yla | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_mue4094rt/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_mue4094rt/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										29
									
								
								esphome/components/xiaomi_mue4094rt/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/xiaomi_mue4094rt/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import binary_sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_DEVICE_CLASS, CONF_TIMEOUT, CONF_ID | ||||
|  | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_mue4094rt_ns = cg.esphome_ns.namespace('xiaomi_mue4094rt') | ||||
| XiaomiMUE4094RT = xiaomi_mue4094rt_ns.class_('XiaomiMUE4094RT', binary_sensor.BinarySensor, | ||||
|                                              cg.Component, esp32_ble_tracker.ESPBTDeviceListener) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiMUE4094RT), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_DEVICE_CLASS, default='motion'): binary_sensor.device_class, | ||||
|     cv.Optional(CONF_TIMEOUT, default='5s'): cv.positive_time_period_milliseconds, | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|     yield binary_sensor.register_binary_sensor(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|     cg.add(var.set_time(config[CONF_TIMEOUT])) | ||||
							
								
								
									
										59
									
								
								esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #include "xiaomi_mue4094rt.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_mue4094rt { | ||||
|  | ||||
| static const char *TAG = "xiaomi_mue4094rt"; | ||||
|  | ||||
| void XiaomiMUE4094RT::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi MUE4094RT"); | ||||
|   LOG_BINARY_SENSOR("  ", "Motion", this); | ||||
| } | ||||
|  | ||||
| bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_motion.has_value()) { | ||||
|       this->publish_state(*res->has_motion); | ||||
|       this->set_timeout("motion_timeout", timeout_, [this]() { this->publish_state(false); }); | ||||
|     } | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_mue4094rt | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										33
									
								
								esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_mue4094rt { | ||||
|  | ||||
| class XiaomiMUE4094RT : public Component, | ||||
|                         public binary_sensor::BinarySensorInitiallyOff, | ||||
|                         public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_time(uint16_t timeout) { timeout_ = timeout; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   uint16_t timeout_; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_mue4094rt | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										0
									
								
								esphome/components/xiaomi_wx08zm/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/xiaomi_wx08zm/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										36
									
								
								esphome/components/xiaomi_wx08zm/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/xiaomi_wx08zm/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, binary_sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TABLET, \ | ||||
|     UNIT_PERCENT, ICON_BUG, ICON_BATTERY, CONF_ID | ||||
|  | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| AUTO_LOAD = ['xiaomi_ble'] | ||||
|  | ||||
| xiaomi_wx08zm_ns = cg.esphome_ns.namespace('xiaomi_wx08zm') | ||||
| XiaomiWX08ZM = xiaomi_wx08zm_ns.class_('XiaomiWX08ZM', binary_sensor.BinarySensor, | ||||
|                                        esp32_ble_tracker.ESPBTDeviceListener, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), | ||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TABLET): sensor.sensor_schema(UNIT_PERCENT, ICON_BUG, 0), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield esp32_ble_tracker.register_ble_device(var, config) | ||||
|     yield binary_sensor.register_binary_sensor(var, config) | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|  | ||||
|     if CONF_TABLET in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TABLET]) | ||||
|         cg.add(var.set_tablet(sens)) | ||||
|     if CONF_BATTERY_LEVEL in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||
|         cg.add(var.set_battery_level(sens)) | ||||
							
								
								
									
										64
									
								
								esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #include "xiaomi_wx08zm.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_wx08zm { | ||||
|  | ||||
| static const char *TAG = "xiaomi_wx08zm"; | ||||
|  | ||||
| void XiaomiWX08ZM::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Xiaomi WX08ZM"); | ||||
|   LOG_BINARY_SENSOR("  ", "Mosquito Repellent", this); | ||||
|   LOG_SENSOR("  ", "Tablet Resource", this->tablet_); | ||||
|   LOG_SENSOR("  ", "Battery Level", this->battery_level_); | ||||
| } | ||||
|  | ||||
| bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||
|   if (device.address_uint64() != this->address_) { | ||||
|     ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); | ||||
|  | ||||
|   bool success = false; | ||||
|   for (auto &service_data : device.get_service_datas()) { | ||||
|     auto res = xiaomi_ble::parse_xiaomi_header(service_data); | ||||
|     if (!res.has_value()) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_duplicate) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->has_encryption) { | ||||
|       ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { | ||||
|       continue; | ||||
|     } | ||||
|     if (res->is_active.has_value()) { | ||||
|       this->publish_state(*res->is_active); | ||||
|     } | ||||
|     if (res->tablet.has_value() && this->tablet_ != nullptr) | ||||
|       this->tablet_->publish_state(*res->tablet); | ||||
|     if (res->battery_level.has_value() && this->battery_level_ != nullptr) | ||||
|       this->battery_level_->publish_state(*res->battery_level); | ||||
|     success = true; | ||||
|   } | ||||
|  | ||||
|   if (!success) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace xiaomi_wx08zm | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										36
									
								
								esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/xiaomi_ble/xiaomi_ble.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace xiaomi_wx08zm { | ||||
|  | ||||
| class XiaomiWX08ZM : public Component, | ||||
|                      public binary_sensor::BinarySensorInitiallyOff, | ||||
|                      public esp32_ble_tracker::ESPBTDeviceListener { | ||||
|  public: | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_tablet(sensor::Sensor *tablet) { tablet_ = tablet; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|  | ||||
|  protected: | ||||
|   uint64_t address_; | ||||
|   sensor::Sensor *tablet_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace xiaomi_wx08zm | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -566,6 +566,23 @@ def mac_address(value): | ||||
|     return core.MACAddress(*parts_int) | ||||
|  | ||||
|  | ||||
| def bind_key(value): | ||||
|     value = string_strict(value) | ||||
|     parts = [value[i:i+2] for i in range(0, len(value), 2)] | ||||
|     if len(parts) != 16: | ||||
|         raise Invalid("Bind key must consist of 16 hexadecimal numbers") | ||||
|     parts_int = [] | ||||
|     if any(len(part) != 2 for part in parts): | ||||
|         raise Invalid("Bind key must be format XX") | ||||
|     for part in parts: | ||||
|         try: | ||||
|             parts_int.append(int(part, 16)) | ||||
|         except ValueError: | ||||
|             raise Invalid("Bind key must be hex values from 00 to FF") | ||||
|  | ||||
|     return ''.join(f'{part:02X}' for part in parts_int) | ||||
|  | ||||
|  | ||||
| def uuid(value): | ||||
|     return Coerce(uuid_.UUID)(value) | ||||
|  | ||||
|   | ||||
| @@ -55,6 +55,7 @@ CONF_BELOW = 'below' | ||||
| CONF_BINARY = 'binary' | ||||
| CONF_BINARY_SENSOR = 'binary_sensor' | ||||
| CONF_BINARY_SENSORS = 'binary_sensors' | ||||
| CONF_BINDKEY = 'bindkey' | ||||
| CONF_BIRTH_MESSAGE = 'birth_message' | ||||
| CONF_BIT_DEPTH = 'bit_depth' | ||||
| CONF_BLUE = 'blue' | ||||
| @@ -194,6 +195,7 @@ CONF_ID = 'id' | ||||
| CONF_IDLE = 'idle' | ||||
| CONF_IDLE_ACTION = 'idle_action' | ||||
| CONF_IDLE_LEVEL = 'idle_level' | ||||
| CONF_IDLE_TIME = 'idle_time' | ||||
| CONF_IF = 'if' | ||||
| CONF_IIR_FILTER = 'iir_filter' | ||||
| CONF_ILLUMINANCE = 'illuminance' | ||||
| @@ -265,6 +267,7 @@ CONF_MODEL = 'model' | ||||
| CONF_MOISTURE = 'moisture' | ||||
| CONF_MONTHS = 'months' | ||||
| CONF_MOSI_PIN = 'mosi_pin' | ||||
| CONF_MOTION = 'motion' | ||||
| CONF_MOVEMENT_COUNTER = 'movement_counter' | ||||
| CONF_MQTT = 'mqtt' | ||||
| CONF_MQTT_ID = 'mqtt_id' | ||||
| @@ -445,6 +448,7 @@ CONF_SUPPORTS_HEAT = 'supports_heat' | ||||
| CONF_SWING_MODE = 'swing_mode' | ||||
| CONF_SWITCHES = 'switches' | ||||
| CONF_SYNC = 'sync' | ||||
| CONF_TABLET = 'tablet' | ||||
| CONF_TAG = 'tag' | ||||
| CONF_TARGET = 'target' | ||||
| CONF_TARGET_TEMPERATURE = 'target_temperature' | ||||
| @@ -520,17 +524,20 @@ ICON_ARROW_EXPAND_VERTICAL = 'mdi:arrow-expand-vertical' | ||||
| ICON_BATTERY = 'mdi:battery' | ||||
| ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download' | ||||
| ICON_BRIGHTNESS_5 = 'mdi:brightness-5' | ||||
| ICON_BUG = 'mdi:bug' | ||||
| ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline' | ||||
| ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon' | ||||
| ICON_COUNTER = 'mdi:counter' | ||||
| ICON_CURRENT_AC = 'mdi:current-ac' | ||||
| ICON_EMPTY = '' | ||||
| ICON_FLASH = 'mdi:flash' | ||||
| ICON_FLASK_OUTLINE = 'mdi:flask-outline' | ||||
| ICON_FLOWER = 'mdi:flower' | ||||
| ICON_GAS_CYLINDER = 'mdi:gas-cylinder' | ||||
| ICON_GAUGE = 'mdi:gauge' | ||||
| ICON_LIGHTBULB = 'mdi:lightbulb' | ||||
| ICON_MAGNET = 'mdi:magnet' | ||||
| ICON_MOTION_SENSOR = 'mdi:motion-sensor' | ||||
| ICON_NEW_BOX = 'mdi:new-box' | ||||
| ICON_PERCENT = 'mdi:percent' | ||||
| ICON_PERIODIC_TABLE_CO2 = 'mdi:periodic-table-co2' | ||||
| @@ -546,6 +553,7 @@ ICON_SIGN_DIRECTION = 'mdi:sign-direction' | ||||
| ICON_SIGNAL = 'mdi:signal-distance-variant' | ||||
| ICON_SIGNAL_DISTANCE_VARIANT = 'mdi:signal' | ||||
| ICON_THERMOMETER = 'mdi:thermometer' | ||||
| ICON_TIMELAPSE = 'mdi:timelapse' | ||||
| ICON_TIMER = 'mdi:timer' | ||||
| ICON_WATER_PERCENT = 'mdi:water-percent' | ||||
| ICON_WEATHER_SUNSET = 'mdi:weather-sunset' | ||||
| @@ -575,6 +583,8 @@ UNIT_MICROGRAMS_PER_CUBIC_METER = 'µg/m³' | ||||
| UNIT_MICROMETER = 'µm' | ||||
| UNIT_MICROSIEMENS_PER_CENTIMETER = 'µS/cm' | ||||
| UNIT_MICROTESLA = 'µT' | ||||
| UNIT_MILLIGRAMS_PER_CUBIC_METER = 'mg/m³' | ||||
| UNIT_MINUTE = 'min' | ||||
| UNIT_OHM = 'Ω' | ||||
| UNIT_PARTS_PER_BILLION = 'ppb' | ||||
| UNIT_PARTS_PER_MILLION = 'ppm' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user