Add support for the SM300D2 7-in-1 sensor module (#1524)

* Added support for SM300D2 sensor module

* Fixed lint errors due to added tvoc config

* add device class

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
Moritz Glöckl 2021-03-03 01:54:52 +01:00 committed by GitHub
parent ac25b138f5
commit 8e93735861
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 4 deletions

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import CONF_ID, DEVICE_CLASS_EMPTY, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ from esphome.const import CONF_ID, DEVICE_CLASS_EMPTY, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2 UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_TVOC, CONF_HUMIDITY, ICON_MOLECULE_CO2
DEPENDENCIES = ['i2c'] DEPENDENCIES = ['i2c']
@ -10,7 +10,6 @@ ccs811_ns = cg.esphome_ns.namespace('ccs811')
CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice)
CONF_ECO2 = 'eco2' CONF_ECO2 = 'eco2'
CONF_TVOC = 'tvoc'
CONF_BASELINE = 'baseline' CONF_BASELINE = 'baseline'
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import CONF_ID, DEVICE_CLASS_EMPTY, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ from esphome.const import CONF_ID, DEVICE_CLASS_EMPTY, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
UNIT_PARTS_PER_BILLION, ICON_MOLECULE_CO2 UNIT_PARTS_PER_BILLION, ICON_MOLECULE_CO2, CONF_TVOC
DEPENDENCIES = ['i2c'] DEPENDENCIES = ['i2c']
@ -10,7 +10,6 @@ sgp30_ns = cg.esphome_ns.namespace('sgp30')
SGP30Component = sgp30_ns.class_('SGP30Component', cg.PollingComponent, i2c.I2CDevice) SGP30Component = sgp30_ns.class_('SGP30Component', cg.PollingComponent, i2c.I2CDevice)
CONF_ECO2 = 'eco2' CONF_ECO2 = 'eco2'
CONF_TVOC = 'tvoc'
CONF_BASELINE = 'baseline' CONF_BASELINE = 'baseline'
CONF_ECO2_BASELINE = 'eco2_baseline' CONF_ECO2_BASELINE = 'eco2_baseline'
CONF_TVOC_BASELINE = 'tvoc_baseline' CONF_TVOC_BASELINE = 'tvoc_baseline'

View file

View file

@ -0,0 +1,61 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import CONF_ID, CONF_CO2, CONF_FORMALDEHYDE, CONF_TVOC, CONF_PM_2_5, \
CONF_PM_10_0, CONF_TEMPERATURE, CONF_HUMIDITY, DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, \
DEVICE_CLASS_HUMIDITY, UNIT_PARTS_PER_MILLION, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_CELSIUS, \
UNIT_PERCENT, ICON_EMPTY, ICON_MOLECULE_CO2, ICON_FLASK, ICON_CHEMICAL_WEAPON, ICON_GRAIN
DEPENDENCIES = ['uart']
sm300d2_ns = cg.esphome_ns.namespace('sm300d2')
SM300D2Sensor = sm300d2_ns.class_('SM300D2Sensor', cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = cv.All(cv.Schema({
cv.GenerateID(): cv.declare_id(SM300D2Sensor),
cv.Optional(CONF_CO2):
sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY),
cv.Optional(CONF_FORMALDEHYDE):
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_FLASK, 0, DEVICE_CLASS_EMPTY),
cv.Optional(CONF_TVOC):
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0,
DEVICE_CLASS_EMPTY),
cv.Optional(CONF_PM_2_5):
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_GRAIN, 0, DEVICE_CLASS_EMPTY),
cv.Optional(CONF_PM_10_0):
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_GRAIN, 0, DEVICE_CLASS_EMPTY),
cv.Optional(CONF_TEMPERATURE):
sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 0, DEVICE_CLASS_TEMPERATURE),
cv.Optional(CONF_HUMIDITY):
sensor.sensor_schema(UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY),
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
if CONF_CO2 in config:
sens = yield sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2_sensor(sens))
if CONF_FORMALDEHYDE in config:
sens = yield sensor.new_sensor(config[CONF_FORMALDEHYDE])
cg.add(var.set_formaldehyde_sensor(sens))
if CONF_TVOC in config:
sens = yield sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc_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))
if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity_sensor(sens))

View file

