From e30512931b0602b1558ae8b73205a56cd4a098db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mayoral=20Mart=C3=ADnez?= Date: Mon, 14 Oct 2019 13:25:08 +0200 Subject: [PATCH] Add Xiaomi Cleargrass Temperature and Humidity Sensor (#735) * Add Xiaomi Cleargrass Temperature and Humidity Sensor * fix CI Travis * fix CI Travis 2 * Improve device detection (more accurate) Co-authored-by: t151602 --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 9 +++- esphome/components/xiaomi_ble/xiaomi_ble.h | 2 +- .../components/xiaomi_cleargrass/__init__.py | 0 .../components/xiaomi_cleargrass/sensor.py | 38 ++++++++++++++ .../xiaomi_cleargrass/xiaomi_cleargrass.cpp | 21 ++++++++ .../xiaomi_cleargrass/xiaomi_cleargrass.h | 50 +++++++++++++++++++ 6 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 esphome/components/xiaomi_cleargrass/__init__.py create mode 100644 esphome/components/xiaomi_cleargrass/sensor.py create mode 100644 esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.cpp create mode 100644 esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.h diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 5bb6709e5f..33e64246b4 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -84,13 +84,14 @@ optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d bool is_mijia = (raw[1] & 0x20) == 0x20 && raw[2] == 0xAA && raw[3] == 0x01; bool is_miflora = (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_cleargrass = (raw[1] & 0x30) == 0x30 && raw[2] == 0x47 && raw[3] == 0x03; - if (!is_mijia && !is_miflora && !is_lywsd02) { + if (!is_mijia && !is_miflora && !is_lywsd02 && !is_cleargrass) { // ESP_LOGVV(TAG, "Xiaomi no magic bytes"); return {}; } - uint8_t raw_offset = is_mijia ? 11 : 12; + uint8_t raw_offset = is_mijia || is_cleargrass ? 11 : 12; const uint8_t raw_type = raw[raw_offset]; const uint8_t data_length = raw[raw_offset + 2]; @@ -107,6 +108,8 @@ optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d result.type = XiaomiParseResult::TYPE_MIJIA; } else if (is_lywsd02) { result.type = XiaomiParseResult::TYPE_LYWSD02; + } else if (is_cleargrass) { + result.type = XiaomiParseResult::TYPE_CLEARGRASS; } bool success = parse_xiaomi_data_byte(raw_type, data, data_length, result); if (!success) @@ -124,6 +127,8 @@ bool XiaomiListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) name = "Mi Jia"; } else if (res->type == XiaomiParseResult::TYPE_LYWSD02) { name = "LYWSD02"; + } else if (res->type == XiaomiParseResult::TYPE_CLEARGRASS) { + name = "Cleargrass"; } ESP_LOGD(TAG, "Got Xiaomi %s (%s):", name, device.address_str().c_str()); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index b8b602ecef..1fbd8ae6b0 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -9,7 +9,7 @@ namespace esphome { namespace xiaomi_ble { struct XiaomiParseResult { - enum { TYPE_MIJIA, TYPE_MIFLORA, TYPE_LYWSD02 } type; + enum { TYPE_MIJIA, TYPE_MIFLORA, TYPE_LYWSD02, TYPE_CLEARGRASS } type; optional temperature; optional humidity; optional battery_level; diff --git a/esphome/components/xiaomi_cleargrass/__init__.py b/esphome/components/xiaomi_cleargrass/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/xiaomi_cleargrass/sensor.py b/esphome/components/xiaomi_cleargrass/sensor.py new file mode 100644 index 0000000000..66fcbc0fcd --- /dev/null +++ b/esphome/components/xiaomi_cleargrass/sensor.py @@ -0,0 +1,38 @@ +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 + +DEPENDENCIES = ['esp32_ble_tracker'] +AUTO_LOAD = ['xiaomi_ble'] + +xiaomi_cleargrass_ns = cg.esphome_ns.namespace('xiaomi_cleargrass') +XiaomiCleargrass = xiaomi_cleargrass_ns.class_( + 'XiaomiCleargrass', esp32_ble_tracker.ESPBTDeviceListener, cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(XiaomiCleargrass), + 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)) + + 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)) diff --git a/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.cpp b/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.cpp new file mode 100644 index 0000000000..e50994ca4e --- /dev/null +++ b/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.cpp @@ -0,0 +1,21 @@ +#include "xiaomi_cleargrass.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_cleargrass { + +static const char *TAG = "xiaomi_cleargrass"; + +void XiaomiCleargrass::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi Cleargrass"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +} // namespace xiaomi_cleargrass +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.h b/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.h new file mode 100644 index 0000000000..ea5c80a64f --- /dev/null +++ b/esphome/components/xiaomi_cleargrass/xiaomi_cleargrass.h @@ -0,0 +1,50 @@ +#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_cleargrass { + +class XiaomiCleargrass : 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 { + 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; + } + + 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_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace xiaomi_cleargrass +} // namespace esphome + +#endif