Air Quality AIRNOW or cfpub.epa.gov

California is battling with fires and so important for me to get the AQI.
Airnow or https://cfpub.epa.gov/ seems to be more accurate than the one I use (purpleair) plus it’s the same value that’s on my iPhone’s built-in weather app.

Has anyone tried to integrate it with HA? Or perhaps has successfully SCRAPE’d it?

I’ve been using AirVisual and it’s been fairly consistent with AirNow:

1 Like

I’m so confused with all these AQI numbers, my iphone shows 27 AQI while AirVisual shows 42 AQI.

AQI is highly localized. You’re going to see a rather wide range between services/apps as they all depend specifically on where they’re getting their readings.

Pay attention to the result: Good, Moderate, Unhealthy for Sensitive Groups, Unhealthy, etc. They won’t be off by more than one. The actual AQI is a average of a pollutant over time, so it’s not even a specific reading at a moment in time. Thus, two stations next to each other can give different values, just by polling them at different times.

More here: https://en.wikipedia.org/wiki/Air_quality_index

-Rob

I was able to get this working with a couple of rest sensors and several template sensors to further parse them.

Step 1: Register for an API key at AirNow
Step 2: Use the query builder AirNow provides to construct the URL for your latitude, longitude and API key, and save it in secrets.yaml

eg:

airnow_current_url: http://www.airnowapi.org/aq/observation/latLong/current/?format=application/json&latitude=12.34567&longitude=-89.01234&distance=25&API_KEY=ABCDEF-12345-67890-GHIJKLM

Step 3: Use AirNow’s query builder to determine what kind of data is available to you. For my location, there were 2 reported measurements: PM2.5 and O3. Some coordinates I tried only had one, some may have more. Narrow in on what’s available to you, and if you don’t want to use all of it, decide on that. I decided to report both into HA as sensors for now.

Step 4: Create a rest sensor for each measurement you want to report in HA (I could not determine how to encapsulate the array of measurements in a single rest sensor based on AirNow’s JSON format. If someone else does, let me know!)

- platform: rest
  name: airnow_current_pm25
  resource_template: !secret airnow_current_url
  json_attributes_path: "$.[?(@.ParameterName=='PM2.5')]"
  json_attributes:
  - AQI
  - Category
  value_template: OK
  
- platform: rest
  name: airnow_current_o3
  resource_template: !secret airnow_current_url
  json_attributes_path: "$.[?(@.ParameterName=='O3')]"
  json_attributes:
  - AQI
  - Category
  value_template: OK

Step 5: Use template sensors to further extract the data:

- platform: template
 sensors:
   airnow_current_pm25_category:
     unique_id: sensor.airnow_pm25_category
     friendly_name: AirNow PM2.5 Category
     value_template: "{{ state_attr('sensor.airnow_current_pm25', 'Category')['Name'] }}"
   airnow_current_pm25_observed:
     unique_id: sensor.airnow_pm25_aqi
     friendly_name: AirNow PM2.5 Observation
     value_template: "{{ state_attr('sensor.airnow_current_pm25', 'AQI') }}"
   airnow_current_ow_category:
     unique_id: sensor.airnow_o3_category
     friendly_name: AirNow Ozone Category
     value_template: "{{ state_attr('sensor.airnow_current_o3', 'Category')['Name'] }}"
   airnow_current_o3_observed:
     unique_id: sensor.airnow_o3_aqi
     friendly_name: AirNow Ozone Observation
     value_template: "{{ state_attr('sensor.airnow_current_o3', 'AQI') }}"

I surface the resulting AQI numbers in Lovelace with the dual-gauge-card custom card, with color codes based on data from aqicn.org:

type: 'custom:dual-gauge-card'
title: Air Quality
min: 0
max: 400
inner:
  entity: sensor.airnow_current_o3_observed
  label: Ozone
outer:
  entity: sensor.airnow_current_pm25_observed
  label: PM2.5
