mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add support for PMSA003i (#1501)
Co-authored-by: Otto Winter <otto@otto-winter.com> Co-authored-by: steve <steve@Hackintosh.local> Co-authored-by: Otto winter <otto@otto-winter.com>
This commit is contained in:
		| @@ -86,6 +86,7 @@ esphome/components/number/* @esphome/core | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pid/* @OttoWinter | ||||
| esphome/components/pmsa003i/* @sjtrny | ||||
| esphome/components/pn532/* @OttoWinter @jesserockz | ||||
| esphome/components/pn532_i2c/* @OttoWinter @jesserockz | ||||
| esphome/components/pn532_spi/* @OttoWinter @jesserockz | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/pmsa003i/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/pmsa003i/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										100
									
								
								esphome/components/pmsa003i/pmsa003i.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								esphome/components/pmsa003i/pmsa003i.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| #include "pmsa003i.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pmsa003i { | ||||
|  | ||||
| static const char *const TAG = "pmsa003i"; | ||||
|  | ||||
| void PMSA003IComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up pmsa003i..."); | ||||
|  | ||||
|   PM25AQIData data; | ||||
|   bool successful_read = this->read_data_(&data); | ||||
|  | ||||
|   if (!successful_read) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void PMSA003IComponent::dump_config() { LOG_I2C_DEVICE(this); } | ||||
|  | ||||
| void PMSA003IComponent::update() { | ||||
|   PM25AQIData data; | ||||
|  | ||||
|   bool successful_read = this->read_data_(&data); | ||||
|  | ||||
|   // Update sensors | ||||
|   if (successful_read) { | ||||
|     this->status_clear_warning(); | ||||
|     ESP_LOGV(TAG, "Read success. Updating sensors."); | ||||
|  | ||||
|     if (this->standard_units_) { | ||||
|       if (this->pm_1_0_sensor_ != nullptr) | ||||
|         this->pm_1_0_sensor_->publish_state(data.pm10_standard); | ||||
|       if (this->pm_2_5_sensor_ != nullptr) | ||||
|         this->pm_2_5_sensor_->publish_state(data.pm25_standard); | ||||
|       if (this->pm_10_0_sensor_ != nullptr) | ||||
|         this->pm_10_0_sensor_->publish_state(data.pm100_standard); | ||||
|     } else { | ||||
|       if (this->pm_1_0_sensor_ != nullptr) | ||||
|         this->pm_1_0_sensor_->publish_state(data.pm10_env); | ||||
|       if (this->pm_2_5_sensor_ != nullptr) | ||||
|         this->pm_2_5_sensor_->publish_state(data.pm25_env); | ||||
|       if (this->pm_10_0_sensor_ != nullptr) | ||||
|         this->pm_10_0_sensor_->publish_state(data.pm100_env); | ||||
|     } | ||||
|  | ||||
|     if (this->pmc_0_3_sensor_ != nullptr) | ||||
|       this->pmc_0_3_sensor_->publish_state(data.particles_03um); | ||||
|     if (this->pmc_0_5_sensor_ != nullptr) | ||||
|       this->pmc_0_5_sensor_->publish_state(data.particles_05um); | ||||
|     if (this->pmc_1_0_sensor_ != nullptr) | ||||
|       this->pmc_1_0_sensor_->publish_state(data.particles_10um); | ||||
|     if (this->pmc_2_5_sensor_ != nullptr) | ||||
|       this->pmc_2_5_sensor_->publish_state(data.particles_25um); | ||||
|     if (this->pmc_5_0_sensor_ != nullptr) | ||||
|       this->pmc_5_0_sensor_->publish_state(data.particles_50um); | ||||
|     if (this->pmc_10_0_sensor_ != nullptr) | ||||
|       this->pmc_10_0_sensor_->publish_state(data.particles_100um); | ||||
|   } else { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGV(TAG, "Read failure. Skipping update."); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool PMSA003IComponent::read_data_(PM25AQIData *data) { | ||||
|   const uint8_t num_bytes = 32; | ||||
|   uint8_t buffer[num_bytes]; | ||||
|  | ||||
|   this->read_bytes_raw(buffer, num_bytes); | ||||
|  | ||||
|   // https://github.com/adafruit/Adafruit_PM25AQI | ||||
|  | ||||
|   // Check that start byte is correct! | ||||
|   if (buffer[0] != 0x42) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // get checksum ready | ||||
|   int16_t sum = 0; | ||||
|   for (uint8_t i = 0; i < 30; i++) { | ||||
|     sum += buffer[i]; | ||||
|   } | ||||
|  | ||||
|   // The data comes in endian'd, this solves it so it works on all platforms | ||||
|   uint16_t buffer_u16[15]; | ||||
|   for (uint8_t i = 0; i < 15; i++) { | ||||
|     buffer_u16[i] = buffer[2 + i * 2 + 1]; | ||||
|     buffer_u16[i] += (buffer[2 + i * 2] << 8); | ||||
|   } | ||||
|  | ||||
|   // put it into a nice struct :) | ||||
|   memcpy((void *) data, (void *) buffer_u16, 30); | ||||
|  | ||||
|   return (sum == data->checksum); | ||||
| } | ||||
|  | ||||
| }  // namespace pmsa003i | ||||
| }  // namespace esphome | ||||
							
								
								
									
										68
									
								
								esphome/components/pmsa003i/pmsa003i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/pmsa003i/pmsa003i.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pmsa003i { | ||||
|  | ||||
| /**! Structure holding Plantower's standard packet **/ | ||||
| // From https://github.com/adafruit/Adafruit_PM25AQI | ||||
| struct PM25AQIData { | ||||
|   uint16_t framelen;        ///< How long this data chunk is | ||||
|   uint16_t pm10_standard,   ///< Standard PM1.0 | ||||
|       pm25_standard,        ///< Standard PM2.5 | ||||
|       pm100_standard;       ///< Standard PM10.0 | ||||
|   uint16_t pm10_env,        ///< Environmental PM1.0 | ||||
|       pm25_env,             ///< Environmental PM2.5 | ||||
|       pm100_env;            ///< Environmental PM10.0 | ||||
|   uint16_t particles_03um,  ///< 0.3um Particle Count | ||||
|       particles_05um,       ///< 0.5um Particle Count | ||||
|       particles_10um,       ///< 1.0um Particle Count | ||||
|       particles_25um,       ///< 2.5um Particle Count | ||||
|       particles_50um,       ///< 5.0um Particle Count | ||||
|       particles_100um;      ///< 10.0um Particle Count | ||||
|   uint16_t unused;          ///< Unused | ||||
|   uint16_t checksum;        ///< Packet checksum | ||||
| }; | ||||
|  | ||||
| class PMSA003IComponent : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_standard_units(bool standard_units) { standard_units_ = standard_units; } | ||||
|  | ||||
|   void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } | ||||
|   void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } | ||||
|   void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; } | ||||
|  | ||||
|   void set_pmc_0_3_sensor(sensor::Sensor *pmc_0_3) { pmc_0_3_sensor_ = pmc_0_3; } | ||||
|   void set_pmc_0_5_sensor(sensor::Sensor *pmc_0_5) { pmc_0_5_sensor_ = pmc_0_5; } | ||||
|   void set_pmc_1_0_sensor(sensor::Sensor *pmc_1_0) { pmc_1_0_sensor_ = pmc_1_0; } | ||||
|   void set_pmc_2_5_sensor(sensor::Sensor *pmc_2_5) { pmc_2_5_sensor_ = pmc_2_5; } | ||||
|   void set_pmc_5_0_sensor(sensor::Sensor *pmc_5_0) { pmc_5_0_sensor_ = pmc_5_0; } | ||||
|   void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; } | ||||
|  | ||||
|  protected: | ||||
|   bool read_data_(PM25AQIData *data); | ||||
|  | ||||
|   bool standard_units_; | ||||
|  | ||||
|   sensor::Sensor *pm_1_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_2_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_10_0_sensor_{nullptr}; | ||||
|  | ||||
|   sensor::Sensor *pmc_0_3_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_0_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_1_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_2_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_5_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_10_0_sensor_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace pmsa003i | ||||
| }  // namespace esphome | ||||
							
								
								
									
										104
									
								
								esphome/components/pmsa003i/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								esphome/components/pmsa003i/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| 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_PMC_0_5, | ||||
