PurpleAir Air Quality Sensor

Here is HA sensors example and python code. Code is a bit poor, however it works. Hope you can follow:

All PurpleAir sensors visible on the map can be accessed through their json interface with no API key. You simply need to get the ID of the sensor from the HTML code that pops up when you hover over “Get This Widget” on the map. You can then access the data from HomeAssistant via the json API.

The URL to get all the data for a sensor looks like this: https://www.purpleair.com/json?show=68339

You just substitute in the number of the sensor you are interested in to get its data.

1 Like

wow, just wow, so many fantastic people doing wonderful things, this is why I love this community…

I just read through the whole thread, the (local) integration part is fairly comprehensible even for a noob like me, I think I’ll manage once I pull the trigger on this project.

Since I know literally nothing about air pollution topic, I have a more theoretical question just to validate my use case, please help me: I have a ventilation system in my home which sucks air from outside and evenly spreads it in every room in the house, after heavy filtration. Since I’m quite pollen allergic, it helps me a lot, and I love it. But in winter time I have some neighbors who produce heavy smoke for heating sometimes. And obviously my vent system pulls this smoke into the house, but when you can feel the smoke inside it is too late to shut down the vent. I’d like to prevent these cases automatically with a smoke or air quality sensor, but so far I didn’t find a suitable one. This PurpleAir device seems quite robust and reliable to me, but I’m still not sure if it will fit to my needs, can someone please give an opinion on it?

Many thanks in advance!!

From personal experience, I can verify that the PurpleAir will pick up smoke from a fire. My outdoor PurpleAir going up is my first indication my neighbors are burning. I have an indoor and an outdoor unit; and can verify that my filtration fans will knock the air quality back down once ‘bad’ outside air is stopped being brought in.

I’ve had my outside for a few years now and it works great; I’ve only had my indoor for a few months but so far, so good.

1 Like

Thanks a lot for the very quick answer… The speed of the sensor in recognizing smoke is that fast, that you think I could base an automation on it to prevent smoke intake then… Freaking good to hear. Can’t wait for my holiday to the US to pick one sensor up to save the 100 bucks shipping fee to Europe :slight_smile:

I live in California, where we often have wildfires in the fall. I have my (indoor) sensor configured to turn our home’s HVAC system on in recirculate mode whenever the AQI goes above 25 inside.

When a fire starts often it turns on before I even smell the smoke. And I can attest to the fact that a MERV16 filter can reduce the smoke level to a point where it is undetectable by this sensor (or my nose) when the smoke level is not too high (say, below 125AQI or so outside), and still does a great-but-not-perfect job when the smoke is worse outside.

1 Like

One thing to keep in mind is you set how often you get the data from the PurpleAir based on your REST sensor. I poll mine every 5 mins, but you could do it more often if you like. I pull the rolling average and not the exact amount also. I do that to prevent alerts on short term events, like someone smoking near the sensor. For burning fires, the average works well for me. My sensor is 11 feet above the ground and under a cover.

Another thing to think about is using other sensors in you ‘area’; I have not done this yet, but others here have averaged surrounding sensors for input. I think this is handy for large fire issues. I do compare my PurpleAir to others on the map and they definitely track for large fire events. When I say ‘surrounding’ most are 20 miles away from me; the closest is 5 miles. I also find that the PurpleAir map is more accurate than the AirNow map. When I say accurate, I mean fast update times to a rise in air quality index.

1 Like

For my use case - preventing smoke intake via the fan - I think I’d poll it locally like 15-30 seconds to be able to react instantly. About the cover: should I put in under some kind of a roof?

You should not have to put it under a cover, but I had a convenient location. I feel it can’t hurt to give it some protection from the sun. The location it is in also allows for airflow from all directions.

Just a heads up to anyone using this, PurpleAir has retired the /json url and I have reached out to them on how we should proceed moving forward. They use api.purpleair.com now but it appears you need an API key but I wasn’t able to find a way to generate one on their site. I’m awaiting a response via email and will share the information I receive.

Update:
Received an email back from [email protected] with my new read/write API keys. Just email that address with your first and last name and request an API key. I had to update the sensors and templates as the returned data format has changed a little.

