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/ota/* @esphome/core | ||||||
| esphome/components/output/* @esphome/core | esphome/components/output/* @esphome/core | ||||||
| esphome/components/pid/* @OttoWinter | esphome/components/pid/* @OttoWinter | ||||||
|  | esphome/components/pmsa003i/* @sjtrny | ||||||
| esphome/components/pn532/* @OttoWinter @jesserockz | esphome/components/pn532/* @OttoWinter @jesserockz | ||||||
| esphome/components/pn532_i2c/* @OttoWinter @jesserockz | esphome/components/pn532_i2c/* @OttoWinter @jesserockz | ||||||
| esphome/components/pn532_spi/* @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' |       name: 'Outside Pressure' | ||||||
|     address: 0x77 |     address: 0x77 | ||||||
|     update_interval: 15s |     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 |   - platform: pulse_counter | ||||||
|     name: 'Pulse Counter' |     name: 'Pulse Counter' | ||||||
|     pin: GPIO12 |     pin: GPIO12 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user