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());
@maia
Copy link

maia commented Nov 17, 2024

I'm unfortunately not familiar with ESPHome code. As for the code listed above, I still use it but removed the PM2.5 value from the indoor AQI as in my case I use this indoor AQI as indicator to open the window – and as I'm seeing more PM2.5 particles outdoor than indoor (at least in winter during thermal inversion periods) it's counter-productive to use these in the calculation.

@rubin110
Copy link
Author

@maia So if I understand it correctly your original ask was to expose from the ESPHome device sensors which provide the EPA AQI values generated from the PM2.5 sensor, correct? The code in this gist does that already. Let me know if I'm mistaken on what you're asking for.

@maia
Copy link

maia commented Nov 17, 2024

@rubin110 My suggestion still is an ESPHome implementation of something like the code I pasted above, an indoor AQI sensor that aggregates CO2, VOC, NOX (and not PM2.5/PM10) as part of the AIR-1 firmware. Because no HA user should need to learn how to create a template sensor when purchasing an AIR-1, they should connect it to HA and have an indoor AQI value available.

@IkerGarcia
Copy link

@rubin110 @maia

I cannot help with ESPHome.

Regarding to the code, the last one posted here is ok. We could also share the approach I shared in Discord, the RESET Air Index, which seems to be a standard.

In my case I've seen that normal day activities can cause PM2.5 values rise, I hope I can probably share some info about this. On the other hand, NOX is not important for me. This is why I would use and standard.

Anyway, the best way to compare indoor/outdoor is two monitors.

@rubin110
Copy link
Author

@maia I know that the EPA standard pulls from other sensor readings too. Generally with most consumer off the shelf air monitors AQI is only calculated with PM2.5 (I'm assuming due to reduce BOM costs), which unfortunately has turned into the standard. Event PurpleAir only does PM2.5 readings.

I am interested in playing with your code and seeing how much reading all the other sensors will waver the AQI value away from just using PM2.5.

@maia
Copy link

maia commented Nov 22, 2024

This is a subjective opinion, likely influenced by the city I live in, which has a high standard of living and minimal pollution: When using the AIR-1 in my bedroom, I want it to alert me when it’s time to open the window, even if it’s freezing outside. Over the past few months, I’ve found three key values to be most relevant: CO2, VOC, and humidity (e.g., after taking a long shower in the adjoining bathroom). Including PM levels in the indoor AQI calculation has been counterproductive, as outdoor PM levels are often higher than indoors.

That said, I understand why outdoor AQI prioritises PM levels. If I had to pick one outdoor air quality metric, it would be PM too. For indoor air quality, however, CO2 is my subjective top priority. Ideally, VOC would also play a role, but the current algorithm from Sensirion hasn’t been as practical as I initially hoped.

Introducing any form of indoor AQI calculation into the AIR-1 firmware would be a big step forward. While I’m comfortable with template sensors and Jinja coding, many people find even creating a single template sensor in the file system overwhelming. That’s why I’m hoping someone can translate our ideas into ESP code to make it more accessible.

@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