colors:
  - color: '#8B0000'
    value: 301
  - color: var(--label-badge-purple)
    value: 201
  - color: var(--label-badge-red)
    value: 151
  - color: '#CF5300'
    value: 101
  - color: var(--label-badge-yellow)
    value: 51
  - color: var(--label-badge-green)
    value: 0

Note that the likely reason that AirNow and your weather app agree is that your weather app is sourcing AQI from AirNow.

3 Likes

It works!!! Thanks so much. You are a genius. How did you learn how to do this? I want to learn how to extract data. Any tips how to start learning how to extract data, through API/JSON?

@iamhueman JSON is a way of representing data, and isn’t all that complicated (I think – disclaimer: I do things like this for my day job, so my perspective may be skewed by that).

The harder parts (for me – again, your experience may vary) were learning how to do what I needed within the constraints of HA (which I’m still fairly new to).

A sensor in HA always has a state (sometimes called a value), and it may have additional attributes. Some of the complications here arise from the fact that I’m hacking around this for the rest sensor, and providing a fake “OK” state (see value_template in my paste), and then shoving all the important JSON bits into the attributes.

Converting the JSON fields into attributes is done with the following 2 parameters on the rest sensors:

  • json_attribute_path
  • json_attributes

The first uses “JSONPath” notation (which was new to me) to tell the sensor which part of the JSON we’re interested in. I got this part right by taking the JSON output from AirNow’s query builder and running it through jsonpath.com with various queries until I figured out what I was clever enough to do (or not do). The second part tells HA which JSON fields we actually want our sensor to report.

In the end, you wind up with 3 sensors for each air quality thing(PM2.5, O3, etc) you are tracking; one is the “raw” data from the AirNow API, and the other 2 are the AQI measurement & category/description.

Most of the concepts here will carry over to any other API you try to apply them to (if it uses JSON), but the details of how to use any one API to get the data you want out of it will vary from API to API. AirNow was a fairly easy one to get working in HA without a formal integration, though!

Wow thanks for the very informative explanation! I appreciate it. Very clear.

Also, any idea why my airnow sensor does not report for 10 minutes every hour?? Could this be an issue on the Airnow side?

See attached. Screen Shot 2020-09-02 at 10.36.19 AM

@iamhueman I can’t explain the odd gaps in your data. I don’t have those in my own. It is possible that these map to periods where home assistant restarted, or your internet dropped. I’m also not sure how often your API call is triggering, or if AirNow does any sort of rate limiting.

To dig into this, I’d suggest you try to check through the HA logbook for events relating to the “main” AirNow sensors (which just report “OK” as their primary state, but have attributes for the rest of this) during these periods of time.

Thanks for the tips, have it working as well.

Would love a custom card that shows air quality in an interesting way. E.g. something like this for air quality by hour (or past N days), color coded based on the severity levels. Credit to Nikolay Likomanov for this example image.

How about this?
I’m still trying to find a custom card to display the “Air Quality is xxx” in a better way but I like the rest.