|     CONF_PMC_1_0, | ||||
|     CONF_PMC_2_5, | ||||
|     CONF_PMC_10_0, | ||||
|     UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|     ICON_CHEMICAL_WEAPON, | ||||
|     ICON_COUNTER, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@sjtrny"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| pmsa003i_ns = cg.esphome_ns.namespace("pmsa003i") | ||||
|  | ||||
| PMSA003IComponent = pmsa003i_ns.class_( | ||||
|     "PMSA003IComponent", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONF_STANDARD_UNITS = "standard_units" | ||||
| UNIT_COUNTS_PER_100ML = "#/0.1L" | ||||
| CONF_PMC_0_3 = "pmc_0_3" | ||||
| CONF_PMC_5_0 = "pmc_5_0" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(PMSA003IComponent), | ||||
|             cv.Optional(CONF_STANDARD_UNITS, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_PM_1_0): sensor.sensor_schema( | ||||
|                 UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|                 ICON_CHEMICAL_WEAPON, | ||||
|                 2, | ||||
|                 DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_PM_2_5): sensor.sensor_schema( | ||||
|                 UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|                 ICON_CHEMICAL_WEAPON, | ||||
|                 2, | ||||
|                 DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_PM_10_0): sensor.sensor_schema( | ||||
|                 UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|                 ICON_CHEMICAL_WEAPON, | ||||
|                 2, | ||||
|                 DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( | ||||
|                 UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x12)) | ||||
| ) | ||||
|  | ||||
| TYPES = { | ||||
|     CONF_PM_1_0: "set_pm_1_0_sensor", | ||||
|     CONF_PM_2_5: "set_pm_2_5_sensor", | ||||
|     CONF_PM_10_0: "set_pm_10_0_sensor", | ||||
|     CONF_PMC_0_3: "set_pmc_0_3_sensor", | ||||
|     CONF_PMC_0_5: "set_pmc_0_5_sensor", | ||||
|     CONF_PMC_1_0: "set_pmc_1_0_sensor", | ||||
|     CONF_PMC_2_5: "set_pmc_2_5_sensor", | ||||
|     CONF_PMC_5_0: "set_pmc_5_0_sensor", | ||||
|     CONF_PMC_10_0: "set_pmc_10_0_sensor", | ||||
| } | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_standard_units(config[CONF_STANDARD_UNITS])) | ||||
|  | ||||
|     for key, funcName in TYPES.items(): | ||||
|  | ||||
|         if key in config: | ||||
|             sens = yield sensor.new_sensor(config[key]) | ||||
|             cg.add(getattr(var, funcName)(sens)) | ||||
| @@ -675,6 +675,27 @@ sensor: | ||||
|       name: 'Outside Pressure' | ||||
|     address: 0x77 | ||||
|     update_interval: 15s | ||||
|   - platform: pmsa003i | ||||
|     pm_1_0: | ||||
|       name: "PMSA003i PM1.0" | ||||
|     pm_2_5: | ||||
|       name: "PMSA003i PM2.5" | ||||
|     pm_10_0: | ||||
|       name: "PMSA003i PM10.0" | ||||
|     pmc_0_3: | ||||
|       name: "PMSA003i PMC <0.3µm" | ||||
|     pmc_0_5: | ||||
|       name: "PMSA003i PMC <0.5µm" | ||||
|     pmc_1_0: | ||||
|       name: "PMSA003i PMC <1µm" | ||||
|     pmc_2_5: | ||||
|       name: "PMSA003i PMC <2.5µm" | ||||
|     pmc_5_0: | ||||
|       name: "PMSA003i PMC <5µm" | ||||
|     pmc_10_0: | ||||
|       name: "PMSA003i PMC <10µm" | ||||
|     address: 0x12 | ||||
|     standard_units: True | ||||
|   - platform: pulse_counter | ||||
|     name: 'Pulse Counter' | ||||
|     pin: GPIO12 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user