mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00: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:
		| @@ -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' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user