type: vertical-stack
cards:
  - type: horizontal-stack
    cards:
      - entities:
          - entity: sensor.purpleair_description
            name: Air Quality is
            icon: blank
        show_header_toggle: false
        type: entities
  - type: 'custom:canvas-gauge-card'
    card_height: 300
    entity: sensor.purpleair_aqi
    name: ''
    gauge:
      type: radial-gauge
      title: AQI
      width: 300
      height: 300
      minValue: 0
      maxValue: 500
      startAngle: 40
      ticksAngle: 280
      valueBox: true
      majorTicks:
        - '0'
        - '50'
        - '100'
        - '150'
        - '200'
        - '250'
        - '300'
        - '350'
        - '400'
        - '450'
        - '500'
      minorTicks: 10
      strokeTicks: true
      highlights:
        - from: 0
          to: 50
          color: 'rgba(104, 225, 67, .75)'
        - from: 50
          to: 100
          color: 'rgba(255, 255, 85, .75)'
        - from: 100
          to: 150
          color: 'rgba(239, 133, 51, .75)'
        - from: 150
          to: 200
          color: 'rgba(234, 51, 36, .75)'
        - from: 200
          to: 300
          color: 'rgba(140, 26, 75, .75)'
        - from: 300
          to: 500
          color: 'rgba(115, 20, 37, .75)'
      borders: 'no'
      needleType: arrow
      needleWidth: 4
      needleCircleSize: 7
      needleCircleOuter: true
      needleCircleInner: false
      animationDuration: 1500
      animationRule: linear
      valueBoxBorderRadius: 10
      colorValueBoxRect: '#222'
      colorValueBoxRectEnd: '#333'
      valueDec: 0
      valueInt: 0
  - type: 'custom:mini-graph-card'
    entities:
      - sensor.purpleair_aqi
    unit: AQI
    name: AQI from Purple (24h)
    icon: 'mdi:chemical-weapon'
    show:
      fill: true
      legend: false
      labels: false
      name: true
      points: false
      name_adaptive_color: true
      icon_adaptive_color: true
      show_legend: false
    font_size: 75
    line_width: 3
    points_per_hour: 4
    hours_to_show: 24
    color_thresholds:
      - value: 0
        color: '#00e500'
      - value: 50
        color: '#00e500'
      - value: 50.5
        color: '#fffe0a'
      - value: 100
        color: '#fffe0a'
      - value: 100.5
        color: '#fe7f03'
      - value: 150
        color: '#fe7f03'
      - value: 150.5
        color: '#ff0200'
      - value: 200
        color: '#ff0200'
      - value: 200.5
        color: '#98004c'
      - value: 300.5
        color: '#7f0024'

Any have any updates on this? I was using AirVisual but I really dont know how accurate it is as they say there are no sensors in my town. So I am trying to switch to Airnow. one thing with air vis is the card for it is cut and dry and looks NICE. Wish I had something more like it for Airnow.

Try PurpleAir
https://www.purpleair.com/map
You also have the option of getting a PurpleAir sensor yourself to get hyper local information (as I did).
I used a sensor a few miles away until I got my own. There is a PurpleAir thread here that will walk you thru importing the site or sites of your choice.

Your killing me! haha

now another data source that is different. lol My wife is going to be sooo mad when I try to make this purchase.

Improving on the stuff from @torrancew, I did the airnow as a package, so it’s a single file.
I also used the restful thing, so it’s only one HTTP request.

rest:
  - scan_interval: 3600
    resource: !secret airnow_current_url
    sensor:
      - name: temp_airnow_pm2_5
        json_attributes_path: "$.[?(@.ParameterName=='PM2.5')]"
        json_attributes:
          - AQI
          - Category
        value_template: "OK"

      - name: temp_airnow_pm10
        json_attributes_path: "$.[?(@.ParameterName=='PM10')]"
        json_attributes:
          - AQI
          - Category
        value_template: "OK"

      - name: temp_airnow_o3
        json_attributes_path: "$.[?(@.ParameterName=='O3')]"
        json_attributes:
          - AQI
          - Category
        value_template: "OK"
template:
  sensor:
    - name: airnow_pm2_5
      state: "{{ state_attr('sensor.temp_airnow_pm2_5', 'AQI') }}"
    - name: airnow_pm2_5_category
      state: "{{ state_attr('sensor.temp_airnow_pm2_5', 'Category')['Name'] }}"

    - name: airnow_pm10
      state: "{{ state_attr('sensor.temp_airnow_pm10', 'AQI') }}"
    - name: airnow_pm10_category
      state: "{{ state_attr('sensor.temp_airnow_pm10', 'Category')['Name'] }}"

    - name: airnow_o3
      state: "{{ state_attr('sensor.temp_airnow_o3', 'AQI') }}"
    - name: airnow_o3_category
      state: "{{ state_attr('sensor.temp_airnow_o3', 'Category')['Name'] }}"