mirror of
https://github.com/esphome/esphome.git
synced 2024-11-29 02:04:13 +01:00
PMSX003: Add support for specifying the update interval and spinning down (#3053)
Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
782186e13d
commit
98c733108e
4 changed files with 125 additions and 3 deletions
|
@ -49,6 +49,47 @@ void PMSX003Component::set_formaldehyde_sensor(sensor::Sensor *formaldehyde_sens
|
||||||
|
|
||||||
void PMSX003Component::loop() {
|
void PMSX003Component::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
// If we update less often than it takes the device to stabilise, spin the fan down
|
||||||
|
// rather than running it constantly. It does take some time to stabilise, so we
|
||||||
|
// need to keep track of what state we're in.
|
||||||
|
if (this->update_interval_ > PMS_STABILISING_MS) {
|
||||||
|
if (this->initialised_ == 0) {
|
||||||
|
this->send_command_(PMS_CMD_AUTO_MANUAL, 0);
|
||||||
|
this->send_command_(PMS_CMD_ON_STANDBY, 1);
|
||||||
|
this->initialised_ = 1;
|
||||||
|
}
|
||||||
|
switch (this->state_) {
|
||||||
|
case PMSX003_STATE_IDLE:
|
||||||
|
// Power on the sensor now so it'll be ready when we hit the update time
|
||||||
|
if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->state_ = PMSX003_STATE_STABILISING;
|
||||||
|
this->send_command_(PMS_CMD_ON_STANDBY, 1);
|
||||||
|
this->fan_on_time_ = now;
|
||||||
|
return;
|
||||||
|
case PMSX003_STATE_STABILISING:
|
||||||
|
// wait for the sensor to be stable
|
||||||
|
if (now - this->fan_on_time_ < PMS_STABILISING_MS)
|
||||||
|
return;
|
||||||
|
// consume any command responses that are in the serial buffer
|
||||||
|
while (this->available())
|
||||||
|
this->read_byte(&this->data_[0]);
|
||||||
|
// Trigger a new read
|
||||||
|
this->send_command_(PMS_CMD_TRIG_MANUAL, 0);
|
||||||
|
this->state_ = PMSX003_STATE_WAITING;
|
||||||
|
break;
|
||||||
|
case PMSX003_STATE_WAITING:
|
||||||
|
// Just go ahead and read stuff
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (now - this->last_update_ < this->update_interval_) {
|
||||||
|
// Otherwise just leave the sensor powered up and come back when we hit the update
|
||||||
|
// time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (now - this->last_transmission_ >= 500) {
|
if (now - this->last_transmission_ >= 500) {
|
||||||
// last transmission too long ago. Reset RX index.
|
// last transmission too long ago. Reset RX index.
|
||||||
this->data_index_ = 0;
|
this->data_index_ = 0;
|
||||||
|
@ -65,6 +106,7 @@ void PMSX003Component::loop() {
|
||||||
// finished
|
// finished
|
||||||
this->parse_data_();
|
this->parse_data_();
|
||||||
this->data_index_ = 0;
|
this->data_index_ = 0;
|
||||||
|
this->last_update_ = now;
|
||||||
} else if (!*check) {
|
} else if (!*check) {
|
||||||
// wrong data
|
// wrong data
|
||||||
this->data_index_ = 0;
|
this->data_index_ = 0;
|
||||||
|
@ -131,6 +173,25 @@ optional<bool> PMSX003Component::check_byte_() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) {
|
||||||
|
this->data_index_ = 0;
|
||||||
|
this->data_[data_index_++] = 0x42;
|
||||||
|
this->data_[data_index_++] = 0x4D;
|
||||||
|
this->data_[data_index_++] = cmd;
|
||||||
|
this->data_[data_index_++] = (data >> 8) & 0xFF;
|
||||||
|
this->data_[data_index_++] = (data >> 0) & 0xFF;
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < data_index_; i++) {
|
||||||
|
sum += this->data_[i];
|
||||||
|
}
|
||||||
|
this->data_[data_index_++] = (sum >> 8) & 0xFF;
|
||||||
|
this->data_[data_index_++] = (sum >> 0) & 0xFF;
|
||||||
|
for (int i = 0; i < data_index_; i++) {
|
||||||
|
this->write_byte(this->data_[i]);
|
||||||
|
}
|
||||||
|
this->data_index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void PMSX003Component::parse_data_() {
|
void PMSX003Component::parse_data_() {
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
case PMSX003_TYPE_5003ST: {
|
case PMSX003_TYPE_5003ST: {
|
||||||
|
@ -218,6 +279,13 @@ void PMSX003Component::parse_data_() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spin down the sensor again if we aren't going to need it until more time has
|
||||||
|
// passed than it takes to stabilise
|
||||||
|
if (this->update_interval_ > PMS_STABILISING_MS) {
|
||||||
|
this->send_command_(PMS_CMD_ON_STANDBY, 0);
|
||||||
|
this->state_ = PMSX003_STATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) {
|
uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) {
|
||||||
|
|
|
@ -7,6 +7,13 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace pmsx003 {
|
namespace pmsx003 {
|
||||||
|
|
||||||
|
// known command bytes
|
||||||
|
#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically
|
||||||
|
#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement
|
||||||
|
#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode
|
||||||
|
|
||||||
|
static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on
|
||||||
|
|
||||||
enum PMSX003Type {
|
enum PMSX003Type {
|
||||||
PMSX003_TYPE_X003 = 0,
|
PMSX003_TYPE_X003 = 0,
|
||||||
PMSX003_TYPE_5003T,
|
PMSX003_TYPE_5003T,
|
||||||
|
@ -14,6 +21,12 @@ enum PMSX003Type {
|
||||||
PMSX003_TYPE_5003S,
|
PMSX003_TYPE_5003S,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PMSX003State {
|
||||||
|
PMSX003_STATE_IDLE = 0,
|
||||||
|
PMSX003_STATE_STABILISING,
|
||||||
|
PMSX003_STATE_WAITING,
|
||||||
|
};
|
||||||
|
|
||||||
class PMSX003Component : public uart::UARTDevice, public Component {
|
class PMSX003Component : public uart::UARTDevice, public Component {
|
||||||
public:
|
public:
|
||||||
PMSX003Component() = default;
|
PMSX003Component() = default;
|
||||||
|
@ -23,6 +36,8 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
||||||
|
|
||||||
void set_type(PMSX003Type type) { type_ = type; }
|
void set_type(PMSX003Type type) { type_ = type; }
|
||||||
|
|
||||||
|
void set_update_interval(uint32_t val) { update_interval_ = val; };
|
||||||
|
|
||||||
void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor);
|
void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor);
|
||||||
void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor);
|
void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor);
|
||||||
void set_pm_10_0_std_sensor(sensor::Sensor *pm_10_0_std_sensor);
|
void set_pm_10_0_std_sensor(sensor::Sensor *pm_10_0_std_sensor);
|
||||||
|
@ -45,11 +60,17 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
||||||
protected:
|
protected:
|
||||||
optional<bool> check_byte_();
|
optional<bool> check_byte_();
|
||||||
void parse_data_();
|
void parse_data_();
|
||||||
|
void send_command_(uint8_t cmd, uint16_t data);
|
||||||
uint16_t get_16_bit_uint_(uint8_t start_index);
|
uint16_t get_16_bit_uint_(uint8_t start_index);
|
||||||
|
|
||||||
uint8_t data_[64];
|
uint8_t data_[64];
|
||||||
uint8_t data_index_{0};
|
uint8_t data_index_{0};
|
||||||
|
uint8_t initialised_{0};
|
||||||
|
uint32_t fan_on_time_{0};
|
||||||
|
uint32_t last_update_{0};
|
||||||
uint32_t last_transmission_{0};
|
uint32_t last_transmission_{0};
|
||||||
|
uint32_t update_interval_{0};
|
||||||
|
PMSX003State state_{PMSX003_STATE_IDLE};
|
||||||
PMSX003Type type_;
|
PMSX003Type type_;
|
||||||
|
|
||||||
// "Standard Particle"
|
// "Standard Particle"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, uart
|
from esphome.components import sensor, uart
|
||||||
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_FORMALDEHYDE,
|
CONF_FORMALDEHYDE,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
|
@ -17,6 +18,7 @@ from esphome.const import (
|
||||||
CONF_PM_2_5UM,
|
CONF_PM_2_5UM,
|
||||||
CONF_PM_5_0UM,
|
CONF_PM_5_0UM,
|
||||||
CONF_PM_10_0UM,
|
CONF_PM_10_0UM,
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
DEVICE_CLASS_PM1,
|
DEVICE_CLASS_PM1,
|
||||||
|
@ -44,6 +46,7 @@ TYPE_PMS5003ST = "PMS5003ST"
|
||||||
TYPE_PMS5003S = "PMS5003S"
|
TYPE_PMS5003S = "PMS5003S"
|
||||||
|
|
||||||
PMSX003Type = pmsx003_ns.enum("PMSX003Type")
|
PMSX003Type = pmsx003_ns.enum("PMSX003Type")
|
||||||
|
|
||||||
PMSX003_TYPES = {
|
PMSX003_TYPES = {
|
||||||
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
|
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
|
||||||
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
|
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
|
||||||
|
@ -68,6 +71,17 @@ def validate_pmsx003_sensors(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def validate_update_interval(value):
|
||||||
|
value = cv.positive_time_period_milliseconds(value)
|
||||||
|
if value == cv.time_period("0s"):
|
||||||
|
return value
|
||||||
|
if value < cv.time_period("30s"):
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Update interval must be greater than or equal to 30 seconds if set."
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
@ -157,6 +171,7 @@ CONFIG_SCHEMA = (
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
@ -164,6 +179,17 @@ CONFIG_SCHEMA = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def final_validate(config):
|
||||||
|
require_tx = config[CONF_UPDATE_INTERVAL] > cv.time_period("0s")
|
||||||
|
schema = uart.final_validate_device_schema(
|
||||||
|
"pmsx003", baud_rate=9600, require_rx=True, require_tx=require_tx
|
||||||
|
)
|
||||||
|
schema(config)
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = final_validate
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
@ -230,3 +256,5 @@ async def to_code(config):
|
||||||
if CONF_FORMALDEHYDE in config:
|
if CONF_FORMALDEHYDE in config:
|
||||||
sens = await sensor.new_sensor(config[CONF_FORMALDEHYDE])
|
sens = await sensor.new_sensor(config[CONF_FORMALDEHYDE])
|
||||||
cg.add(var.set_formaldehyde_sensor(sens))
|
cg.add(var.set_formaldehyde_sensor(sens))
|
||||||
|
|
||||||
|
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
|
|
@ -266,6 +266,10 @@ uart:
|
||||||
stop_bits: 2
|
stop_bits: 2
|
||||||
# Specifically added for testing debug with no options at all.
|
# Specifically added for testing debug with no options at all.
|
||||||
debug:
|
debug:
|
||||||
|
- id: uart8
|
||||||
|
tx_pin: GPIO4
|
||||||
|
rx_pin: GPIO5
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
modbus:
|
modbus:
|
||||||
uart_id: uart1
|
uart_id: uart1
|
||||||
|
@ -559,7 +563,7 @@ sensor:
|
||||||
name: 'AQI'
|
name: 'AQI'
|
||||||
calculation_type: 'AQI'
|
calculation_type: 'AQI'
|
||||||
- platform: pmsx003
|
- platform: pmsx003
|
||||||
uart_id: uart2
|
uart_id: uart8
|
||||||
type: PMSX003
|
type: PMSX003
|
||||||
pm_1_0:
|
pm_1_0:
|
||||||
name: 'PM 1.0 Concentration'
|
name: 'PM 1.0 Concentration'
|
||||||
|
@ -585,8 +589,9 @@ sensor:
|
||||||
name: 'Particulate Count >5.0um'
|
name: 'Particulate Count >5.0um'
|
||||||
pm_10_0um:
|
pm_10_0um:
|
||||||
name: 'Particulate Count >10.0um'
|
name: 'Particulate Count >10.0um'
|
||||||
|
update_interval: 30s
|
||||||
- platform: pmsx003
|
- platform: pmsx003
|
||||||
uart_id: uart2
|
uart_id: uart5
|
||||||
type: PMS5003T
|
type: PMS5003T
|
||||||
pm_2_5:
|
pm_2_5:
|
||||||
name: 'PM 2.5 Concentration'
|
name: 'PM 2.5 Concentration'
|
||||||
|
@ -595,7 +600,7 @@ sensor:
|
||||||
humidity:
|
humidity:
|
||||||
name: 'PMS Humidity'
|
name: 'PMS Humidity'
|
||||||
- platform: pmsx003
|
- platform: pmsx003
|
||||||
uart_id: uart2
|
uart_id: uart6
|
||||||
type: PMS5003ST
|
type: PMS5003ST
|
||||||
pm_1_0:
|
pm_1_0:
|
||||||
name: 'PM 1.0 Concentration'
|
name: 'PM 1.0 Concentration'
|
||||||
|
|
Loading…
Reference in a new issue