- platform: rest
    name: 'PurpleAir'
    #Change the 72291 to whatever station you use
    resource: https://api.purpleair.com/v1/sensors/72291
    headers:
      X-API-Key: ********-****-****-****-************
    scan_interval: 180
    device_class: timestamp
    value_template: >
      {{ state_attr('sensor.purpleair', 'sensor')['last_seen']|timestamp_local(0) }}
    json_attributes:
      - sensor
purpleair_aqi:
        friendly_name: 'PurpleAir AQI'
        unique_id: purpleair_aqi
        # Set this sensor to be the AQI value.
        #
        # Code translated from JavaScript found at:
        # https://docs.google.com/document/d/15ijz94dXJ-YAZLi9iZ_RaBwrZ4KtYeCy08goGBwnbCU/edit#
        value_template: >
          {% macro calcAQI(Cp, Ih, Il, BPh, BPl) -%}
            {{ (((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il)|round }}
          {%- endmacro %}
          {% set pm25 = state_attr('sensor.purpleair', 'sensor')['pm2.5']|float(0) %}
          {% if pm25 > 1000 %}
            invalid
          {% elif pm25 > 350.5 %}
            {{ calcAQI(pm25, 500.0, 401.0, 500.0, 350.5) }}
          {% elif pm25 > 250.5 %}
            {{ calcAQI(pm25, 400.0, 301.0, 350.4, 250.5) }}
          {% elif pm25 > 150.5 %}
            {{ calcAQI(pm25, 300.0, 201.0, 250.4, 150.5) }}
          {% elif pm25 > 55.5 %}
            {{ calcAQI(pm25, 200.0, 151.0, 150.4, 55.5) }}
          {% elif pm25 > 35.5 %}
            {{ calcAQI(pm25, 150.0, 101.0, 55.4, 35.5) }}
          {% elif pm25 > 12.1 %}
            {{ calcAQI(pm25, 100.0, 51.0, 35.4, 12.1) }}
          {% elif pm25 >= 0.0 %}
            {{ calcAQI(pm25, 50.0, 0.0, 12.0, 0.0) }}
          {% else %}
            invalid
          {% endif %}
        unit_of_measurement: "AQI"
purpleair_pm25:
        friendly_name: 'PurpleAir PM 2.5'
        unique_id: purpleair_pm25
        value_template: "{{ state_attr('sensor.purpleair', 'sensor')['pm2.5'] }}"
#        value_template: "{{ state_attr('sensor.purpleair','results')[0]['PM2_5Value'] }}"
        unit_of_measurement: "μg/m3"

I don’t use the other template sensors provided by OP, but they will need to be updated if you use them. I left the previous line in the above sensor as a reference but here’s a quick overview:

  • “results” needs to be changed to “sensor”
  • drop the [0]
  • “PM2_5Value” needs to be changed to “pm2.5”

Here’s a sample of the json output using the new API for reference as well:

{
  "api_version" : "V1.0.10-0.0.17",
  "time_stamp" : 1653674398,
  "data_time_stamp" : 1653674353,
  "sensor" : {
    "sensor_index" : 72291,
    "last_modified" : 1614368627,
    "date_created" : 1601050452,
    "last_seen" : 1653674266,
    "private" : 0,
    "is_owner" : 0,
    "name" : "Clean Air Carolina & ECU - EB Aycock Middle School",
    "icon" : 0,
    "location_type" : 0,
    "model" : "PA-II",
    "hardware" : "2.0+BME280+PMSX003-B+PMSX003-A",
    "led_brightness" : 35,
    "firmware_version" : "7.00",
    "rssi" : -73,
    "uptime" : 15804,
    "pa_latency" : 343,
    "memory" : 16080,
    "position_rating" : 5,
    "latitude" : 35.586803,
    "longitude" : -77.35311,
    "altitude" : 70,
    "channel_state" : 3,
    "channel_flags" : 0,
    "channel_flags_manual" : 0,
    "channel_flags_auto" : 0,
    "confidence" : 100,
    "confidence_auto" : 100,
    "confidence_manual" : 100,
    "humidity" : 69,
    "humidity_a" : 69,
    "temperature" : 80,
    "temperature_a" : 80,
    "pressure" : 1011.6,
    "pressure_a" : 1011.62,
    "analog_input" : 0.01,
    "pm1.0" : 4.7,
    "pm1.0_a" : 5.3,
    "pm1.0_b" : 4.2,
    "pm1.0_atm" : 4.7,
    "pm1.0_cf_1" : 4.7,
    "pm2.5" : 7.4,
    "pm2.5_a" : 8.3,
    "pm2.5_b" : 6.5,
    "pm2.5_atm" : 7.4,
    "pm2.5_cf_1" : 7.4,
    "pm2.5_alt" : 4.9,
    "pm2.5_alt_a" : 5.5,
    "pm2.5_alt_b" : 4.3,
    "pm10.0" : 8.9,
    "pm10.0_a" : 9.7,
    "pm10.0_b" : 8.0,
    "pm10.0_atm" : 8.9,
    "pm10.0_cf_1" : 8.9,
    "scattering_coefficient" : 14.4,
    "scattering_coefficient_a" : 15.1,
    "scattering_coefficient_b" : 13.7,
    "deciviews" : 10.0,
    "deciviews_a" : 10.3,
    "deciviews_b" : 9.7,
    "visual_range" : 143.1,
    "visual_range_a" : 138.6,
    "visual_range_b" : 147.5,
    "0.3_um_count" : 960,
    "0.3_um_count_a" : 1008,
    "0.3_um_count_b" : 913,
    "0.5_um_count" : 283,
    "0.5_um_count_a" : 299,
    "0.5_um_count_b" : 268,
    "1.0_um_count" : 53,
    "1.0_um_count_a" : 62,
    "1.0_um_count_b" : 44,
    "2.5_um_count" : 5,
    "2.5_um_count_a" : 5,
    "2.5_um_count_b" : 5,
    "5.0_um_count" : 1,
    "5.0_um_count_a" : 1,
    "5.0_um_count_b" : 1,
    "10.0_um_count" : 1,
    "10.0_um_count_a" : 1,
    "10.0_um_count_b" : 1,
    "pm1.0_atm_a" : 5.3,
    "pm2.5_atm_a" : 8.32,
    "pm10.0_atm_a" : 9.68,
    "pm1.0_cf_1_a" : 5.3,
    "pm2.5_cf_1_a" : 8.32,
    "pm10.0_cf_1_a" : 9.68,
    "pm1.0_atm_b" : 4.15,
    "pm2.5_atm_b" : 6.46,
    "pm10.0_atm_b" : 8.04,
    "pm1.0_cf_1_b" : 4.15,
    "pm2.5_cf_1_b" : 6.46,
    "pm10.0_cf_1_b" : 8.04,
    "primary_id_a" : 1158951,
    "primary_key_a" : "BPVYP48L8OQGWGFQ",
    "primary_id_b" : 1158953,
    "primary_key_b" : "CO5OUXLQXW2MV6F2",
    "secondary_id_a" : 1158952,
    "secondary_key_a" : "KL54YM2SSVF5B4BL",
    "secondary_id_b" : 1158954,
    "secondary_key_b" : "LZFBH47P4EXH9GT4",
    "stats" : {"pm2.5" : 7.4, "pm2.5_10minute" : 7.2, "pm2.5_30minute" : 6.8, "pm2.5_60minute" : 6.8, "pm2.5_6hour" : 7.5, "pm2.5_24hour" : 7.6, "pm2.5_1week" : 8.9, "time_stamp" : 1653674266},
    "stats_a" : {"pm2.5" : 8.3, "pm2.5_10minute" : 7.5, "pm2.5_30minute" : 7.1, "pm2.5_60minute" : 7.0, "pm2.5_6hour" : 7.9, "pm2.5_24hour" : 8.0, "pm2.5_1week" : 9.5, "time_stamp" : 1653674266},
    "stats_b" : {"pm2.5" : 6.5, "pm2.5_10minute" : 6.8, "pm2.5_30minute" : 6.6, "pm2.5_60minute" : 6.5, "pm2.5_6hour" : 7.1, "pm2.5_24hour" : 7.1, "pm2.5_1week" : 8.3, "time_stamp" : 1653674266}
  }
}%                                        

Should be back in business after getting your API key and making the above updates.

5 Likes

Thanks @afxefx

Some links for information about the migration of the API. Looks to be part of a larger infrastructure change on the part of Purple Air.

Discontinuation of the /json and /data.json urls - PurpleAir API - PurpleAir Community
Redirecting...

2 Likes

Other changes in the API:

  1. If you are accessing a private purpleair sensor, the CGI parameter of the read key changed from ‘key=’ to ‘read_key=’ and needs to be passed in the URL, and not as a header like your API Key.

  2. The temperature field changed from ‘temp_f’ to ‘temperature’ (still in F)

Here’s the curl command to use to test your access. Leave off the ‘?read_key=<16digitReadKey>’ if you’re accessing a public sensorid. Obviously substitute your actual API Key, , and optional <16digitReadKey>

curl -H 'X-API-Key: ********-****-****-****-************ '  'https://api.purpleair.com/v1/sensors/<sensorId>?read_key=<16digitReadKey>'

Instead of making the primary RESTful sensor a “device_class: timestamp” I have its value set to be the AQI value, like so. I’m not sure what value there is to making the device class a timestamp. @afxefx ??

Example:

- platform: rest
  name: "PurpleAir"
  resource: https://api.purpleair.com/v1/sensors/55831
  headers:
    X-API-Key: ********-****-****-****-************
  scan_interval: 900
  value_template: >
    {% macro calcAQI(Cp, Ih, Il, BPh, BPl) -%}
      {{ (((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il)|round }}
    {%- endmacro %}
    {% set pm25 = value_json['sensor']['pm2.5']|float(0) %}
    {% if pm25 > 1000 %}
      invalid
    {% elif pm25 > 350.5 %}
      {{ calcAQI(pm25, 500.0, 401.0, 500.0, 350.5) }}
    {% elif pm25 > 250.5 %}
      {{ calcAQI(pm25, 400.0, 301.0, 350.4, 250.5) }}
    {% elif pm25 > 150.5 %}
      {{ calcAQI(pm25, 300.0, 201.0, 250.4, 150.5) }}
    {% elif pm25 > 55.5 %}
      {{ calcAQI(pm25, 200.0, 151.0, 150.4, 55.5) }}
    {% elif pm25 > 35.5 %}
      {{ calcAQI(pm25, 150.0, 101.0, 55.4, 35.5) }}
    {% elif pm25 > 12.1 %}
      {{ calcAQI(pm25, 100.0, 51.0, 35.4, 12.1) }}
    {% elif pm25 >= 0.0 %}
      {{ calcAQI(pm25, 50.0, 0.0, 12.0, 0.0) }}
    {% else %}
      invalid
    {% endif %}
  unit_of_measurement: "AQI"
  json_attributes:
    - sensor
2 Likes

Hello all. I have been following this thread a long time, and have used several of the HA integrations that have been forked from the original @gibwar built. I live in Northern California, and given all the wildfire smoke, the HA purple air integration makes it easy to add several sensors to my dashboard.

@gibwar has updated his code to use the new Purple Air V1 API. It will prompt you for your Purple Air API Read Key. (If you need Purple Air API keys send an email to [email protected])

To install things… I was able to download the zip file from Joshua / home-assistant-purpleair · GitLab site, and copy the extracted purpleair directory into the custom_components directory on my HA install. Then I went to the Settings → Devices & Services and then clicked on the Add Integration button, chose purpleair and I was prompted to enter my Purple Air API read key and the number of the Purple Air sensor I wanted included…

I am getting AQI readings that are different from the Purple Air site, but I will dig into that later…

Thanks to @gibwar for updating the code, and making it easy to add Purple Air sensors to the HA dashboard utilizing the new APIs!

-Bill

1 Like

Yeah I added also and the stats showing on integration is not the same on map :confused:

This looks to be why the purple air integration values may be different than PurpleAir map values:

“Additionally, the calculated AQI uses a rolling history, and may not be
exactly accurate compared to the EPA AirNow map or the PurpleAir map
with appropriate adjustments. This is due to the AQI calculation using a
bucket for calculating the value for a given hour, rather than a live,
updating value.”

hi, is there anyway to use this API for public data WITHOUT an API? i don’t have a purpleair device and was wondering if there are any options to consume the public data on their website. thanks

it gives an error, any ideas?

Error: Server Error

The server encountered an error and could not complete your request.

Please try again in 30 seconds.

to answer my own question:

Public API discontinued

1 Like

Joshua updated the integration to version 3.1.0 to better reflect the EPA correction.

In case anyone is looking for a custom component for local purple air devices, I modified the one linked above and put it here: https://github.com/catchdave/home-assistant-purpleair

It also incorporates some adjustments that match purple air (temp, humidity, dewpoint) changes (e.g. subtract 8F from temp reading).

I removed all purpleair website connectivity to make this integration a true local integration. Hope someone else finds it useful.

1 Like