mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +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/pipsolar/* @andreashergert1984 | ||||
| esphome/components/pm1006/* @habbie | ||||
| esphome/components/pm2005/* @andrewjswan | ||||
| esphome/components/pmsa003i/* @sjtrny | ||||
| esphome/components/pmwcs3/* @SeByDocKy | ||||
| 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