PurpleAir Air Quality Sensor

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

just came to say thanks! works like a charm. Since I live in Europe, wish it would report in Celsius instead of Fahrenheit, I couldn’t find a way to change it on the device…

Nevertheless, kudos for the custom component…

Anyone tried to build a nice fancy lovelace dashboard for visualizing data coming from the sensor in a meaningful way?

dear @catchdave

I was using you custom component for quite some days without issues, but since yesterday I started having problems, the integration stopped communicating with my device, even tough my device is still accessible via LAN without issues, I can’t figure out what could be the problem…

These are the log entries I’m getting, did anyone else experience similar, and know hows to resolve it?

2022-08-17 11:26:16.694 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/config/custom_components/purpleair/PurpleAirApi.py", line 179, in _update
    'current_temp_raw': result['current_temp_f'],
KeyError: 'current_temp_f'

Edit, additional info:
could the problem be related to received values? I noticed, that a construction site close to me caused the values to skyrocket:

Edit2: hm, something could have happened with my device?? Did it get faulty so soon? I just realised that PurpleAir Map reports that my device:

  • temp, humidity and pressure sensor is not detected (I also do not see any relevant data in the json locally)
  • confidence is ZERO, channel A reporting is bad


Yeah it’s likely your device. The error you mentioned simply means an expected value in the API is missing.

If you browse to 192.168.x.x/json?live=false (where 192.168.x.x is the IP of your device), you’ll probably see current_temp_f is missing or malformed.

Based on your edit—I’d check the device for physical stuff in tiur sensors—maybe blow on it with compressed air to clear whatever is clogging the sensor.

1 Like