mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 00:48:19 +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 "esphome/core/log.h"
|
||||||
|
#include "hm3301.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace hm3301 {
|
namespace hm3301 {
|
||||||
|
@ -30,6 +30,7 @@ void HM3301Component::dump_config() {
|
||||||
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
|
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
|
||||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||||
LOG_SENSOR(" ", "PM10.0", this->pm_10_0_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; }
|
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
@ -47,17 +48,38 @@ void HM3301Component::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t pm_1_0_value = -1;
|
||||||
if (this->pm_1_0_sensor_ != nullptr) {
|
if (this->pm_1_0_sensor_ != nullptr) {
|
||||||
uint16_t value = get_sensor_value_(data_buffer_, PM_1_0_VALUE_INDEX);
|
pm_1_0_value = get_sensor_value_(data_buffer_, PM_1_0_VALUE_INDEX);
|
||||||
this->pm_1_0_sensor_->publish_state(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t pm_2_5_value = -1;
|
||||||
if (this->pm_2_5_sensor_ != nullptr) {
|
if (this->pm_2_5_sensor_ != nullptr) {
|
||||||
uint16_t value = get_sensor_value_(data_buffer_, PM_2_5_VALUE_INDEX);
|
pm_2_5_value = get_sensor_value_(data_buffer_, PM_2_5_VALUE_INDEX);
|
||||||
this->pm_2_5_sensor_->publish_state(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t pm_10_0_value = -1;
|
||||||
if (this->pm_10_0_sensor_ != nullptr) {
|
if (this->pm_10_0_sensor_ != nullptr) {
|
||||||
uint16_t value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
|
pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
|
||||||
this->pm_10_0_sensor_->publish_state(value);
|
}
|
||||||
|
|
||||||
|
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();
|
this->status_clear_warning();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "aqi_calculator_factory.h"
|
||||||
|
|
||||||
#include <Seeed_HM330X.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_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_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_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 setup() override;
|
||||||
void dump_config() 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_1_0_sensor_{nullptr};
|
||||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||||
sensor::Sensor *pm_10_0_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 read_sensor_value_(uint8_t *);
|
||||||
bool validate_checksum_(const uint8_t *);
|
bool validate_checksum_(const uint8_t *);
|
||||||
|
|
|
@ -8,6 +8,25 @@ DEPENDENCIES = ['i2c']
|
||||||
|
|
||||||
hm3301_ns = cg.esphome_ns.namespace('hm3301')
|
hm3301_ns = cg.esphome_ns.namespace('hm3301')
|
||||||
HM3301Component = hm3301_ns.class_('HM3301Component', cg.PollingComponent, i2c.I2CDevice)
|
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({
|
CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(HM3301Component),
|
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),
|
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0),
|
||||||
cv.Optional(CONF_PM_10_0):
|
cv.Optional(CONF_PM_10_0):
|
||||||
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0),
|
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0),
|
||||||
|
cv.Optional(CONF_AQI):
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)))
|
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):
|
def to_code(config):
|
||||||
|
@ -39,5 +61,10 @@ def to_code(config):
|
||||||
sens = yield sensor.new_sensor(config[CONF_PM_10_0])
|
sens = yield sensor.new_sensor(config[CONF_PM_10_0])
|
||||||
cg.add(var.set_pm_10_0_sensor(sens))
|
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
|
# https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
|
||||||
cg.add_library('6306', '1.0.3')
|
cg.add_library('6306', '1.0.3')
|
||||||
|
|
|
@ -738,6 +738,9 @@ sensor:
|
||||||
name: "PM2.5"
|
name: "PM2.5"
|
||||||
pm_10_0:
|
pm_10_0:
|
||||||
name: "PM10.0"
|
name: "PM10.0"
|
||||||
|
aqi:
|
||||||
|
name: "AQI"
|
||||||
|
calculation_type: "CAQI"
|
||||||
|
|
||||||
esp32_touch:
|
esp32_touch:
|
||||||
setup_mode: False
|
setup_mode: False
|
||||||
|
|
|
@ -361,6 +361,9 @@ sensor:
|
||||||
name: "PM2.5"
|
name: "PM2.5"
|
||||||
pm_10_0:
|
pm_10_0:
|
||||||
name: "PM10.0"
|
name: "PM10.0"
|
||||||
|
aqi:
|
||||||
|
name: "AQI"
|
||||||
|
calculation_type: "AQI"
|
||||||
- platform: pmsx003
|
- platform: pmsx003
|
||||||
type: PMSX003
|
type: PMSX003
|
||||||
pm_1_0:
|
pm_1_0:
|
||||||
|
|
Loading…
Reference in a new issue