mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +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() { | ||||
|   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) { | ||||
|     // last transmission too long ago. Reset RX index. | ||||
|     this->data_index_ = 0; | ||||
| @@ -65,6 +106,7 @@ void PMSX003Component::loop() { | ||||
|       // finished | ||||
|       this->parse_data_(); | ||||
|       this->data_index_ = 0; | ||||
|       this->last_update_ = now; | ||||
|     } else if (!*check) { | ||||
|       // wrong data | ||||
|       this->data_index_ = 0; | ||||
| @@ -131,6 +173,25 @@ optional<bool> PMSX003Component::check_byte_() { | ||||
|   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_() { | ||||
|   switch (this->type_) { | ||||
|     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(); | ||||
| } | ||||
| uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) { | ||||
|   | ||||
| @@ -7,6 +7,13 @@ | ||||
| namespace esphome { | ||||
| 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 { | ||||
|   PMSX003_TYPE_X003 = 0, | ||||
|   PMSX003_TYPE_5003T, | ||||
| @@ -14,6 +21,12 @@ enum PMSX003Type { | ||||
|   PMSX003_TYPE_5003S, | ||||
| }; | ||||
|  | ||||
| enum PMSX003State { | ||||
|   PMSX003_STATE_IDLE = 0, | ||||
|   PMSX003_STATE_STABILISING, | ||||
|   PMSX003_STATE_WAITING, | ||||
| }; | ||||
|  | ||||
| class PMSX003Component : public uart::UARTDevice, public Component { | ||||
|  public: | ||||
|   PMSX003Component() = default; | ||||
| @@ -23,6 +36,8 @@ class PMSX003Component : public uart::UARTDevice, public Component { | ||||
|  | ||||
|   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_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); | ||||
| @@ -45,11 +60,17 @@ class PMSX003Component : public uart::UARTDevice, public Component { | ||||
|  protected: | ||||
|   optional<bool> check_byte_(); | ||||
|   void parse_data_(); | ||||
|   void send_command_(uint8_t cmd, uint16_t data); | ||||
|   uint16_t get_16_bit_uint_(uint8_t start_index); | ||||
|  | ||||
|   uint8_t data_[64]; | ||||
|   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 update_interval_{0}; | ||||
|   PMSX003State state_{PMSX003_STATE_IDLE}; | ||||
|   PMSX003Type type_; | ||||
|  | ||||
|   // "Standard Particle" | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
|  | ||||
| from esphome.const import ( | ||||
|     CONF_FORMALDEHYDE, | ||||
|     CONF_HUMIDITY, | ||||
| @@ -17,6 +18,7 @@ from esphome.const import ( | ||||
|     CONF_PM_2_5UM, | ||||
|     CONF_PM_5_0UM, | ||||
|     CONF_PM_10_0UM, | ||||
|     CONF_UPDATE_INTERVAL, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_TYPE, | ||||
|     DEVICE_CLASS_PM1, | ||||
| @@ -44,6 +46,7 @@ TYPE_PMS5003ST = "PMS5003ST" | ||||
| TYPE_PMS5003S = "PMS5003S" | ||||
|  | ||||
| PMSX003Type = pmsx003_ns.enum("PMSX003Type") | ||||
|  | ||||
| PMSX003_TYPES = { | ||||
|     TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, | ||||
|     TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, | ||||
| @@ -68,6 +71,17 @@ def validate_pmsx003_sensors(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 = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
| @@ -157,6 +171,7 @@ CONFIG_SCHEMA = ( | ||||
|                 accuracy_decimals=0, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval, | ||||
|         } | ||||
|     ) | ||||
|     .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): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
| @@ -230,3 +256,5 @@ async def to_code(config): | ||||
|     if CONF_FORMALDEHYDE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_FORMALDEHYDE]) | ||||
|         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 | ||||
|     # Specifically added for testing debug with no options at all. | ||||
|     debug: | ||||
|   - id: uart8 | ||||
|     tx_pin: GPIO4 | ||||
|     rx_pin: GPIO5 | ||||
|     baud_rate: 9600 | ||||
|  | ||||
| modbus: | ||||
|   uart_id: uart1 | ||||
| @@ -559,7 +563,7 @@ sensor: | ||||
|       name: 'AQI' | ||||
|       calculation_type: 'AQI' | ||||
|   - platform: pmsx003 | ||||
|     uart_id: uart2 | ||||
|     uart_id: uart8 | ||||
|     type: PMSX003 | ||||
|     pm_1_0: | ||||
|       name: 'PM 1.0 Concentration' | ||||
| @@ -585,8 +589,9 @@ sensor: | ||||
|       name: 'Particulate Count >5.0um' | ||||
|     pm_10_0um: | ||||
|       name: 'Particulate Count >10.0um' | ||||
|     update_interval: 30s | ||||
|   - platform: pmsx003 | ||||
|     uart_id: uart2 | ||||
|     uart_id: uart5 | ||||
|     type: PMS5003T | ||||
|     pm_2_5: | ||||
|       name: 'PM 2.5 Concentration' | ||||
| @@ -595,7 +600,7 @@ sensor: | ||||
|     humidity: | ||||
|       name: 'PMS Humidity' | ||||
|   - platform: pmsx003 | ||||
|     uart_id: uart2 | ||||
|     uart_id: uart6 | ||||
|     type: PMS5003ST | ||||
|     pm_1_0: | ||||
|       name: 'PM 1.0 Concentration' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user