mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add CUBIC PM2005/PM2105 Laser Particle Sensor Module (#8292)
Co-authored-by: Djordje <6750655+DjordjeMandic@users.noreply.github.com>
This commit is contained in:
		| @@ -324,6 +324,7 @@ esphome/components/pcf8563/* @KoenBreeman | |||||||
| esphome/components/pid/* @OttoWinter | esphome/components/pid/* @OttoWinter | ||||||
| esphome/components/pipsolar/* @andreashergert1984 | esphome/components/pipsolar/* @andreashergert1984 | ||||||
| esphome/components/pm1006/* @habbie | esphome/components/pm1006/* @habbie | ||||||
|  | esphome/components/pm2005/* @andrewjswan | ||||||
| esphome/components/pmsa003i/* @sjtrny | esphome/components/pmsa003i/* @sjtrny | ||||||
| esphome/components/pmwcs3/* @SeByDocKy | esphome/components/pmwcs3/* @SeByDocKy | ||||||
| esphome/components/pn532/* @OttoWinter @jesserockz | esphome/components/pn532/* @OttoWinter @jesserockz | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/pm2005/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/pm2005/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | """PM2005/2105 component for ESPHome.""" | ||||||
							
								
								
									
										123
									
								
								esphome/components/pm2005/pm2005.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/pm2005/pm2005.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "pm2005.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pm2005 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "pm2005"; | ||||||
|  |  | ||||||
|  | // Converts a sensor situation to a human readable string | ||||||
|  | static const LogString *pm2005_get_situation_string(int status) { | ||||||
|  |   switch (status) { | ||||||
|  |     case 1: | ||||||
|  |       return LOG_STR("Close"); | ||||||
|  |     case 2: | ||||||
|  |       return LOG_STR("Malfunction"); | ||||||
|  |     case 3: | ||||||
|  |       return LOG_STR("Under detecting"); | ||||||
|  |     case 0x80: | ||||||
|  |       return LOG_STR("Detecting completed"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR("Invalid"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Converts a sensor measuring mode to a human readable string | ||||||
|  | static const LogString *pm2005_get_measuring_mode_string(int status) { | ||||||
|  |   switch (status) { | ||||||
|  |     case 2: | ||||||
|  |       return LOG_STR("Single"); | ||||||
|  |     case 3: | ||||||
|  |       return LOG_STR("Continuous"); | ||||||
|  |     case 5: | ||||||
|  |       return LOG_STR("Dynamic"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR("Timing"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline uint16_t get_sensor_value(const uint8_t *data, uint8_t i) { return data[i] * 0x100 + data[i + 1]; } | ||||||
|  |  | ||||||
|  | void PM2005Component::setup() { | ||||||
|  |   if (this->sensor_type_ == PM2005) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "Setting up PM2005..."); | ||||||
|  |  | ||||||
|  |     this->situation_value_index_ = 3; | ||||||
|  |     this->pm_1_0_value_index_ = 4; | ||||||
|  |     this->pm_2_5_value_index_ = 6; | ||||||
|  |     this->pm_10_0_value_index_ = 8; | ||||||
|  |     this->measuring_value_index_ = 10; | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGCONFIG(TAG, "Setting up PM2105..."); | ||||||
|  |  | ||||||
|  |     this->situation_value_index_ = 2; | ||||||
|  |     this->pm_1_0_value_index_ = 3; | ||||||
|  |     this->pm_2_5_value_index_ = 5; | ||||||
|  |     this->pm_10_0_value_index_ = 7; | ||||||
|  |     this->measuring_value_index_ = 9; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Communication failed!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PM2005Component::update() { | ||||||
|  |   if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Read result failed."); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->sensor_situation_ == this->data_buffer_[this->situation_value_index_]) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->sensor_situation_ = this->data_buffer_[this->situation_value_index_]; | ||||||
|  |   ESP_LOGD(TAG, "Sensor situation: %s.", LOG_STR_ARG(pm2005_get_situation_string(this->sensor_situation_))); | ||||||
|  |   if (this->sensor_situation_ == 2) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->sensor_situation_ != 0x80) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t pm1 = get_sensor_value(this->data_buffer_, this->pm_1_0_value_index_); | ||||||
|  |   uint16_t pm25 = get_sensor_value(this->data_buffer_, this->pm_2_5_value_index_); | ||||||
|  |   uint16_t pm10 = get_sensor_value(this->data_buffer_, this->pm_10_0_value_index_); | ||||||
|  |   uint16_t sensor_measuring_mode = get_sensor_value(this->data_buffer_, this->measuring_value_index_); | ||||||
|  |   ESP_LOGD(TAG, "PM1.0: %d, PM2.5: %d, PM10: %d, Measuring mode: %s.", pm1, pm25, pm10, | ||||||
|  |            LOG_STR_ARG(pm2005_get_measuring_mode_string(sensor_measuring_mode))); | ||||||
|  |  | ||||||
|  |   if (this->pm_1_0_sensor_ != nullptr) { | ||||||
|  |     this->pm_1_0_sensor_->publish_state(pm1); | ||||||
|  |   } | ||||||
|  |   if (this->pm_2_5_sensor_ != nullptr) { | ||||||
|  |     this->pm_2_5_sensor_->publish_state(pm25); | ||||||
|  |   } | ||||||
|  |   if (this->pm_10_0_sensor_ != nullptr) { | ||||||
|  |     this->pm_10_0_sensor_->publish_state(pm10); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PM2005Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "PM2005:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Type: PM2%u05", this->sensor_type_ == PM2105); | ||||||
|  |  | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with PM2%u05 failed!", this->sensor_type_ == PM2105); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "PM1.0", this->pm_1_0_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "PM2.5", this->pm_2_5_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "PM10 ", this->pm_10_0_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace pm2005 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										46
									
								
								esphome/components/pm2005/pm2005.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/pm2005/pm2005.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pm2005 { | ||||||
|  |  | ||||||
|  | enum SensorType { | ||||||
|  |   PM2005, | ||||||
|  |   PM2105, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class PM2005Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   float get_setup_priority() const override { return esphome::setup_priority::DATA; } | ||||||
|  |  | ||||||
|  |   void set_sensor_type(SensorType sensor_type) { this->sensor_type_ = sensor_type; } | ||||||
|  |  | ||||||
|  |   void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { this->pm_1_0_sensor_ = pm_1_0_sensor; } | ||||||
|  |   void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { this->pm_2_5_sensor_ = pm_2_5_sensor; } | ||||||
|  |   void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { this->pm_10_0_sensor_ = pm_10_0_sensor; } | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t sensor_situation_{0}; | ||||||
|  |   uint8_t data_buffer_[12]; | ||||||
|  |   SensorType sensor_type_{PM2005}; | ||||||
|  |  | ||||||
|  |   sensor::Sensor *pm_1_0_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *pm_2_5_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *pm_10_0_sensor_{nullptr}; | ||||||
|  |  | ||||||
|  |   uint8_t situation_value_index_{3}; | ||||||
|  |   uint8_t pm_1_0_value_index_{4}; | ||||||
|  |   uint8_t pm_2_5_value_index_{6}; | ||||||
|  |   uint8_t pm_10_0_value_index_{8}; | ||||||
|  |   uint8_t measuring_value_index_{10}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace pm2005 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										86
									
								
								esphome/components/pm2005/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								esphome/components/pm2005/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | """PM2005/2105 Sensor component for ESPHome.""" | ||||||
|  |  | ||||||
|  | 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_10_0, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     DEVICE_CLASS_PM1, | ||||||
|  |     DEVICE_CLASS_PM10, | ||||||
|  |     DEVICE_CLASS_PM25, | ||||||
|  |     ICON_CHEMICAL_WEAPON, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | CODEOWNERS = ["@andrewjswan"] | ||||||
|  |  | ||||||
|  | pm2005_ns = cg.esphome_ns.namespace("pm2005") | ||||||
|  | PM2005Component = pm2005_ns.class_( | ||||||
|  |     "PM2005Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | SensorType = pm2005_ns.enum("SensorType") | ||||||
|  | SENSOR_TYPE = { | ||||||
|  |     "PM2005": SensorType.PM2005, | ||||||
|  |     "PM2105": SensorType.PM2105, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(PM2005Component), | ||||||
|  |             cv.Optional(CONF_TYPE, default="PM2005"): cv.enum(SENSOR_TYPE, upper=True), | ||||||
|  |             cv.Optional(CONF_PM_1_0): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||||
|  |                 icon=ICON_CHEMICAL_WEAPON, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_PM1, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_PM_2_5): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||||
|  |                 icon=ICON_CHEMICAL_WEAPON, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_PM25, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_PM_10_0): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||||
|  |                 icon=ICON_CHEMICAL_WEAPON, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_PM10, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x28)), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config) -> None: | ||||||
|  |     """Code generation entry point.""" | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_sensor_type(config[CONF_TYPE])) | ||||||
|  |  | ||||||
|  |     if pm_1_0_config := config.get(CONF_PM_1_0): | ||||||
|  |         sens = await sensor.new_sensor(pm_1_0_config) | ||||||
|  |         cg.add(var.set_pm_1_0_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if pm_2_5_config := config.get(CONF_PM_2_5): | ||||||
|  |         sens = await sensor.new_sensor(pm_2_5_config) | ||||||
|  |         cg.add(var.set_pm_2_5_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if pm_10_0_config := config.get(CONF_PM_10_0): | ||||||
|  |         sens = await sensor.new_sensor(pm_10_0_config) | ||||||
|  |         cg.add(var.set_pm_10_0_sensor(sens)) | ||||||
							
								
								
									
										13
									
								
								tests/components/pm2005/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/components/pm2005/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | i2c: | ||||||
|  |   - id: i2c_pm2005 | ||||||
|  |     scl: ${scl_pin} | ||||||
|  |     sda: ${sda_pin} | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: pm2005 | ||||||
|  |     pm_1_0: | ||||||
|  |       name: PM1.0 | ||||||
|  |     pm_2_5: | ||||||
|  |       name: PM2.5 | ||||||
|  |     pm_10_0: | ||||||
|  |       name: PM10.0 | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pm2005/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pm2005/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user