@ -0,0 +1,99 @@
#include "sm300d2.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sm300d2 {
static const char *TAG = "sm300d2";
static const uint8_t SM300D2_RESPONSE_LENGTH = 17;
void SM300D2Sensor::update() {
uint8_t response[SM300D2_RESPONSE_LENGTH];
flush();
bool read_success = read_array(response, SM300D2_RESPONSE_LENGTH);
flush();
if (!read_success) {
ESP_LOGW(TAG, "Reading data from SM300D2 failed!");
status_set_warning();
return;
}
if (response[0] != 0x3C || response[1] != 0x02) {
ESP_LOGW(TAG, "Invalid preamble for SM300D2 response!");
this->status_set_warning();
return;
}
uint16_t calculated_checksum = this->sm300d2_checksum_(response);
if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) {
ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1],
calculated_checksum);
this->status_set_warning();
return;
}
this->status_clear_warning();
ESP_LOGW(TAG, "Successfully read SM300D2 data");
const uint16_t co2 = (response[2] * 256) + response[3];
const uint16_t formaldehyde = (response[4] * 256) + response[5];
const uint16_t tvoc = (response[6] * 256) + response[7];
const uint16_t pm_2_5 = (response[8] * 256) + response[9];
const uint16_t pm_10_0 = (response[10] * 256) + response[11];
const float temperature = response[12] + (response[13] * 0.1);
const float humidity = response[14] + (response[15] * 0.1);
ESP_LOGD(TAG, "Received CO₂: %u ppm", co2);
if (this->co2_sensor_ != nullptr)
this->co2_sensor_->publish_state(co2);
ESP_LOGD(TAG, "Received Formaldehyde: %u µg/m³", formaldehyde);
if (this->formaldehyde_sensor_ != nullptr)
this->formaldehyde_sensor_->publish_state(formaldehyde);
ESP_LOGD(TAG, "Received TVOC: %u µg/m³", tvoc);
if (this->tvoc_sensor_ != nullptr)
this->tvoc_sensor_->publish_state(tvoc);
ESP_LOGD(TAG, "Received PM2.5: %u µg/m³", pm_2_5);
if (this->pm_2_5_sensor_ != nullptr)
this->pm_2_5_sensor_->publish_state(pm_2_5);
ESP_LOGD(TAG, "Received pm_10_0: %u µg/m³", pm_10_0);
if (this->pm_10_0_sensor_ != nullptr)
this->pm_10_0_sensor_->publish_state(pm_10_0);
ESP_LOGD(TAG, "Received Temperature: %.2f °C", temperature);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
ESP_LOGD(TAG, "Received Humidity: %.2f percent", humidity);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity);
}
uint16_t SM300D2Sensor::sm300d2_checksum_(uint8_t *ptr) {
uint8_t sum = 0;
for (int i = 0; i < (SM300D2_RESPONSE_LENGTH - 1); i++) {
sum += *ptr++;
}
return sum;
}
void SM300D2Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "SM300D2:");
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
this->check_uart_settings(9600);
}
} // namespace sm300d2
} // namespace esphome

View file

@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace sm300d2 {
class SM300D2Sensor : public PollingComponent, public uart::UARTDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
void set_formaldehyde_sensor(sensor::Sensor *formaldehyde_sensor) { formaldehyde_sensor_ = formaldehyde_sensor; }
void set_tvoc_sensor(sensor::Sensor *tvoc_sensor) { tvoc_sensor_ = tvoc_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 set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
void update() override;
void dump_config() override;
protected:
uint16_t sm300d2_checksum_(uint8_t *ptr);
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *formaldehyde_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace sm300d2
} // namespace esphome

View file

@ -538,6 +538,7 @@ CONF_TRIGGER_ID = 'trigger_id'
CONF_TRIGGER_PIN = 'trigger_pin' CONF_TRIGGER_PIN = 'trigger_pin'
CONF_TURN_OFF_ACTION = 'turn_off_action' CONF_TURN_OFF_ACTION = 'turn_off_action'
CONF_TURN_ON_ACTION = 'turn_on_action' CONF_TURN_ON_ACTION = 'turn_on_action'
CONF_TVOC = 'tvoc'
CONF_TX_BUFFER_SIZE = 'tx_buffer_size' CONF_TX_BUFFER_SIZE = 'tx_buffer_size'
CONF_TX_PIN = 'tx_pin' CONF_TX_PIN = 'tx_pin'
CONF_TX_POWER = 'tx_power' CONF_TX_POWER = 'tx_power'
@ -595,10 +596,12 @@ ICON_COUNTER = 'mdi:counter'
ICON_CURRENT_AC = 'mdi:current-ac' ICON_CURRENT_AC = 'mdi:current-ac'
ICON_EMPTY = '' ICON_EMPTY = ''
ICON_FLASH = 'mdi:flash' ICON_FLASH = 'mdi:flash'
ICON_FLASK = 'mdi:flask'
ICON_FLASK_OUTLINE = 'mdi:flask-outline' ICON_FLASK_OUTLINE = 'mdi:flask-outline'
ICON_FLOWER = 'mdi:flower' ICON_FLOWER = 'mdi:flower'
ICON_GAS_CYLINDER = 'mdi:gas-cylinder' ICON_GAS_CYLINDER = 'mdi:gas-cylinder'
ICON_GAUGE = 'mdi:gauge' ICON_GAUGE = 'mdi:gauge'
ICON_GRAIN = 'mdi:grain'
ICON_LIGHTBULB = 'mdi:lightbulb' ICON_LIGHTBULB = 'mdi:lightbulb'
ICON_MAGNET = 'mdi:magnet' ICON_MAGNET = 'mdi:magnet'
ICON_MOLECULE_CO2 = 'mdi:molecule-co2' ICON_MOLECULE_CO2 = 'mdi:molecule-co2'

View file

@ -642,6 +642,22 @@ sensor:
co2: co2:
name: 'SenseAir CO2 Value' name: 'SenseAir CO2 Value'
update_interval: 15s update_interval: 15s
- platform: sm300d2
co2:
name: "SM300D2 CO2 Value"
formaldehyde:
name: "SM300D2 Formaldehyde Value"
tvoc:
name: "SM300D2 TVOC Value"
pm_2_5:
name: "SM300D2 PM2.5 Value"
pm_10_0:
name: "SM300D2 PM10 Value"
temperature:
name: "SM300D2 Temperature Value"
humidity:
name: "SM300D2 Humidity Value"
update_interval: 60s
- platform: sht3xd - platform: sht3xd
temperature: temperature:
name: 'Living Room Temperature 8' name: 'Living Room Temperature 8'