mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 15:08:10 +01: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:
parent
821c1a8bbd
commit
42007d03d4
9 changed files with 214 additions and 9 deletions
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:
|
||||
|
|
Loading…
Reference in a new issue