From dea6675c218fe4b3c6435cadb33d7da74dc18b0f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 6 Apr 2020 19:11:41 +0200 Subject: [PATCH] Add HM3301 laser dust detection sensor (#963) * Add HM3301 laser dust detection sensor * Fixed after lint * Fixed after lint * added status clear warning --- esphome/components/hm3301/__init__.py | 0 esphome/components/hm3301/hm3301.cpp | 82 +++++++++++++++++++++++++++ esphome/components/hm3301/hm3301.h | 42 ++++++++++++++ esphome/components/hm3301/sensor.py | 43 ++++++++++++++ platformio.ini | 1 + tests/test1.yaml | 7 +++ tests/test3.yaml | 7 +++ 7 files changed, 182 insertions(+) create mode 100644 esphome/components/hm3301/__init__.py create mode 100644 esphome/components/hm3301/hm3301.cpp create mode 100644 esphome/components/hm3301/hm3301.h create mode 100644 esphome/components/hm3301/sensor.py diff --git a/esphome/components/hm3301/__init__.py b/esphome/components/hm3301/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp new file mode 100644 index 0000000000..6456ee354a --- /dev/null +++ b/esphome/components/hm3301/hm3301.cpp @@ -0,0 +1,82 @@ +#include "hm3301.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace hm3301 { + +static const char *TAG = "hm3301.sensor"; + +static const uint8_t PM_1_0_VALUE_INDEX = 5; +static const uint8_t PM_2_5_VALUE_INDEX = 6; +static const uint8_t PM_10_0_VALUE_INDEX = 7; + +void HM3301Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up HM3301..."); + hm3301_ = new HM330X(); + error_code_ = hm3301_->init(); + if (error_code_ != NO_ERROR) { + this->mark_failed(); + return; + } +} + +void HM3301Component::dump_config() { + ESP_LOGCONFIG(TAG, "HM3301:"); + LOG_I2C_DEVICE(this); + if (error_code_ == ERROR_COMM) { + ESP_LOGE(TAG, "Communication with HM3301 failed!"); + } + + LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_); +} + +float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } + +void HM3301Component::update() { + if (!this->read_sensor_value_(data_buffer_)) { + ESP_LOGW(TAG, "Read result failed"); + this->status_set_warning(); + return; + } + + if (!this->validate_checksum_(data_buffer_)) { + ESP_LOGW(TAG, "Checksum validation failed"); + this->status_set_warning(); + return; + } + + if (this->pm_1_0_sensor_ != nullptr) { + uint16_t value = get_sensor_value_(data_buffer_, PM_1_0_VALUE_INDEX); + this->pm_1_0_sensor_->publish_state(value); + } + if (this->pm_2_5_sensor_ != nullptr) { + uint16_t value = get_sensor_value_(data_buffer_, PM_2_5_VALUE_INDEX); + this->pm_2_5_sensor_->publish_state(value); + } + if (this->pm_10_0_sensor_ != nullptr) { + uint16_t value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); + this->pm_10_0_sensor_->publish_state(value); + } + + this->status_clear_warning(); +} + +bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); } + +bool HM3301Component::validate_checksum_(const uint8_t *data) { + uint8_t sum = 0; + for (int i = 0; i < 28; i++) { + sum += data[i]; + } + + return sum == data[28]; +} + +uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { + return (uint16_t) data[i * 2] << 8 | data[i * 2 + 1]; +} + +} // namespace hm3301 +} // namespace esphome diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h new file mode 100644 index 0000000000..0fbb32612e --- /dev/null +++ b/esphome/components/hm3301/hm3301.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +#include + +namespace esphome { +namespace hm3301 { + +class HM3301Component : public PollingComponent, public i2c::I2CDevice { + public: + HM3301Component() = default; + + void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { pm_1_0_sensor_ = pm_1_0_sensor; } + void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { pm_2_5_sensor_ = pm_2_5_sensor; } + void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + HM330X *hm3301_; + + HM330XErrorCode error_code_{NO_ERROR}; + + uint8_t data_buffer_[30]; + + sensor::Sensor *pm_1_0_sensor_{nullptr}; + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + + bool read_sensor_value_(uint8_t *); + bool validate_checksum_(const uint8_t *); + uint16_t get_sensor_value_(const uint8_t *, uint8_t); +}; + +} // namespace hm3301 +} // namespace esphome diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py new file mode 100644 index 0000000000..718d0a20bb --- /dev/null +++ b/esphome/components/hm3301/sensor.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import CONF_ID, CONF_PM_2_5, CONF_PM_10_0, CONF_PM_1_0, \ + UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON + +DEPENDENCIES = ['i2c'] + +hm3301_ns = cg.esphome_ns.namespace('hm3301') +HM3301Component = hm3301_ns.class_('HM3301Component', cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All(cv.Schema({ + cv.GenerateID(): cv.declare_id(HM3301Component), + + cv.Optional(CONF_PM_1_0): + sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), + cv.Optional(CONF_PM_2_5): + sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), + cv.Optional(CONF_PM_10_0): + sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), + +}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40))) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + if CONF_PM_1_0 in config: + sens = yield sensor.new_sensor(config[CONF_PM_1_0]) + cg.add(var.set_pm_1_0_sensor(sens)) + + if CONF_PM_2_5 in config: + sens = yield sensor.new_sensor(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(sens)) + + if CONF_PM_10_0 in config: + sens = yield sensor.new_sensor(config[CONF_PM_10_0]) + cg.add(var.set_pm_10_0_sensor(sens)) + + # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 + cg.add_library('6306', '1.0.3') diff --git a/platformio.ini b/platformio.ini index 408e5af1ce..a6e9926de6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ lib_deps = ESPAsyncTCP-esphome@1.2.2 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display + 6306@1.0.3 ; HM3301 build_flags = -Wno-reorder -DUSE_WEB_SERVER diff --git a/tests/test1.yaml b/tests/test1.yaml index 1fc6b650c4..f87d77c9c2 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -720,6 +720,13 @@ sensor: - platform: tmp117 name: "TMP117 Temperature" update_interval: 5s + - platform: hm3301 + pm_1_0: + name: "PM1.0" + pm_2_5: + name: "PM2.5" + pm_10_0: + name: "PM10.0" esp32_touch: setup_mode: False diff --git a/tests/test3.yaml b/tests/test3.yaml index 9407cab687..e7c3da18de 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -353,6 +353,13 @@ sensor: name: "PZEMDC Current" power: name: "PZEMDC Power" + - platform: hm3301 + pm_1_0: + name: "PM1.0" + pm_2_5: + name: "PM2.5" + pm_10_0: + name: "PM10.0" time: - platform: homeassistant