Skip to content

Instantly share code, notes, and snippets.

@rubin110
Last active November 22, 2024 07:12
Show Gist options
  • Save rubin110/d5dd8e89457cb631f0a610ac539a2b12 to your computer and use it in GitHub Desktop.
Save rubin110/d5dd8e89457cb631f0a610ac539a2b12 to your computer and use it in GitHub Desktop.
Generate AQI from PM2.5 on the great Apollo Automation AIR-1!
Generate AQI from PM2.5 on the great Apollo Automation AIR-1!
# https://apolloautomation.com/products/air-1
# https://wiki.apolloautomation.com/books/air-1
# https://github.com/ApolloAutomation/AIR-1
# This can also be applied to basically any other PM2.5 sensor.
# ESPHome doesn't provide a simple means to generate an AQI value from a PM2.5 sensor.
# AQI is subjective based on where you live in the world, so this is something you need
# to put together by hand. This can be done within ESPHome (as shown here) or a template
# in HA.
# All this is provided as is. The values generated are for the US EPA standards. Other
# parts of the world generate AQI differently. Regardless of if you're in the US or not
# you should do the research yourself before using any of this. I am no expert at any of
# this. If you've got thoughts on how this could be done differently/better/more
# accurately, I'm all ears. You can find me on the net as either rubin110 or Rubin Abdi
# or Rubin Starset.
# What's provided here is:
# - PM <2.5µm Weight concentration - Already part of the AIR-1 default yaml.
# - PM2.5 average - Most air quality products I've used average out PM2.5 because drafts
# happen when you walk past sensors which can widely mess with the value.
# - AQI Value - A bunch of math is applied to take the PM2.5 average and generate a USA
# EPA AQI number, which can be compared to other weather websites that also offer
# their air quality data as AQI.
# - AQI Level - This is the US EPA human readable level which you can point to a random
# person on the street and say the street smog is "Unhealthy" air, or the hills are
# alive with "Good" air.
# - AQI - This is both the AQI Value and AQI Level turned into a single entity to be
# displayed on an HA dashboard.
# - Air Pollution to Smoked Cigarettes Per Day Equivalence - Bonus! Some great people
# at Cal Berkeley figured out how many cigarettes you've smoked when polution is high.
# This is the closest thing you'll find to a universal standard in generating a human
# readable value when it comes down to understaning air quality polution. Now you have
# "cigarettes" as a unit of measurement in HA.
# You will need to copy and paste parts of this code into your AIR-1 yaml, or whatever
# PM2.5 device you've got. This works fine with the highly inaccurate PM1006 that comes
# inside the hackable Ikea Vindriktning air quality monitoring device.
# If you want to read more about how to generate these values:
# https://www.epa.gov/system/files/documents/2024-02/pm-naaqs-air-quality-index-fact-sheet.pdf
# https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
# https://www.airnow.gov/aqi/aqi-basics/
# https://berkeleyearth.org/air-pollution-and-cigarette-equivalence/
# If you live in a seasonale wild fire part of the world, humidity makes a difference.
# You should check out this paper:
# https://cfpub.epa.gov/si/si_public_record_report.cfm?Lab=CEMM&dirEntryId=349513
# Thanks!
sensor:
# Grab PM2.5 from the SEN5X, this is already part of the AIR-1 defauly yaml.
- platform: sen5x
id: sen55
pm_2_5:
name: "PM <2.5µm Weight concentration"
id: pm_2_5
accuracy_decimals: 2
# Average out the PM2.5 over time. On update use that value to generate the AQI Value
# and AQI Level, then populate those sensors. Also figures out how many cigarettes
# you've smoked today.
- platform: copy
source_id: pm_2_5
id: pm_2_5_average
name: "PM2.5 average"
accuracy_decimals: 1
filters:
- sliding_window_moving_average:
window_size: 50
send_every: 10
# send_first_at: 20
on_value:
lambda: |-
static int i = 0;
i++;
if(i>=1){
// https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
// https://www.epa.gov/system/files/documents/2024-02/pm-naaqs-air-quality-index-fact-sheet.pdf
if (id(pm_2_5_average).state < 9.0) {
// good
id(aqi_level).publish_state("Good");
id(pm_2_5_average_aqi).publish_state((50.0 - 0.0) / (9.0 - 0.0) * (id(pm_2_5_average).state - 0.0) + 0.0);
} else if (id(pm_2_5_average).state < 35.4) {
// moderate
id(aqi_level).publish_state("Moderate");
id(pm_2_5_average_aqi).publish_state((100.0 - 51.0) / (35.4 - 9.1) * (id(pm_2_5_average).state - 9.0) + 51.0);
} else if (id(pm_2_5_average).state < 55.4) {
// Unhealthy for Sensitive Groups
id(aqi_level).publish_state("Unhealthy for Sensitive Groups");
id(pm_2_5_average_aqi).publish_state((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5_average).state - 35.5) + 101.0);
} else if (id(pm_2_5_average).state < 125.4) {
// unhealthy
id(aqi_level).publish_state("Unhealthy");
id(pm_2_5_average_aqi).publish_state((200.0 - 151.0) / (125.4 - 55.5) * (id(pm_2_5_average).state - 55.5) + 151.0);
} else if (id(pm_2_5_average).state < 225.4) {
// very unhealthy
id(aqi_level).publish_state("Very Unhealthy");
id(pm_2_5_average_aqi).publish_state((300.0 - 201.0) / (225.4 - 125.5) * (id(pm_2_5_average).state - 125.5) + 201.0);
} else if (id(pm_2_5_average).state > 225.5) {
// hazardous
id(aqi_level).publish_state("Hazardous");
}
// https://berkeleyearth.org/air-pollution-and-cigarette-equivalence/
id(pm_2_5_cigarettes).publish_state(id(pm_2_5_average).state / 22);
}
# AQI Value sensors waiting to be populdated.
- platform: template
name: "AQI Value"
device_class: aqi
unit_of_measurement: "AQI"
icon: "mdi:air-filter"
accuracy_decimals: 0
id: pm_2_5_average_aqi
# Air Pollution Cigarette Equivalence waiting to be populdated.
- platform: template
name: "Air Pollution to Smoked Cigarettes Per Day Equivalence"
unit_of_measurement: "cigarettes"
icon: "mdi:smoking"
accuracy_decimals: 1
id: pm_2_5_cigarettes
text_sensor:
# AQI Level, the human readable level, waiting to be populated.
- platform: template
name: "AQI Level"
icon: "mdi:air-filter"
id: aqi_level
# Combind both the Value and Level into a single entity to be displayed on an HA dashboard.
- platform: template
name: "AQI"
icon: "mdi:air-filter"
id: aqi
lambda: return str_sprintf("%.0f - %s", id(pm_2_5_average_aqi).state, id(aqi_level).state.c_str());
@rubin110
Copy link
Author

I hear you. Let me muck around a little bit with this. I'm no ESPHome expert but I like to play.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment