mirror of
https://github.com/esphome/esphome.git
synced 2026-01-08 19:20:51 -07:00
[aqi] Implement a sensor that computes AQI (#12958)
Co-authored-by: jas <jas@asspa.in>
This commit is contained in:
@@ -10,38 +10,37 @@ namespace esphome::aqi {
|
|||||||
class AQICalculator : public AbstractAQICalculator {
|
class AQICalculator : public AbstractAQICalculator {
|
||||||
public:
|
public:
|
||||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
uint16_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 pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||||
|
|
||||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const int AMOUNT_OF_LEVELS = 6;
|
static constexpr int NUM_LEVELS = 6;
|
||||||
|
|
||||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||||
|
|
||||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
|
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
|
||||||
{56, 125}, {126, 225}, {226, INT_MAX}};
|
|
||||||
|
|
||||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||||
|
|
||||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index_(value, array);
|
int grid_index = get_grid_index(value, array);
|
||||||
if (grid_index == -1) {
|
if (grid_index == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int aqi_lo = index_grid_[grid_index][0];
|
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||||
int aqi_hi = index_grid_[grid_index][1];
|
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||||
int conc_lo = array[grid_index][0];
|
int conc_lo = array[grid_index][0];
|
||||||
int conc_hi = array[grid_index][1];
|
int conc_hi = array[grid_index][1];
|
||||||
|
|
||||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||||
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
|
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||||
if (value >= array[i][0] && value <= array[i][1]) {
|
if (value >= array[i][0] && value <= array[i][1]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|||||||
52
esphome/components/aqi/aqi_sensor.cpp
Normal file
52
esphome/components/aqi/aqi_sensor.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "aqi_sensor.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome::aqi {
|
||||||
|
|
||||||
|
static const char *const TAG = "aqi";
|
||||||
|
|
||||||
|
void AQISensor::setup() {
|
||||||
|
if (this->pm_2_5_sensor_ != nullptr) {
|
||||||
|
this->pm_2_5_sensor_->add_on_state_callback([this](float value) {
|
||||||
|
this->pm_2_5_value_ = value;
|
||||||
|
// Defer calculation to avoid double-publishing if both sensors update in the same loop
|
||||||
|
this->defer("update", [this]() { this->calculate_aqi_(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this->pm_10_0_sensor_ != nullptr) {
|
||||||
|
this->pm_10_0_sensor_->add_on_state_callback([this](float value) {
|
||||||
|
this->pm_10_0_value_ = value;
|
||||||
|
this->defer("update", [this]() { this->calculate_aqi_(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AQISensor::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AQI Sensor:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
|
||||||
|
if (this->pm_2_5_sensor_ != nullptr) {
|
||||||
|
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
|
||||||
|
}
|
||||||
|
if (this->pm_10_0_sensor_ != nullptr) {
|
||||||
|
ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str());
|
||||||
|
}
|
||||||
|
LOG_SENSOR(" ", "AQI", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AQISensor::calculate_aqi_() {
|
||||||
|
if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
|
||||||
|
if (calculator == nullptr) {
|
||||||
|
ESP_LOGW(TAG, "Unknown AQI calculator type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t aqi =
|
||||||
|
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
|
||||||
|
this->publish_state(aqi);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::aqi
|
||||||
31
esphome/components/aqi/aqi_sensor.h
Normal file
31
esphome/components/aqi/aqi_sensor.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "aqi_calculator_factory.h"
|
||||||
|
|
||||||
|
namespace esphome::aqi {
|
||||||
|
|
||||||
|
class AQISensor : public sensor::Sensor, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
|
||||||
|
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
|
||||||
|
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void calculate_aqi_();
|
||||||
|
|
||||||
|
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||||
|
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||||
|
AQICalculatorType aqi_calc_type_{AQI_TYPE};
|
||||||
|
AQICalculatorFactory aqi_calculator_factory_;
|
||||||
|
|
||||||
|
float pm_2_5_value_{NAN};
|
||||||
|
float pm_10_0_value_{NAN};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::aqi
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "abstract_aqi_calculator.h"
|
#include "abstract_aqi_calculator.h"
|
||||||
|
|
||||||
namespace esphome::aqi {
|
namespace esphome::aqi {
|
||||||
@@ -8,37 +7,37 @@ namespace esphome::aqi {
|
|||||||
class CAQICalculator : public AbstractAQICalculator {
|
class CAQICalculator : public AbstractAQICalculator {
|
||||||
public:
|
public:
|
||||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
uint16_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 pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||||
|
|
||||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const int AMOUNT_OF_LEVELS = 5;
|
static constexpr int NUM_LEVELS = 5;
|
||||||
|
|
||||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
static constexpr int INDEX_GRID[NUM_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}};
|
static constexpr int PM2_5_GRID[NUM_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}};
|
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
|
||||||
|
|
||||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index_(value, array);
|
int grid_index = get_grid_index(value, array);
|
||||||
if (grid_index == -1) {
|
if (grid_index == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int aqi_lo = index_grid_[grid_index][0];
|
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||||
int aqi_hi = index_grid_[grid_index][1];
|
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||||
int conc_lo = array[grid_index][0];
|
int conc_lo = array[grid_index][0];
|
||||||
int conc_hi = array[grid_index][1];
|
int conc_hi = array[grid_index][1];
|
||||||
|
|
||||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||||
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
|
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||||
if (value >= array[i][0] && value <= array[i][1]) {
|
if (value >= array[i][0] && value <= array[i][1]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|||||||
51
esphome/components/aqi/sensor.py
Normal file
51
esphome/components/aqi/sensor.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_PM_2_5,
|
||||||
|
CONF_PM_10_0,
|
||||||
|
DEVICE_CLASS_AQI,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
|
||||||
|
|
||||||
|
CODEOWNERS = ["@jasstrong"]
|
||||||
|
DEPENDENCIES = ["sensor"]
|
||||||
|
|
||||||
|
UNIT_INDEX = "index"
|
||||||
|
|
||||||
|
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
AQISensor,
|
||||||
|
unit_of_measurement=UNIT_INDEX,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_AQI,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
|
||||||
|
AQI_CALCULATION_TYPE, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5])
|
||||||
|
cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor))
|
||||||
|
|
||||||
|
pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0])
|
||||||
|
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
|
||||||
|
|
||||||
|
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE
|
from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE
|
||||||
@@ -16,6 +18,8 @@ from esphome.const import (
|
|||||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ["aqi"]
|
AUTO_LOAD = ["aqi"]
|
||||||
CODEOWNERS = ["@freekode"]
|
CODEOWNERS = ["@freekode"]
|
||||||
@@ -99,7 +103,12 @@ async def to_code(config):
|
|||||||
sens = await sensor.new_sensor(config[CONF_PM_10_0])
|
sens = await 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))
|
||||||
|
|
||||||
|
# Remove before 2026.12.0
|
||||||
if CONF_AQI in config:
|
if CONF_AQI in config:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The 'aqi' option in hm3301 is deprecated, "
|
||||||
|
"please use the standalone 'aqi' sensor platform instead."
|
||||||
|
)
|
||||||
sens = await sensor.new_sensor(config[CONF_AQI])
|
sens = await sensor.new_sensor(config[CONF_AQI])
|
||||||
cg.add(var.set_aqi_sensor(sens))
|
cg.add(var.set_aqi_sensor(sens))
|
||||||
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
|
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
|
||||||
|
|||||||
@@ -265,13 +265,6 @@ void PMSX003Component::parse_data_() {
|
|||||||
if (this->pm_particles_25um_sensor_ != nullptr)
|
if (this->pm_particles_25um_sensor_ != nullptr)
|
||||||
this->pm_particles_25um_sensor_->publish_state(pm_particles_25um);
|
this->pm_particles_25um_sensor_->publish_state(pm_particles_25um);
|
||||||
|
|
||||||
// Calculate and publish AQI if sensor is configured
|
|
||||||
if (this->aqi_sensor_ != nullptr) {
|
|
||||||
aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
|
|
||||||
int32_t aqi_value = calculator->get_aqi(pm_2_5_concentration, pm_10_0_concentration);
|
|
||||||
this->aqi_sensor_->publish_state(aqi_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->type_ == PMSX003_TYPE_5003T) {
|
if (this->type_ == PMSX003_TYPE_5003T) {
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
|
"Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, "
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
#include "esphome/components/aqi/aqi_calculator_factory.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace pmsx003 {
|
namespace pmsx003 {
|
||||||
@@ -74,10 +73,6 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
|||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; }
|
|
||||||
|
|
||||||
void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
optional<bool> check_byte_();
|
optional<bool> check_byte_();
|
||||||
void parse_data_();
|
void parse_data_();
|
||||||
@@ -121,12 +116,6 @@ class PMSX003Component : public uart::UARTDevice, public Component {
|
|||||||
// Temperature and Humidity
|
// Temperature and Humidity
|
||||||
sensor::Sensor *temperature_sensor_{nullptr};
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
sensor::Sensor *humidity_sensor_{nullptr};
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
|
|
||||||
// AQI
|
|
||||||
sensor::Sensor *aqi_sensor_{nullptr};
|
|
||||||
|
|
||||||
aqi::AQICalculatorType aqi_calc_type_;
|
|
||||||
aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pmsx003
|
} // namespace pmsx003
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor, uart
|
from esphome.components import sensor, uart
|
||||||
from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_FORMALDEHYDE,
|
CONF_FORMALDEHYDE,
|
||||||
@@ -21,7 +20,6 @@ from esphome.const import (
|
|||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
DEVICE_CLASS_AQI,
|
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_PM1,
|
DEVICE_CLASS_PM1,
|
||||||
DEVICE_CLASS_PM10,
|
DEVICE_CLASS_PM10,
|
||||||
@@ -37,13 +35,11 @@ from esphome.const import (
|
|||||||
|
|
||||||
CODEOWNERS = ["@ximex"]
|
CODEOWNERS = ["@ximex"]
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
AUTO_LOAD = ["aqi"]
|
|
||||||
|
|
||||||
pmsx003_ns = cg.esphome_ns.namespace("pmsx003")
|
pmsx003_ns = cg.esphome_ns.namespace("pmsx003")
|
||||||
PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component)
|
PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component)
|
||||||
PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
|
PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
|
||||||
|
|
||||||
UNIT_INDEX = "index"
|
|
||||||
TYPE_PMSX003 = "PMSX003"
|
TYPE_PMSX003 = "PMSX003"
|
||||||
TYPE_PMS5003T = "PMS5003T"
|
TYPE_PMS5003T = "PMS5003T"
|
||||||
TYPE_PMS5003ST = "PMS5003ST"
|
TYPE_PMS5003ST = "PMS5003ST"
|
||||||
@@ -81,10 +77,6 @@ def validate_pmsx003_sensors(value):
|
|||||||
for key, types in SENSORS_TO_TYPE.items():
|
for key, types in SENSORS_TO_TYPE.items():
|
||||||
if key in value and value[CONF_TYPE] not in types:
|
if key in value and value[CONF_TYPE] not in types:
|
||||||
raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!")
|
raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!")
|
||||||
if CONF_AQI in value and CONF_PM_2_5 not in value:
|
|
||||||
raise cv.Invalid("AQI computation requires PM 2.5 sensor")
|
|
||||||
if CONF_AQI in value and CONF_PM_10_0 not in value:
|
|
||||||
raise cv.Invalid("AQI computation requires PM 10 sensor")
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -200,19 +192,6 @@ CONFIG_SCHEMA = (
|
|||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_AQI): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_INDEX,
|
|
||||||
icon=ICON_CHEMICAL_WEAPON,
|
|
||||||
accuracy_decimals=0,
|
|
||||||
device_class=DEVICE_CLASS_AQI,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
|
|
||||||
AQI_CALCULATION_TYPE, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval,
|
cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -299,9 +278,4 @@ async def to_code(config):
|
|||||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||||
cg.add(var.set_humidity_sensor(sens))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
|
||||||
if CONF_AQI in config:
|
|
||||||
sens = await 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]))
|
|
||||||
|
|
||||||
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
|||||||
22
tests/components/aqi/common.yaml
Normal file
22
tests/components/aqi/common.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
sensor:
|
||||||
|
- platform: template
|
||||||
|
id: pm25_sensor
|
||||||
|
name: "PM2.5"
|
||||||
|
lambda: "return 25.0;"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: pm10_sensor
|
||||||
|
name: "PM10"
|
||||||
|
lambda: "return 50.0;"
|
||||||
|
|
||||||
|
- platform: aqi
|
||||||
|
name: "Air Quality Index (AQI)"
|
||||||
|
pm_2_5: pm25_sensor
|
||||||
|
pm_10_0: pm10_sensor
|
||||||
|
calculation_type: AQI
|
||||||
|
|
||||||
|
- platform: aqi
|
||||||
|
name: "Air Quality Index (CAQI)"
|
||||||
|
pm_2_5: pm25_sensor
|
||||||
|
pm_10_0: pm10_sensor
|
||||||
|
calculation_type: CAQI
|
||||||
1
tests/components/aqi/test.esp32-idf.yaml
Normal file
1
tests/components/aqi/test.esp32-idf.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
||||||
1
tests/components/aqi/test.esp8266-ard.yaml
Normal file
1
tests/components/aqi/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
||||||
1
tests/components/aqi/test.rp2040-ard.yaml
Normal file
1
tests/components/aqi/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
||||||
@@ -25,7 +25,4 @@ sensor:
|
|||||||
name: Particulate Count >5.0um
|
name: Particulate Count >5.0um
|
||||||
pm_10_0um:
|
pm_10_0um:
|
||||||
name: Particulate Count >10.0um
|
name: Particulate Count >10.0um
|
||||||
aqi:
|
|
||||||
name: AQI
|
|
||||||
calculation_type: AQI
|
|
||||||
update_interval: 30s
|
update_interval: 30s
|
||||||
|
|||||||
Reference in New Issue
Block a user