mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	AQI calculator for HM3301 (#1011)
* HM3301 AQI calculator * remove logs * fixed after lint * fixed after lint * fixed after lint * check NP for AQI sensor * validation for AQI sensor
This commit is contained in:
		
							
								
								
									
										14
									
								
								esphome/components/hm3301/abstract_aqi_calculator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/hm3301/abstract_aqi_calculator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hm3301 { | ||||
|  | ||||
| class AbstractAQICalculator { | ||||
|  public: | ||||
|   virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; | ||||
| }; | ||||
|  | ||||
| }  // namespace hm3301 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										46
									
								
								esphome/components/hm3301/aqi_calculator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/hm3301/aqi_calculator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #include "abstract_aqi_calculator.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hm3301 { | ||||
|  | ||||
| class AQICalculator : public AbstractAQICalculator { | ||||
|  public: | ||||
|   uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { | ||||
|     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); | ||||
|     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); | ||||
|  | ||||
|     return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   static const int AMOUNT_OF_LEVELS = 6; | ||||
|  | ||||
|   int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; | ||||
|  | ||||
|   int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 45}, {36, 55}, {56, 150}, {151, 250}, {251, 500}}; | ||||
|  | ||||
|   int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54},    {55, 154},  {155, 254}, | ||||
|                                                        {255, 354}, {355, 424}, {425, 604}}; | ||||
|  | ||||
|   int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { | ||||
|     int grid_index = get_grid_index_(value, array); | ||||
|     int aqi_lo = index_grid_[grid_index][0]; | ||||
|     int aqi_hi = index_grid_[grid_index][1]; | ||||
|     int conc_lo = array[grid_index][0]; | ||||
|     int conc_hi = array[grid_index][1]; | ||||
|  | ||||
|     return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; | ||||
|   } | ||||
|  | ||||
|   int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { | ||||
|     for (int i = 0; i < AMOUNT_OF_LEVELS - 1; i++) { | ||||
|       if (value >= array[i][0] && value <= array[i][1]) { | ||||
|         return i; | ||||
|       } | ||||
|     } | ||||
|     return -1; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace hm3301 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										30
									
								
								esphome/components/hm3301/aqi_calculator_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/hm3301/aqi_calculator_factory.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Arduino.h" | ||||
| #include "caqi_calculator.cpp" | ||||
| #include "aqi_calculator.cpp" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hm3301 { | ||||
|  | ||||
| enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 }; | ||||
|  | ||||
| class AQICalculatorFactory { | ||||
|  public: | ||||
|   AbstractAQICalculator *get_calculator(AQICalculatorType type) { | ||||
|     if (type == 0) { | ||||
|       return caqi_calculator_; | ||||
|     } else if (type == 1) { | ||||
|       return aqi_calculator_; | ||||
|     } | ||||
|  | ||||
|     return nullptr; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   CAQICalculator *caqi_calculator_ = new CAQICalculator(); | ||||
|   AQICalculator *aqi_calculator_ = new AQICalculator(); | ||||
| }; | ||||
|  | ||||
| }  // namespace hm3301 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										52
									
								
								esphome/components/hm3301/caqi_calculator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/hm3301/caqi_calculator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include "abstract_aqi_calculator.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hm3301 { | ||||
