mirror of
https://github.com/esphome/esphome.git
synced 2024-10-31 08:25:53 +01:00
Add support for Sensirion SPS30 Particulate Matter sensors (#891)
* Add support for Sensirion SPS30 Particulate Matter sensors * Remove blocking of the main thread on initialization; Improve wording on the debug messages; Add robustness in re-initialization of reconnected or replaced sensors; * Fix code formatting; Co-authored-by: Nad <valordk@github>
This commit is contained in:
parent
0f406c38eb
commit
7f895abc24
0
esphome/components/sps30/__init__.py
Normal file
0
esphome/components/sps30/__init__.py
Normal file
82
esphome/components/sps30/sensor.py
Normal file
82
esphome/components/sps30/sensor.py
Normal file
@ -0,0 +1,82 @@
|
||||
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_1_0, CONF_PM_2_5, CONF_PM_4_0, CONF_PM_10_0, \
|
||||
CONF_PMC_0_5, CONF_PMC_1_0, CONF_PMC_2_5, CONF_PMC_4_0, CONF_PMC_10_0, CONF_PM_SIZE, \
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_METER, UNIT_MICROMETER, \
|
||||
ICON_CHEMICAL_WEAPON, ICON_COUNTER, ICON_RULER
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
sps30_ns = cg.esphome_ns.namespace('sps30')
|
||||
SPS30Component = sps30_ns.class_('SPS30Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(SPS30Component),
|
||||
cv.Optional(CONF_PM_1_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_CHEMICAL_WEAPON, 2),
|
||||
cv.Optional(CONF_PM_2_5): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_CHEMICAL_WEAPON, 2),
|
||||
cv.Optional(CONF_PM_4_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_CHEMICAL_WEAPON, 2),
|
||||
cv.Optional(CONF_PM_10_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_CHEMICAL_WEAPON, 2),
|
||||
cv.Optional(CONF_PMC_0_5): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER,
|
||||
ICON_COUNTER, 2),
|
||||
cv.Optional(CONF_PMC_1_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER,
|
||||
ICON_COUNTER, 2),
|
||||
cv.Optional(CONF_PMC_2_5): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER,
|
||||
ICON_COUNTER, 2),
|
||||
cv.Optional(CONF_PMC_4_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER,
|
||||
ICON_COUNTER, 2),
|
||||
cv.Optional(CONF_PMC_10_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER,
|
||||
ICON_COUNTER, 2),
|
||||
cv.Optional(CONF_PM_SIZE): sensor.sensor_schema(UNIT_MICROMETER,
|
||||
ICON_RULER, 0),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x69))
|
||||
|
||||
|
||||
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_4_0 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PM_4_0])
|
||||
cg.add(var.set_pm_4_0_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_PMC_0_5 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PMC_0_5])
|
||||
cg.add(var.set_pmc_0_5_sensor(sens))
|
||||
|
||||
if CONF_PMC_1_0 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PMC_1_0])
|
||||
cg.add(var.set_pmc_1_0_sensor(sens))
|
||||
|
||||
if CONF_PMC_2_5 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PMC_2_5])
|
||||
cg.add(var.set_pmc_2_5_sensor(sens))
|
||||
|
||||
if CONF_PMC_4_0 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PMC_4_0])
|
||||
cg.add(var.set_pmc_4_0_sensor(sens))
|
||||
|
||||
if CONF_PMC_10_0 in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PMC_10_0])
|
||||
cg.add(var.set_pmc_10_0_sensor(sens))
|
||||
|
||||
if CONF_PM_SIZE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_PM_SIZE])
|
||||
cg.add(var.set_pm_size_sensor(sens))
|
268
esphome/components/sps30/sps30.cpp
Normal file
268
esphome/components/sps30/sps30.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
#include "sps30.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sps30 {
|
||||
|
||||
static const char *TAG = "sps30";
|
||||
|
||||
static const uint16_t SPS30_CMD_GET_ARTICLE_CODE = 0xD025;
|
||||
static const uint16_t SPS30_CMD_GET_SERIAL_NUMBER = 0xD033;
|
||||
static const uint16_t SPS30_CMD_GET_FIRMWARE_VERSION = 0xD100;
|
||||
static const uint16_t SPS30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0010;
|
||||
static const uint16_t SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG = 0x0300;
|
||||
static const uint16_t SPS30_CMD_GET_DATA_READY_STATUS = 0x0202;
|
||||
static const uint16_t SPS30_CMD_READ_MEASUREMENT = 0x0300;
|
||||
static const uint16_t SPS30_CMD_STOP_MEASUREMENTS = 0x0104;
|
||||
static const uint16_t SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS = 0x8004;
|
||||
static const uint16_t SPS30_CMD_START_FAN_CLEANING = 0x5607;
|
||||
static const uint16_t SPS30_CMD_SOFT_RESET = 0xD304;
|
||||
static const size_t SERIAL_NUMBER_LENGTH = 8;
|
||||
static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
|
||||
|
||||
void SPS30Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up sps30...");
|
||||
this->write_command_(SPS30_CMD_SOFT_RESET);
|
||||
/// Deferred Sensor initialization
|
||||
this->set_timeout(500, [this]() {
|
||||
/// Firmware version identification
|
||||
if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) {
|
||||
this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_firmware_version[4];
|
||||
if (!this->read_data_(raw_firmware_version, 4)) {
|
||||
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, " Firmware version v%0d.%02d", (raw_firmware_version[0] >> 8),
|
||||
uint16_t(raw_firmware_version[0] & 0xFF));
|
||||
/// Serial number identification
|
||||
if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) {
|
||||
this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[8];
|
||||
if (!this->read_data_(raw_serial_number, 8)) {
|
||||
this->error_code_ = SERIAL_NUMBER_READ_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
this->serial_number_[i * 2] = static_cast<char>(raw_serial_number[i] >> 8);
|
||||
this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF));
|
||||
}
|
||||
ESP_LOGD(TAG, " Serial Number: '%s'", this->serial_number_);
|
||||
this->start_continuous_measurement_();
|
||||
});
|
||||
}
|
||||
|
||||
void SPS30Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "sps30:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||
break;
|
||||
case MEASUREMENT_INIT_FAILED:
|
||||
ESP_LOGW(TAG, "Measurement Initialization failed!");
|
||||
break;
|
||||
case SERIAL_NUMBER_REQUEST_FAILED:
|
||||
ESP_LOGW(TAG, "Unable to request sensor serial number");
|
||||
break;
|
||||
case SERIAL_NUMBER_READ_FAILED:
|
||||
ESP_LOGW(TAG, "Unable to read sensor serial number");
|
||||
break;
|
||||
case FIRMWARE_VERSION_REQUEST_FAILED:
|
||||
ESP_LOGW(TAG, "Unable to request sensor firmware version");
|
||||
break;
|
||||
case FIRMWARE_VERSION_READ_FAILED:
|
||||
ESP_LOGW(TAG, "Unable to read sensor firmware version");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown setup error!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_);
|
||||
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM4", this->pm_4_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_);
|
||||
}
|
||||
|
||||
void SPS30Component::update() {
|
||||
/// Check if warning flag active (sensor reconnected?)
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGD(TAG, "Trying to reconnect the sensor...");
|
||||
if (this->write_command_(SPS30_CMD_SOFT_RESET)) {
|
||||
ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
|
||||
this->set_timeout(500, [this]() {
|
||||
this->start_continuous_measurement_();
|
||||
/// Sensor restarted and reading attempt made next cycle
|
||||
this->status_clear_warning();
|
||||
this->skipped_data_read_cycles_ = 0;
|
||||
ESP_LOGD(TAG, "Sensor reconnected successfully. Resuming continuous measurement!");
|
||||
});
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Sensor soft-reset failed. Is the sensor offline?");
|
||||
}
|
||||
return;
|
||||
}
|
||||
/// Check if measurement is ready before reading the value
|
||||
if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_read_status[1];
|
||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
||||
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
|
||||
this->skipped_data_read_cycles_++;
|
||||
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
|
||||
/// as failed so that new sensor is eventually forced to be reinitialized for continuous measurement.
|
||||
if (this->skipped_data_read_cycles_ > MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR) {
|
||||
ESP_LOGD(TAG, "Sensor exceeded max allowed attempts. Sensor communication will be reinitialized.");
|
||||
this->status_set_warning();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement status!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[20];
|
||||
if (!this->read_data_(raw_data, 20)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement data!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
union uint32_float_t {
|
||||
uint32_t uint32;
|
||||
float value;
|
||||
};
|
||||
|
||||
/// Reading and converting Mass concentration
|
||||
uint32_float_t pm_1_0{.uint32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])))};
|
||||
uint32_float_t pm_2_5{.uint32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])))};
|
||||
uint32_float_t pm_4_0{.uint32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])))};
|
||||
uint32_float_t pm_10_0{.uint32 = (((uint32_t(raw_data[6])) << 16) | (uint32_t(raw_data[7])))};
|
||||
|
||||
/// Reading and converting Number concentration
|
||||
uint32_float_t pmc_0_5{.uint32 = (((uint32_t(raw_data[8])) << 16) | (uint32_t(raw_data[9])))};
|
||||
uint32_float_t pmc_1_0{.uint32 = (((uint32_t(raw_data[10])) << 16) | (uint32_t(raw_data[11])))};
|
||||
uint32_float_t pmc_2_5{.uint32 = (((uint32_t(raw_data[12])) << 16) | (uint32_t(raw_data[13])))};
|
||||
uint32_float_t pmc_4_0{.uint32 = (((uint32_t(raw_data[14])) << 16) | (uint32_t(raw_data[15])))};
|
||||
uint32_float_t pmc_10_0{.uint32 = (((uint32_t(raw_data[16])) << 16) | (uint32_t(raw_data[17])))};
|
||||
|
||||
/// Reading and converting Typical size
|
||||
uint32_float_t pm_size{.uint32 = (((uint32_t(raw_data[18])) << 16) | (uint32_t(raw_data[19])))};
|
||||
|
||||
if (this->pm_1_0_sensor_ != nullptr)
|
||||
this->pm_1_0_sensor_->publish_state(pm_1_0.value);
|
||||
if (this->pm_2_5_sensor_ != nullptr)
|
||||
this->pm_2_5_sensor_->publish_state(pm_2_5.value);
|
||||
if (this->pm_4_0_sensor_ != nullptr)
|
||||
this->pm_4_0_sensor_->publish_state(pm_4_0.value);
|
||||
if (this->pm_10_0_sensor_ != nullptr)
|
||||
this->pm_10_0_sensor_->publish_state(pm_10_0.value);
|
||||
|
||||
if (this->pmc_0_5_sensor_ != nullptr)
|
||||
this->pmc_0_5_sensor_->publish_state(pmc_0_5.value);
|
||||
if (this->pmc_1_0_sensor_ != nullptr)
|
||||
this->pmc_1_0_sensor_->publish_state(pmc_1_0.value);
|
||||
if (this->pmc_2_5_sensor_ != nullptr)
|
||||
this->pmc_2_5_sensor_->publish_state(pmc_2_5.value);
|
||||
if (this->pmc_4_0_sensor_ != nullptr)
|
||||
this->pmc_4_0_sensor_->publish_state(pmc_4_0.value);
|
||||
if (this->pmc_10_0_sensor_ != nullptr)
|
||||
this->pmc_10_0_sensor_->publish_state(pmc_10_0.value);
|
||||
|
||||
if (this->pm_size_sensor_ != nullptr)
|
||||
this->pm_size_sensor_->publish_state(pm_size.value);
|
||||
|
||||
this->status_clear_warning();
|
||||
this->skipped_data_read_cycles_ = 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool SPS30Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SPS30Component::start_continuous_measurement_() {
|
||||
uint8_t data[4];
|
||||
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
|
||||
data[1] = 0x03;
|
||||
data[2] = 0x00;
|
||||
data[3] = sht_crc_(0x03, 0x00);
|
||||
if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) {
|
||||
ESP_LOGE(TAG, "Error initiating measurements");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPS30Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
auto *buf = new uint8_t[num_bytes];
|
||||
|
||||
if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) {
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
delete[](buf);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
delete[](buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sps30
|
||||
} // namespace esphome
|
62
esphome/components/sps30/sps30.h
Normal file
62
esphome/components/sps30/sps30.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sps30 {
|
||||
|
||||
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
|
||||
/// PM1.0, PM2.5, PM4, PM10 Air Quality sensors.
|
||||
class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
|
||||
void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { pm_4_0_sensor_ = pm_4_0; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; }
|
||||
void set_pmc_0_5_sensor(sensor::Sensor *pmc_0_5) { pmc_0_5_sensor_ = pmc_0_5; }
|
||||
void set_pmc_1_0_sensor(sensor::Sensor *pmc_1_0) { pmc_1_0_sensor_ = pmc_1_0; }
|
||||
void set_pmc_2_5_sensor(sensor::Sensor *pmc_2_5) { pmc_2_5_sensor_ = pmc_2_5; }
|
||||
void set_pmc_4_0_sensor(sensor::Sensor *pmc_4_0) { pmc_4_0_sensor_ = pmc_4_0; }
|
||||
void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; }
|
||||
|
||||
void set_pm_size_sensor(sensor::Sensor *pm_size) { pm_size_sensor_ = pm_size; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
char serial_number_[17] = {0}; /// Terminating NULL character
|
||||
bool start_continuous_measurement_();
|
||||
uint8_t skipped_data_read_cycles_ = 0;
|
||||
|
||||
enum ErrorCode {
|
||||
COMMUNICATION_FAILED,
|
||||
FIRMWARE_VERSION_REQUEST_FAILED,
|
||||
FIRMWARE_VERSION_READ_FAILED,
|
||||
SERIAL_NUMBER_REQUEST_FAILED,
|
||||
SERIAL_NUMBER_READ_FAILED,
|
||||
MEASUREMENT_INIT_FAILED,
|
||||
UNKNOWN
|
||||
} error_code_{UNKNOWN};
|
||||
|
||||
sensor::Sensor *pm_1_0_sensor_{nullptr};
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pm_4_0_sensor_{nullptr};
|
||||
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||
sensor::Sensor *pmc_0_5_sensor_{nullptr};
|
||||
sensor::Sensor *pmc_1_0_sensor_{nullptr};
|
||||
sensor::Sensor *pmc_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pmc_4_0_sensor_{nullptr};
|
||||
sensor::Sensor *pmc_10_0_sensor_{nullptr};
|
||||
sensor::Sensor *pm_size_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace sps30
|
||||
} // namespace esphome
|
@ -329,6 +329,13 @@ CONF_PLATFORMIO_OPTIONS = 'platformio_options'
|
||||
CONF_PM_1_0 = 'pm_1_0'
|
||||
CONF_PM_10_0 = 'pm_10_0'
|
||||
CONF_PM_2_5 = 'pm_2_5'
|
||||
CONF_PM_4_0 = 'pm_4_0'
|
||||
CONF_PM_SIZE = 'pm_size'
|
||||
CONF_PMC_0_5 = 'pmc_0_5'
|
||||
CONF_PMC_1_0 = 'pmc_1_0'
|
||||
CONF_PMC_10_0 = 'pmc_10_0'
|
||||
CONF_PMC_2_5 = 'pmc_2_5'
|
||||
CONF_PMC_4_0 = 'pmc_4_0'
|
||||
CONF_PORT = 'port'
|
||||
CONF_POSITION = 'position'
|
||||
CONF_POSITION_ACTION = 'position_action'
|
||||
@ -503,6 +510,7 @@ ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download'
|
||||
ICON_BRIGHTNESS_5 = 'mdi:brightness-5'
|
||||
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'
|
||||
@ -519,6 +527,7 @@ ICON_PULSE = 'mdi:pulse'
|
||||
ICON_RADIATOR = 'mdi:radiator'
|
||||
ICON_RESTART = 'mdi:restart'
|
||||
ICON_ROTATE_RIGHT = 'mdi:rotate-right'
|
||||
ICON_RULER = 'mdi:ruler'
|
||||
ICON_SCALE = 'mdi:scale'
|
||||
ICON_SCREEN_ROTATION = 'mdi:screen-rotation'
|
||||
ICON_SIGN_DIRECTION = 'mdi:sign-direction'
|
||||
@ -535,6 +544,7 @@ ICON_WIFI = 'mdi:wifi'
|
||||
|
||||
UNIT_AMPERE = 'A'
|
||||
UNIT_CELSIUS = u'°C'
|
||||
UNIT_COUNTS_PER_CUBIC_METER = u'#/m³'
|
||||
UNIT_DECIBEL = 'dB'
|
||||
UNIT_DECIBEL_MILLIWATT = 'dBm'
|
||||
UNIT_DEGREE_PER_SECOND = u'°/s'
|
||||
@ -550,6 +560,7 @@ UNIT_LUX = 'lx'
|
||||
UNIT_METER = 'm'
|
||||
UNIT_METER_PER_SECOND_SQUARED = u'm/s²'
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER = u'µg/m³'
|
||||
UNIT_MICROMETER = 'µm'
|
||||
UNIT_MICROSIEMENS_PER_CENTIMETER = u'µS/cm'
|
||||
UNIT_MICROTESLA = u'µT'
|
||||
UNIT_OHM = u'Ω'
|
||||
|
@ -593,6 +593,36 @@ sensor:
|
||||
accuracy_decimals: 1
|
||||
address: 0x58
|
||||
update_interval: 5s
|
||||
- platform: sps30
|
||||
pm_1_0:
|
||||
name: "Workshop PM <1µm Weight concentration"
|
||||
id: "workshop_PM_1_0"
|
||||
pm_2_5:
|
||||
name: "Workshop PM <2.5µm Weight concentration"
|
||||
id: "workshop_PM_2_5"
|
||||
pm_4_0:
|
||||
name: "Workshop PM <4µm Weight concentration"
|
||||
id: "workshop_PM_4_0"
|
||||
pm_10_0:
|
||||
name: "Workshop PM <10µm Weight concentration"
|
||||
id: "workshop_PM_10_0"
|
||||
pmc_0_5:
|
||||
name: "Workshop PM <0.5µm Number concentration"
|
||||
id: "workshop_PMC_0_5"
|
||||
pmc_1_0:
|
||||
name: "Workshop PM <1µm Number concentration"
|
||||
id: "workshop_PMC_1_0"
|
||||
pmc_2_5:
|
||||
name: "Workshop PM <2.5µm Number concentration"
|
||||
id: "workshop_PMC_2_5"
|
||||
pmc_4_0:
|
||||
name: "Workshop PM <4µm Number concentration"
|
||||
id: "workshop_PMC_4_0"
|
||||
pmc_10_0:
|
||||
name: "Workshop PM <10µm Number concentration"
|
||||
id: "workshop_PMC_10_0"
|
||||
address: 0x69
|
||||
update_interval: 10s
|
||||
- platform: shtcx
|
||||
temperature:
|
||||
name: "Living Room Temperature 10"
|
||||
|
Loading…
Reference in New Issue
Block a user