For now, I just pasted the yaml from Post #1 in this thread into configuration.yaml and changed the url
I wish I’d read the entire thread before I went hunting to find the ID of the station nearest to me. I basically tried random IDs increasing from the 37027 one earlier in this thread and looked at the create time in the json result. I knew the sensor I wanted was created 8/31/20 and I knew the name from the purple air map. I managed to work my way up in leaps and found 63835 created 8/31/20. A little script later searching the ones numerically up and down where the create date was still 8/31 turned up the one I wanted.
FYI - 435 new IDs were created on 8/31 - most often in pairs as each PurpleAir device has two sensors, each with a different ID.
I thought I’d share my icon choices for the items that didn’t have anything specific. I’m curious what others may have used.
### customize.yaml
### AQI
sensor.purpleair_description:
icon: mdi:lungs
sensor.purpleair_aqi:
icon: mdi:chemical-weapon
sensor.purpleair_pm25:
icon: mdi:grain
For those that utilize Grafana:
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 1,
"links": [],
"panels": [
{
"datasource": "HomeAssistant",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"max": 500,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "green",
"value": 0
},
{
"color": "#EAB839",
"value": 51
},
{
"color": "orange",
"value": 101
},
{
"color": "red",
"value": 151
},
{
"color": "purple",
"value": 201
},
{
"color": "dark-purple",
"value": 301
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 6,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.2.0",
"targets": [
{
"alias": "",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"measurement": "AQI",
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "last"
}
]
],
"tags": []
}
],
"timeFrom": null,
"timeShift": null,
"title": "Air Quaility",
"transparent": true,
"type": "gauge"
},
{
"aliasColors": {
"AQI": "blue"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "HomeAssistant",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"max": 500,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "green",
"value": 0
},
{
"color": "#EAB839",
"value": 51
},
{
"color": "orange",
"value": 101
},
{
"color": "red",
"value": 151
},
{
"color": "purple",
"value": 201
},
{
"color": "dark-purple",
"value": 301
}
]
}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"hiddenSeries": false,
"id": 7,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.0",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "AQI",
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"measurement": "AQI",
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "last"
}
]
],
"tags": []
}
],
"thresholds": [
{
"colorMode": "ok",
"fill": false,
"line": true,
"op": "gt",
"value": 0,
"yaxis": "left"
},
{
"colorMode": "custom",
"fill": false,
"fillColor": "rgba(51, 162, 229, 0.2)",
"line": true,
"lineColor": "#FADE2A",
"op": "gt",
"value": 51,
"yaxis": "left"
},
{
"colorMode": "custom",
"fill": false,
"fillColor": "rgba(51, 162, 229, 0.2)",
"line": true,
"lineColor": "#FF9830",
"op": "gt",
"value": 101,
"yaxis": "left"
},
{
"colorMode": "custom",
"fill": false,
"fillColor": "rgba(51, 162, 229, 0.2)",
"line": true,
"lineColor": "#F2495C",
"op": "gt",
"value": 151,
"yaxis": "left"
},
{
"colorMode": "custom",
"fill": false,
"fillColor": "rgba(51, 162, 229, 0.2)",
"line": true,
"lineColor": "#B877D9",
"op": "gt",
"value": 201,
"yaxis": "left"
},
{
"colorMode": "custom",
"fill": false,
"fillColor": "rgba(51, 162, 229, 0.2)",
"line": true,
"lineColor": "#8F3BB8",
"op": "gt",
"value": 301,
"yaxis": "left"
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Air Quaility",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"transparent": true,
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "5m",
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Environmental",
"uid": "hmhTkNOMk",
"version": 10
}
How is the local API working for you?
I will be getting a PurpleAir device probably a PurpleAir PA-II-SD and a maybe a PurpleAir PA-I-Indoor.
I’m curious what model you have and if anyone else knows if the local API is the same for all the devices (at lease for the sensors that device contains).
Just wanted to share how I’m using the PurpleAir sensors. Like many of you I’m just using the REST sensor. My neighbor has an outdoor PurpleAir so I use the public API to grab that data. I then bought an indoor PurpleAir and use the local API to get that data.
For the outdoor sensor I use different data than most. I found using the PM2_5Value
, which is using the real-time data, made the sensor too “wobbly.” Since I had actions tied to it’s value weird things would happen when there was a sudden change in the data. I found buried in the json data the 10 minute average of the sensor. Using that helped smooth out the data. Unfortunately, the data is not proper json. Its actually a string. But with a bit of string parsing in the template I was able to get at the value. I also decided to assign the AQI value to a variable in the template, for two reasons, to make it more readable, and easier to switch back to the real-time value if I wanted.
- platform: rest
name: 'PurpleAir'
resource: https://www.purpleair.com/json?show=19269
scan_interval: 60
force_update: true
value_template: >
{% macro calcAQI(Cp, Ih, Il, BPh, BPl) -%}
{{ (((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il)|round }}
{%- endmacro %}
# Use the 10m average of PM25 and assign it to variable
{% set pm25 = value_json.results[0].Stats.split(',')[1].split(':')[1] %}
{% if (pm25|float) > 1000 %}
invalid
{% elif (pm25|float) > 350.5 %}
{{ calcAQI((pm25|float), 500.0, 401.0, 500.0, 350.5) }}
{% elif (pm25|float) > 250.5 %}
{{ calcAQI((pm25|float), 400.0, 301.0, 350.4, 250.5) }}
{% elif (pm25|float) > 150.5 %}
{{ calcAQI((pm25|float), 300.0, 201.0, 250.4, 150.5) }}
{% elif (pm25|float) > 55.5 %}
{{ calcAQI((pm25|float), 200.0, 151.0, 150.4, 55.5) }}
{% elif (pm25|float) > 35.5 %}
{{ calcAQI((pm25|float), 150.0, 101.0, 55.4, 35.5) }}
{% elif (pm25|float) > 12.1 %}
{{ calcAQI((pm25|float), 100.0, 51.0, 35.4, 12.1) }}
{% elif (pm25|float) >= 0.0 %}
{{ calcAQI((pm25|float), 50.0, 0.0, 12.0, 0.0) }}
{% else %}
invalid
{% endif %}
unit_of_measurement: "AQI"
json_attributes:
- Stats
- PM2_5Value
- LastSeen
- LastUpdateCheck
- Label
json_attributes_path: "$.results[0]"
For the indoor sensor that data that is returned is the from the local API is different. I have access to the AQI directly but it is a real-time value as far as I can tell. PurpleAir says they are updating the FAQ to include details of the local API, so hopefully I can get a better definition of what the value actually is.
- platform: rest
name: 'AQI Indoors'
resource: http://<Internal IP>/json
scan_interval: 30
force_update: true
value_template: '{{ value_json["pm2.5_aqi"] }}'
unit_of_measurement: "AQI"
I also tied the indoor AQI to the fan of the air handler in my HVAC system. It also gave me a use case for the new wait_for_trigger
action options for automation. It’s not working quite right. I think it has to do with getting to the timeout value. But that is a discussion for another thread.
- alias: HVAC Dirty Air Cycle
trigger:
platform: numeric_state
entity_id: sensor.aqi_indoors
above: 50
condition:
- condition: state
entity_id: fan.whole_house
state: 'off'
- condition: numeric_state
entity_id: sensor.open_window_count
below: 1
action:
- service: climate.set_fan_mode
data:
entity_id: climate.my_ecobee
fan_mode: 'on'
- wait_for_trigger:
- platform: numeric_state
entity_id: sensor.aqi_indoors
below: 20
timeout:
minutes: 60
continue_on_timeout: true
- service: climate.set_fan_mode
data:
entity_id: climate.my_ecobee
fan_mode: 'auto'
This is the information I wanted; thank you.
I was planing to do something similar for my exhaust fans whether or not to run based on a comparison of indoor and outdoor air quality.
I currently have one of my three exhaust fans set to an automation (in HA) based on humidity.
I still have my ‘main’ exhaust fan set on a every 6 hour schedule, this is the one I will hamper based on air quality. I am waiting for my own PurpleAir before I implement since the outdoor one I am using is several miles away.
Notifications for Air Quality sent to mine and my wife’s phones.
All of this being based on the custom sensor from above.
- id: '1602015312411'
alias: Air Quality Notification 1 Good
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Good
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Good
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Good
mode: single
- id: '1602009421488'
alias: Air Quality Notification 2 Moderate
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Moderate
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Moderate
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Moderate
mode: single
- id: '1602017331248'
alias: Air Quality Notification 3 Unhealthy for Sensitive Groups
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
for: 0:10:00
to: Unhealthy for Sensitive Groups
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Unhealthy for Sensitive Groups
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Unhealthy for Sensitive Groups
mode: single
- id: '1602017478518'
alias: Air Quality Notification 4 Unhealthy
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Unhealthy
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Unhealthy
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Unhealthy
mode: single
- id: '1602017682704'
alias: Air Quality Notification 5 Very Unhealthy
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Very Unhealthy
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Very Unhealthy
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Very Unhealthy
mode: single
- id: '1602017729305'
alias: Air Quality Notification 6 Hazardous
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Hazardous
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Hazardous
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Hazardous
mode: single
- id: '1602035435658'
alias: Air Quality Notification 7 Very Hazardous
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Very Hazardous
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is Very Hazardous
- service: notify.mobile_app_sm_g950u
data:
message: Air Quaility is Very Hazardous
mode: single
Try to reload the integrations page - I had the same problem, then purple showed up in the list after restart and browser reload.
The internal JSON API appears to return the 120sec by default (?live=false
). If you want real time, just add ?live=true
I noticed PurpleAir added a US EPA conversion to their list of available conversions. I find it better tracks the EPA ratings with wildfire smoke. Specifically LRAPA seems to reasonably match AirNow official sensors for lower smoke levels, but for extreme smoke levels (AirNow AQI 180+), LRAPA seems to come in much too low.
I might play around with switching to the PurpleAir US EPA conversion formula, or maybe update the component to provide multiple conversions at once.
Here’s the listed conversions with the formulas they use as of today 2020-10-10:
US EPA: Courtesy of the United States Environmental Protection Agency Office of Research and Development, correction equation from their US wide study validated for wildfire and woodsmoke. 0-250 ug/m3 range (>250 may underestimate true PM2.5):PM2.5 (µg/m³) = 0.534 x PA(cf_1) - 0.0844 x RH + 5.604
AQandU: Courtesy of the University of Utah, conversion factors from their study of the PA sensors during winter in Salt Lake City. Visit their web site. PM2.5 (µg/m³) = 0.778 x PA + 2.65
LRAPA: Courtesy of the Lane Regional Air Protection Agency, conversion factors from their study of the PA sensors. Visit their web site. 0 - 65 µg/m³ range:LRAPA PM2.5 (µg/m³) = 0.5 x PA (PM2.5 CF=ATM) – 0.66
Simplified … a little (all in one automation vice several),
I could not come up with a simple way to simply the state change without getting it for every value change in AQI.
- id: '1602015312411'
alias: Air Quality Notification Change
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
to: Good
for: 0:10:00
- platform: state
entity_id: sensor.purpleair_description
to: Moderate
for: 0:10:00
- platform: state
entity_id: sensor.purpleair_description
to: Unhealthy for Sensitive Groups
for: 0:10:00
- platform: state
entity_id: sensor.purpleair_description
for: 0:10:00
to: Unhealthy
- platform: state
entity_id: sensor.purpleair_description
for: 0:10:00
to: Very Unhealthy
- platform: state
entity_id: sensor.purpleair_description
for: 0:10:00
to: Hazardous
- platform: state
entity_id: sensor.purpleair_description
to: Very Hazardous
for: 0:10:00
condition:
- condition: time
before: '20:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is {{ states('sensor.purpleair_description') }} at AQI
{{ states('sensor.purpleair_aqi') }}
mode: single
Temp sensor is off by 8 degrees! Easy fix:
value_template: "{{ state_attr('sensor.purpleair','results')[0]['temp_f'] | int - 8}}"
I just recieved teh PurpleAir PA-I-Indoor sensor and set it up. The local api works great, it’s just a rest call to http://x.x.x.x/json?live=true
This is even more simplified for an automation. I mis-read the description for leaving state blank, and have verified that this works without extra notifications.
This will send a notification between the hours of 0700-2100 for any change in air quality (i.e. from Good to Moderate; the change must be for at least 10 minutes to prevent spurious notifications). I have a separate automation to send a notification at 0700 reporting the air quality at that time.
- id: '1602977146994'
alias: Air Quality Notification Change (Simple)
description: ''
trigger:
- platform: state
entity_id: sensor.purpleair_description
for: 0:10:00
condition:
- condition: time
before: '21:00:00'
after: 07:00:00
action:
- service: notify.mobile_app_gphone
data:
message: Air Quaility is {{ states('sensor.purpleair_description') }} at AQI
{{ states('sensor.purpleair_aqi') }}
mode: single
Thanks, now I am more confident in getting both an indoor and an outdoor sensor.
As much as possible I try to get things that have local control; it does not bother me if it has cloud access but I don’t like the things that require it. I would have gotten this anyway but I feel better knowing it is accessible locally.
@ozczecho could you give a little more detail.
I have tried the canvas gauge in the past and just tried again but have not been successful with it, that is why I went with an imported grafana graph.
I installed via HACS and tried to put in as both a manual card and a picture entry; but with no luck. I can only get a “No card type configured.” I am sure it is something simple, but it eludes me at this time.
I tried your example and also the examples from the github page, same result.
I tried on at least HA 115 and 116.
Hi @GlennHA,
I am currently on HA 0.115. I don’t use HACS - so I cannot help there.
I copied the source file canvas-gauge-card.js
into /www/custom-lovelace
In my configuration.yaml
, I have:
lovelace:
mode: yaml
resources:
- url: /local/custom-lovelace/canvas-gauge-card.js?v=1.2
type: module
And then
- type: vertical-stack
title: A stack of cards
cards:
- type: custom:canvas-gauge-card
card_height: 200
entity: sensor.purpleair
name: AQI reading
gauge:
type: "radial-gauge"
title: AQI
width: 200
height: 200
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: 2
valueInt: 2
I hope that helps .
I am finding your discussion on Purple AQI interesting and helpful, thanks. I am experimenting with ways to present Purple AQI data. I have found the custom lovelace mini-graph-card to offer some interesting options for visualizing this data. Code and examples below:
# 24 hour average
- type: custom:mini-graph-card
entities:
- sensor.purple_aqi_sb_average_current
unit: " "
name: "Santa Barbara AQI from Purple (24h)"
icon: mdi:smog
show:
fill: false
legend: false
labels: false
name: true
points: true
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' # green
- value: 50.42
color: '#fffe0a' # yellow
- value: 147.92
color: '#fe7f03' # orange
- value: 231.25
color: '#ff0200' # red
- value: 627.1
color: '#98004c' # purple
- value: 1043.75
color: '#7f0024' # dark purple
- value: 1460.42
color: '#7f0024' # dark purple
# 7 day min max graph
- type: custom:mini-graph-card
entities:
- entity: sensor.purple_aqi_sb_average_current
aggregate_func: max
name: Max
color: #e74c3c
- entity: sensor.purple_aqi_sb_average_current
aggregate_func: min
name: Min
- entity: sensor.purple_aqi_sb_average_current
aggregate_func: avg
name: Avg
color: green
name: Santa Barbara AQI from Purple (7d)
icon: mdi:smog
hours_to_show: 168
group_by: date
Thank you, I finally got it…
I had an indention issue, one little mistake causes many hours of beating your head into the wall