|  | ||||
| class CAQICalculator : public AbstractAQICalculator { | ||||
|  public: | ||||
|   uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { | ||||
|     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); | ||||
|     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); | ||||
|  | ||||
|     return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   static const int AMOUNT_OF_LEVELS = 5; | ||||
|  | ||||
|   int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; | ||||
|  | ||||
|   int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; | ||||
|  | ||||
|   int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; | ||||
|  | ||||
|   int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { | ||||
|     int grid_index = get_grid_index_(value, array); | ||||
|     if (grid_index == -1) { | ||||
|       return -1; | ||||
|     } | ||||
|  | ||||
|     int aqi_lo = index_grid_[grid_index][0]; | ||||
|     int aqi_hi = index_grid_[grid_index][1]; | ||||
|     int conc_lo = array[grid_index][0]; | ||||
|     int conc_hi = array[grid_index][1]; | ||||
|  | ||||
|     int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; | ||||
|  | ||||
|     return aqi; | ||||
|   } | ||||
|  | ||||
|   int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { | ||||
|     for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { | ||||
|       if (value >= array[i][0] && value <= array[i][1]) { | ||||
|         return i; | ||||
|       } | ||||
|     } | ||||
|     return -1; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace hm3301 | ||||
| }  // namespace esphome | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "hm3301.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "hm3301.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hm3301 { | ||||
| @@ -30,6 +30,7 @@ void HM3301Component::dump_config() { | ||||
|   LOG_SENSOR("  ", "PM1.0", this->pm_1_0_sensor_); | ||||
|   LOG_SENSOR("  ", "PM2.5", this->pm_2_5_sensor_); | ||||
|   LOG_SENSOR("  ", "PM10.0", this->pm_10_0_sensor_); | ||||
|   LOG_SENSOR("  ", "AQI", this->aqi_sensor_); | ||||
| } | ||||
|  | ||||
| float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
| @@ -47,17 +48,38 @@ void HM3301Component::update() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   int16_t pm_1_0_value = -1; | ||||
|   if (this->pm_1_0_sensor_ != nullptr) { | ||||
|     uint16_t value = get_sensor_value_(data_buffer_, PM_1_0_VALUE_INDEX); | ||||
|     this->pm_1_0_sensor_->publish_state(value); | ||||
|     pm_1_0_value = get_sensor_value_(data_buffer_, PM_1_0_VALUE_INDEX); | ||||
|   } | ||||
|  | ||||
|   int16_t pm_2_5_value = -1; | ||||
|   if (this->pm_2_5_sensor_ != nullptr) { | ||||
|     uint16_t value = get_sensor_value_(data_buffer_, PM_2_5_VALUE_INDEX); | ||||
|     this->pm_2_5_sensor_->publish_state(value); | ||||
|     pm_2_5_value = get_sensor_value_(data_buffer_, PM_2_5_VALUE_INDEX); | ||||
|   } | ||||
|  | ||||
|   int16_t pm_10_0_value = -1; | ||||
|   if (this->pm_10_0_sensor_ != nullptr) { | ||||
|     uint16_t value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); | ||||
|     this->pm_10_0_sensor_->publish_state(value); | ||||
|     pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); | ||||
|   } | ||||
|  | ||||
|   int8_t aqi_value = -1; | ||||
|   if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { | ||||
|     AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); | ||||
|     aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value); | ||||
|   } | ||||
|  | ||||
|   if (pm_1_0_value != -1) { | ||||
|     this->pm_1_0_sensor_->publish_state(pm_1_0_value); | ||||
|   } | ||||
|   if (pm_2_5_value != -1) { | ||||
|     this->pm_2_5_sensor_->publish_state(pm_2_5_value); | ||||
|   } | ||||
|   if (pm_10_0_value != -1) { | ||||
|     this->pm_10_0_sensor_->publish_state(pm_10_0_value); | ||||
|   } | ||||
|   if (aqi_value != -1) { | ||||
|     this->aqi_sensor_->publish_state(aqi_value); | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "aqi_calculator_factory.h" | ||||
|  | ||||
| #include <Seeed_HM330X.h> | ||||
|  | ||||
| @@ -16,6 +17,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { pm_1_0_sensor_ = pm_1_0_sensor; } | ||||
|   void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { pm_2_5_sensor_ = pm_2_5_sensor; } | ||||
|   void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } | ||||
|   void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } | ||||
|  | ||||
|   void set_aqi_calculation_type(AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| @@ -32,6 +36,10 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   sensor::Sensor *pm_1_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_2_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_10_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *aqi_sensor_{nullptr}; | ||||
|  | ||||
|   AQICalculatorType aqi_calc_type_; | ||||
|   AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); | ||||
|  | ||||
|   bool read_sensor_value_(uint8_t *); | ||||
|   bool validate_checksum_(const uint8_t *); | ||||
|   | ||||
| @@ -8,6 +8,25 @@ DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| hm3301_ns = cg.esphome_ns.namespace('hm3301') | ||||
| HM3301Component = hm3301_ns.class_('HM3301Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| AQICalculatorType = hm3301_ns.enum('AQICalculatorType') | ||||
|  | ||||
| CONF_AQI = 'aqi' | ||||
| CONF_CALCULATION_TYPE = 'calculation_type' | ||||
| UNIT_INDEX = 'index' | ||||
|  | ||||
| AQI_CALCULATION_TYPE = { | ||||
|     'CAQI': AQICalculatorType.CAQI_TYPE, | ||||
|     'AQI': AQICalculatorType.AQI_TYPE | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate(config): | ||||
|     if CONF_AQI in config and CONF_PM_2_5 not in config: | ||||
|         raise cv.Invalid("AQI sensor requires PM 2.5") | ||||
|     if CONF_AQI in config and CONF_PM_10_0 not in config: | ||||
|         raise cv.Invalid("AQI sensor requires PM 10 sensors") | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(HM3301Component), | ||||
| @@ -18,8 +37,11 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|         sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), | ||||
|     cv.Optional(CONF_PM_10_0): | ||||
|         sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), | ||||
|  | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40))) | ||||
|     cv.Optional(CONF_AQI): | ||||
|         sensor.sensor_schema(UNIT_INDEX, ICON_CHEMICAL_WEAPON, 0).extend({ | ||||
|             cv.Required(CONF_CALCULATION_TYPE): cv.enum(AQI_CALCULATION_TYPE, upper=True), | ||||
|         }) | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)), validate) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -39,5 +61,10 @@ def to_code(config): | ||||
|         sens = yield sensor.new_sensor(config[CONF_PM_10_0]) | ||||
|         cg.add(var.set_pm_10_0_sensor(sens)) | ||||
|  | ||||
|     if CONF_AQI in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_AQI]) | ||||
|         cg.add(var.set_aqi_sensor(sens)) | ||||
|         cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) | ||||
|  | ||||
|     # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 | ||||
|     cg.add_library('6306', '1.0.3') | ||||
|   | ||||
| @@ -738,6 +738,9 @@ sensor: | ||||
|       name: "PM2.5" | ||||
|     pm_10_0: | ||||
|       name: "PM10.0" | ||||
|     aqi: | ||||
|       name: "AQI" | ||||
|       calculation_type: "CAQI" | ||||
|  | ||||
| esp32_touch: | ||||
|   setup_mode: False | ||||
|   | ||||
| @@ -361,6 +361,9 @@ sensor: | ||||
|       name: "PM2.5" | ||||
|     pm_10_0: | ||||
|       name: "PM10.0" | ||||
|     aqi: | ||||
|       name: "AQI" | ||||
|       calculation_type: "AQI" | ||||
|   - platform: pmsx003 | ||||
|     type: PMSX003 | ||||
|     pm_1_0: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user