diff --git a/.travis.yml b/.travis.yml index ca0a3082db..3e6c7adccb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ matrix: - esphome tests/test1.yaml compile - esphome tests/test2.yaml compile - esphome tests/test3.yaml compile + - esphome tests/test4.yaml compile - env: TARGET=Cpp-Lint dist: trusty sudo: required diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 030ee73d4b..2ca27bf51a 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -1,187 +1,322 @@ #include "xiaomi_ble.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef ARDUINO_ARCH_ESP32 +#include +#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 &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 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 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(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 &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::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(raw[4])); + + mbedtls_ccm_free(&ctx); + return true; +} + +bool report_xiaomi_results(const optional &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 diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 824ea80edf..daa71787a5 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -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 temperature; optional humidity; - optional battery_level; + optional moisture; optional conductivity; optional illuminance; - optional moisture; + optional formaldehyde; + optional battery_level; + optional tablet; + optional idle_time; + optional is_active; + optional has_motion; + optional 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 parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device); +bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); +optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); +bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); +bool report_xiaomi_results(const optional &result, const std::string &address); class XiaomiListener : public esp32_ble_tracker::ESPBTDeviceListener { public: diff --git a/esphome/components/xiaomi_cgd1/__init__.py b/esphome/components/xiaomi_cgd1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py new file mode 100644 index 0000000000..343279f8fe --- /dev/null +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -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") diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp new file mode 100644 index 0000000000..d701e8ee6d --- /dev/null +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -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 &>(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 diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h new file mode 100644 index 0000000000..b9e05f857c --- /dev/null +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -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 diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 6cc14f5a8e..a7c94fafad 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -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 diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index 7f73011275..57f883405c 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -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; } diff --git a/esphome/components/xiaomi_gcls002/__init__.py b/esphome/components/xiaomi_gcls002/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_gcls002/sensor.py b/esphome/components/xiaomi_gcls002/sensor.py new file mode 100644 index 0000000000..1822977c38 --- /dev/null +++ b/esphome/components/xiaomi_gcls002/sensor.py @@ -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)) diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp new file mode 100644 index 0000000000..24156f98ac --- /dev/null +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -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 diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h new file mode 100644 index 0000000000..d800e2837d --- /dev/null +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h @@ -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 diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 495446ba11..0b0349d7e4 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -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)) diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 8c8152c54c..fd099f7aa5 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -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 diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h index c1b8511bb8..e72bf98161 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h @@ -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 diff --git a/esphome/components/xiaomi_hhccpot002/__init__.py b/esphome/components/xiaomi_hhccpot002/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_hhccpot002/sensor.py b/esphome/components/xiaomi_hhccpot002/sensor.py new file mode 100644 index 0000000000..33cd96252e --- /dev/null +++ b/esphome/components/xiaomi_hhccpot002/sensor.py @@ -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)) diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp new file mode 100644 index 0000000000..2b5ad3a826 --- /dev/null +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -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 diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h new file mode 100644 index 0000000000..1add8e27b1 --- /dev/null +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h @@ -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 diff --git a/esphome/components/xiaomi_jqjcy01ym/__init__.py b/esphome/components/xiaomi_jqjcy01ym/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py new file mode 100644 index 0000000000..2bd397e829 --- /dev/null +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -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)) diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp new file mode 100644 index 0000000000..3e7090509b --- /dev/null +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -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 diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h new file mode 100644 index 0000000000..d750e1e97f --- /dev/null +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h @@ -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 diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index cd77c133a5..5ecd99047e 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -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 diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h index 9b8aba1bb0..f32506eb44 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h @@ -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; } diff --git a/esphome/components/xiaomi_lywsd03mmc/__init__.py b/esphome/components/xiaomi_lywsd03mmc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py new file mode 100644 index 0000000000..71f7b20752 --- /dev/null +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -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") diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp new file mode 100644 index 0000000000..e9cc99358b --- /dev/null +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -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 &>(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 diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h new file mode 100644 index 0000000000..c2828e3cd1 --- /dev/null +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -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 diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 2dacff2876..035ac8c906 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -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 diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h index b6756eec61..553b5965fd 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h @@ -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; } diff --git a/esphome/components/xiaomi_mjyd02yla/__init__.py b/esphome/components/xiaomi_mjyd02yla/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py new file mode 100644 index 0000000000..e34864c480 --- /dev/null +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -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") diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp new file mode 100644 index 0000000000..aaea3606ba --- /dev/null +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -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 &>(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 diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h new file mode 100644 index 0000000000..d3fde4d6f8 --- /dev/null +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -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 diff --git a/esphome/components/xiaomi_mue4094rt/__init__.py b/esphome/components/xiaomi_mue4094rt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_mue4094rt/binary_sensor.py b/esphome/components/xiaomi_mue4094rt/binary_sensor.py new file mode 100644 index 0000000000..946b1694c4 --- /dev/null +++ b/esphome/components/xiaomi_mue4094rt/binary_sensor.py @@ -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])) diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp new file mode 100644 index 0000000000..45337f330e --- /dev/null +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -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 diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h new file mode 100644 index 0000000000..31f913ec94 --- /dev/null +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h @@ -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 diff --git a/esphome/components/xiaomi_wx08zm/__init__.py b/esphome/components/xiaomi_wx08zm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py new file mode 100644 index 0000000000..1d60dbf5e0 --- /dev/null +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -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)) diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp new file mode 100644 index 0000000000..6ecdf8f4f4 --- /dev/null +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -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 diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h new file mode 100644 index 0000000000..f3eba0e159 --- /dev/null +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h @@ -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 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 55199e6647..fa2a14dbd6 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -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) diff --git a/esphome/const.py b/esphome/const.py index 31359e610d..b244d5681a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -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' diff --git a/tests/test2.yaml b/tests/test2.yaml index 6bc0ac93c3..34b7ad6b07 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -44,16 +44,14 @@ ota: logger: level: DEBUG -web_server: - auth: - username: admin - password: admin - as3935_i2c: irq_pin: GPIO12 sensor: + - platform: homeassistant + entity_id: sensor.hello_world + id: ha_hello_world - platform: ble_rssi mac_address: AC:37:43:77:5F:4C name: "BLE Google Home Mini RSSI value" @@ -66,40 +64,6 @@ sensor: - platform: ble_rssi service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' name: "BLE Test Service 128" - - platform: xiaomi_hhccjcy01 - mac_address: 94:2B:FF:5C:91:61 - temperature: - name: "Xiaomi HHCCJCY01 Temperature" - moisture: - name: "Xiaomi HHCCJCY01 Moisture" - illuminance: - name: "Xiaomi HHCCJCY01 Illuminance" - conductivity: - name: "Xiaomi HHCCJCY01 Soil Conductivity" - battery_level: - name: "Xiaomi HHCCJCY01 Battery Level" - - platform: xiaomi_lywsdcgq - mac_address: 7A:80:8E:19:36:BA - temperature: - name: "Xiaomi LYWSDCGQ Temperature" - humidity: - name: "Xiaomi LYWSDCGQ Humidity" - battery_level: - name: "Xiaomi LYWSDCGQ Battery Level" - - platform: xiaomi_lywsd02 - mac_address: 3F:5B:7D:82:58:4E - temperature: - name: "Xiaomi LYWSD02 Temperature" - humidity: - name: "Xiaomi LYWSD02 Humidity" - - platform: xiaomi_cgg1 - mac_address: 7A:80:8E:19:36:BA - temperature: - name: "Xiaomi CGG1 Temperature" - humidity: - name: "Xiaomi CGG1 Humidity" - battery_level: - name: "Xiaomi CGG1 Battery Level" - platform: ruuvitag mac_address: FF:56:D3:2F:7D:E8 humidity: @@ -122,14 +86,88 @@ sensor: name: "RuuviTag Movement Counter" measurement_sequence_number: name: "RuuviTag Measurement Sequence Number" - - platform: homeassistant - entity_id: sensor.hello_world - id: ha_hello_world - platform: as3935 lightning_energy: name: "Lightning Energy" distance: name: "Distance Storm" + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: "Xiaomi HHCCJCY01 Temperature" + moisture: + name: "Xiaomi HHCCJCY01 Moisture" + illuminance: + name: "Xiaomi HHCCJCY01 Illuminance" + conductivity: + name: "Xiaomi HHCCJCY01 Soil Conductivity" + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: "Xiaomi LYWSDCGQ Temperature" + humidity: + name: "Xiaomi LYWSDCGQ Humidity" + battery_level: + name: "Xiaomi LYWSDCGQ Battery Level" + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: "Xiaomi LYWSD02 Temperature" + humidity: + name: "Xiaomi LYWSD02 Humidity" + - platform: xiaomi_cgg1 + mac_address: 7A:80:8E:19:36:BA + temperature: + name: "Xiaomi CGG1 Temperature" + humidity: + name: "Xiaomi CGG1 Humidity" + battery_level: + name: "Xiaomi CGG1 Battery Level" + - platform: xiaomi_gcls002 + mac_address: "94:2B:FF:5C:91:61" + temperature: + name: "GCLS02 Temperature" + moisture: + name: "GCLS02 Moisture" + conductivity: + name: "GCLS02 Soil Conductivity" + illuminance: + name: "GCLS02 Illuminance" + - platform: xiaomi_hhccpot002 + mac_address: "94:2B:FF:5C:91:61" + moisture: + name: "HHCCPOT002 Moisture" + conductivity: + name: "HHCCPOT002 Soil Conductivity" + - platform: xiaomi_lywsd03mmc + mac_address: "A4:C1:38:4E:16:78" + bindkey: "e9efaa6873f9f9c87a5e75a5f814801c" + temperature: + name: "Xiaomi LYWSD03MMC Temperature" + humidity: + name: "Xiaomi LYWSD03MMC Humidity" + battery_level: + name: "Xiaomi LYWSD03MMC Battery Level" + - platform: xiaomi_cgd1 + mac_address: "A4:C1:38:D1:61:7D" + bindkey: "c99d2313182473b38001086febf781bd" + temperature: + name: "Xiaomi CGD1 Temperature" + humidity: + name: "Xiaomi CGD1 Humidity" + battery_level: + name: "Xiaomi CGD1 Battery Level" + - platform: xiaomi_jqjcy01ym + mac_address: "7A:80:8E:19:36:BA" + temperature: + name: "JQJCY01YM Temperature" + humidity: + name: "JQJCY01YM Humidity" + formaldehyde: + name: "JQJCY01YM Formaldehyde" + battery_level: + name: "JQJCY01YM Battery Level" + time: - platform: homeassistant @@ -142,6 +180,9 @@ esp32_touch: setup_mode: True binary_sensor: + - platform: homeassistant + entity_id: binary_sensor.hello_world + id: ha_hello_world_binary - platform: ble_presence mac_address: AC:37:43:77:5F:4C name: "ESP32 BLE Tracker Google Home Mini" @@ -158,11 +199,30 @@ binary_sensor: name: "ESP32 Touch Pad GPIO27" pin: GPIO27 threshold: 1000 - - platform: homeassistant - entity_id: binary_sensor.hello_world - id: ha_hello_world_binary - platform: as3935 name: "Storm Alert" + - platform: xiaomi_mue4094rt + name: "MUE4094RT Motion" + mac_address: "7A:80:8E:19:36:BA" + timeout: "5s" + - platform: xiaomi_mjyd02yla + name: "MJYD02YL-A Motion" + mac_address: "50:EC:50:CD:32:02" + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + idle_time: + name: "MJYD02YL-A Idle Time" + light: + name: "MJYD02YL-A Light Status" + battery_level: + name: "MJYD02YL-A Battery Level" + - platform: xiaomi_wx08zm + name: "WX08ZM Activation State" + mac_address: "74:a3:4a:b5:07:34" + tablet: + name: "WX08ZM Tablet Resource" + battery_level: + name: "WX08ZM Battery Level" + esp32_ble_tracker: on_ble_advertise: @@ -257,3 +317,4 @@ interval: display: + diff --git a/tests/test4.yaml b/tests/test4.yaml new file mode 100644 index 0000000000..c7aa016839 --- /dev/null +++ b/tests/test4.yaml @@ -0,0 +1,73 @@ +esphome: + name: $devicename + platform: ESP32 + board: nodemcu-32s + build_path: build/test4 + +substitutions: + devicename: test4 + +ethernet: + type: LAN8720 + mdc_pin: GPIO23 + mdio_pin: GPIO25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: GPIO25 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local + +api: + +i2c: + sda: 21 + scl: 22 + scan: False + +spi: + clk_pin: GPIO21 + mosi_pin: GPIO22 + miso_pin: GPIO23 + +uart: + tx_pin: GPIO22 + rx_pin: GPIO23 + baud_rate: 115200 + +ota: + safe_mode: True + port: 3286 + +logger: + level: DEBUG + +web_server: + auth: + username: admin + password: admin + +sensor: + - platform: homeassistant + entity_id: sensor.hello_world + id: ha_hello_world +# +# platform sensor.apds9960 requires component apds9960 +# +# - platform: apds9960 +# type: proximity +# name: APDS9960 Proximity +# - platform: apds9960 +# type: clear +# name: APDS9960 Clear +# - platform: apds9960 +# type: red +# name: APDS9960 Red +# - platform: apds9960 +# type: green +# name: APDS9960 Green +# - platform: apds9960 +# type: blue +# name: APDS9960 Blue