I received my outdoor kit last week and have been testing this esphome configuration since then and other than the 18h/12h delays it has been working well.
My problem has been that a few very small power interruptions return the 18h/12h delays on the AQI making it hard to rely on the value.
I also experienced reboots (crashes?) when the device can’t upload to airgradient (internet outage or firewall block) which again end up restarting the counter.
I finally commented out the upload to airgradient and added the calculations to home-assistant. The AQI was pretty straightforward using a 24h statistic sensor and template sensors. But the NowCast is complex to do in Home-Assistant using the official calculations. After testing a few scenarios it seems that using a 3h statistics sensor with the AQI calculations return values that are very close to the NowCast values returned by Esphome.
*The AQI is off at the beginning of the graph because of a 18h/12h reset
Regarding the query for statistics from Home-Assistant, this can be done using the websocket API. I don’t think it is documented but the relevant code in Home-Assistant is here
If any one is interested here are my configurations for Home-Assistant sensors
## AQI 3h
sensor:
- platform: statistics
name: PM2.5 Mean 3h
unique_id: pm2_5_mean_3h
entity_id: sensor.airgradient_airgradient_open_air_particulate_matter_2_5um_concentration
state_characteristic: mean
max_age:
hours: 3
- platform: statistics
name: PM10 Mean 3h
unique_id: pm10_mean_3h
entity_id: sensor.airgradient_airgradient_open_air_particulate_matter_10_0um_concentration
state_characteristic: mean
max_age:
hours: 3
- platform: statistics
name: PM2.5 Mean 24h
unique_id: pm2_5_mean_24h
entity_id: sensor.airgradient_airgradient_open_air_particulate_matter_2_5um_concentration
state_characteristic: mean
max_age:
hours: 24
- platform: statistics
name: PM10 Mean 24h
unique_id: pm10_mean_24h
entity_id: sensor.airgradient_airgradient_open_air_particulate_matter_10_0um_concentration
state_characteristic: mean
max_age:
hours: 24
template:
- sensor:
- name: "AQI EPA PM2.5 3h"
unique_id: aqi_epa_pm25_3h
device_class: aqi
state: >
{% set pm25 = states('sensor.pm2_5_mean_3h')|float %}
{% if pm25 < 12.0 %}
{{ ((50.0 - 0.0) / (12.0 - 0.0) * (pm25 - 0.0) + 0.0)|round }}
{% elif pm25 < 35.4 %}
{{ ((100.0 - 51.0) / (35.4 - 12.1) * (pm25 - 12.1) + 51.0)|round }}
{% elif pm25 < 55.4 %}
{{ ((150.0 - 101.0) / (55.4 - 35.5) * (pm25 - 35.5) + 101.0)|round }}
{% elif pm25 < 150.4 %}
{{ ((200.0 - 151.0) / (150.4 - 55.5) * (pm25 - 55.5) + 151.0)|round }}
{% elif pm25 < 250.4 %}
{{ ((300.0 - 201.0) / (250.4 - 150.5) * (pm25 - 150.5) + 201.0)|round }}
{% elif pm25 < 350.4 %}
{{ ((400.0 - 301.0) / (350.4 - 250.5) * (pm25 - 250.5) + 301.0)|round }}
{% elif pm25 < 500.4 %}
{{ ((500.0 - 401.0) / (500.4 - 350.5) * (pm25 - 350.5) + 401.0)|round }}
{% else %}
500
{% endif %}
- name: "AQI EPA PM10 3h"
unique_id: aqi_epa_pm10_3h
device_class: aqi
state: >
{% set pm10 = states('sensor.pm10_mean_3h')|float %}
{% if pm10 < 54.0 %}
{{ ((50.0 - 0.0) / (54.0 - 0.0) * (pm10 - 0.0) + 0.0)|round }}
{% elif pm10 < 154.0 %}
{{ ((100.0 - 51.0) / (154.0 - 55.0) * (pm10 - 55.0) + 51.0)|round }}
{% elif pm10 < 254.0 %}
{{ ((150.0 - 101.0) / (254.0 - 155.0) * (pm10 - 155.0) + 101.0)|round }}
{% elif pm10 < 354.0 %}
{{ ((200.0 - 151.0) / (354.0 - 255.0) * (pm10 - 255.0) + 151.0)|round }}
{% elif pm10 < 424.0 %}
{{ ((300.0 - 201.0) / (424.0 - 355.0) * (pm10 - 355.0) + 201.0)|round }}
{% elif pm10 < 504.0 %}
{{ ((400.0 - 301.0) / (504.0 - 425.0) * (pm10 - 425.0) + 301.0)|round }}
{% elif pm10 < 604.0 %}
{{ ((500.0 - 401.0) / (604.0 - 505.0) * (pm10 - 505.0) + 401.0)|round }}
{% else %}
500
{% endif %}
- name: "AQI EPA 3h"
unique_id: aqi_epa_3h
device_class: aqi
state: >-
{{ [states('sensor.aqi_epa_pm10_3h')|int, states('sensor.aqi_epa_pm2_5_3h')|int]|max }}
- name: "AQI EPA Category 3h"
unique_id: aqi_epa_category_3h
state: >-
{% set aqi = states('sensor.aqi_epa_3h')|int %}
{% if aqi <= 50.0 %}
"Good"
{% elif aqi <= 100.0 %}
"Moderate"
{% elif aqi <= 150.0 %}
"Unhealthy for Sensitive Groups"
{% elif aqi <= 200.0 %}
"Unhealthy"
{% elif aqi <= 300.0 %}
"Very Unhealthy"
{% elif aqi <= 500.0 %}
"Hazardous"
{% else %}
"Extreme Hazardous"
{% endif %}
## AQI EPA 24h
- name: AQI EPA PM2.5
unique_id: aqi_epa_pm25
device_class: aqi
state: >
{% set pm25 = states('sensor.pm2_5_mean_24h')|float %}
{% if pm25 < 12.0 %}
{{ ((50.0 - 0.0) / (12.0 - 0.0) * (pm25 - 0.0) + 0.0)|round }}
{% elif pm25 < 35.4 %}
{{ ((100.0 - 51.0) / (35.4 - 12.1) * (pm25 - 12.1) + 51.0)|round }}
{% elif pm25 < 55.4 %}
{{ ((150.0 - 101.0) / (55.4 - 35.5) * (pm25 - 35.5) + 101.0)|round }}
{% elif pm25 < 150.4 %}
{{ ((200.0 - 151.0) / (150.4 - 55.5) * (pm25 - 55.5) + 151.0)|round }}
{% elif pm25 < 250.4 %}
{{ ((300.0 - 201.0) / (250.4 - 150.5) * (pm25 - 150.5) + 201.0)|round }}
{% elif pm25 < 350.4 %}
{{ ((400.0 - 301.0) / (350.4 - 250.5) * (pm25 - 250.5) + 301.0)|round }}
{% elif pm25 < 500.4 %}
{{ ((500.0 - 401.0) / (500.4 - 350.5) * (pm25 - 350.5) + 401.0)|round }}
{% else %}
500
{% endif %}
- name: "AQI EPA PM10"
unique_id: aqi_epa_pm10
device_class: aqi
state: >
{% set pm10 = states('sensor.pm10_mean_24h')|float %}
{% if pm10 < 54.0 %}
{{ ((50.0 - 0.0) / (54.0 - 0.0) * (pm10 - 0.0) + 0.0)|round }}
{% elif pm10 < 154.0 %}
{{ ((100.0 - 51.0) / (154.0 - 55.0) * (pm10 - 55.0) + 51.0)|round }}
{% elif pm10 < 254.0 %}
{{ ((150.0 - 101.0) / (254.0 - 155.0) * (pm10 - 155.0) + 101.0)|round }}
{% elif pm10 < 354.0 %}
{{ ((200.0 - 151.0) / (354.0 - 255.0) * (pm10 - 255.0) + 151.0)|round }}
{% elif pm10 < 424.0 %}
{{ ((300.0 - 201.0) / (424.0 - 355.0) * (pm10 - 355.0) + 201.0)|round }}
{% elif pm10 < 504.0 %}
{{ ((400.0 - 301.0) / (504.0 - 425.0) * (pm10 - 425.0) + 301.0)|round }}
{% elif pm10 < 604.0 %}
{{ ((500.0 - 401.0) / (604.0 - 505.0) * (pm10 - 505.0) + 401.0)|round }}
{% else %}
500
{% endif %}
- name: "AQI EPA"
unique_id: aqi_epa
device_class: aqi
state: >-
{{ [states('sensor.aqi_epa_pm10')|int, states('sensor.aqi_epa_pm2_5')|int]|max }}
- name: "AQI EPA Category "
unique_id: aqi_epa_category
state: >-
{% set aqi = states('sensor.aqi_epa')|int %}
{% if aqi <= 50.0 %}
"Good"
{% elif aqi <= 100.0 %}
"Moderate"
{% elif aqi <= 150.0 %}
"Unhealthy for Sensitive Groups"
{% elif aqi <= 200.0 %}
"Unhealthy"
{% elif aqi <= 300.0 %}
"Very Unhealthy"
{% elif aqi <= 500.0 %}
"Hazardous"
{% else %}
"Extreme Hazardous"
{% endif %}