Solved: Airvisual Node Pro Air Quality Monitor Support

With the recent addition of support for the Awair air quality monitor, I’m feeling left out with my AirVisual Node Pro air quality monitor. Home Assistant has supported the API of AirVisual for public monitoring stations for some time, but hasn’t yet integrated to the air quality monitors that you can put in your home (which to me are far more useful for deciding if you need to run an air purifier or not). SmartThings has already had an integration for some time

The API is very straightforward, just an API key to a web service that returns all the data in JSON. A decent programmer could whip it up very quickly. Even I could maybe do it if someone wanted to help me with the nitty gritty of how to get it included in Home Assistant, I’ve never done a project on GitHub or for Home Assistant.

Any help or advice would be appreciated.

I’m the developer of the AirVisual integration in HASS. I’d be happy to update the platform for your use case. Let me take a look at the API docs and see what I can whip up.

Great!

I spent a few hours last night trying to hollow out your integration and plug in just the little amount of code it would take to return the values from the node to homeassistant, and made a small working sample like

import urllib.request, json
myurl = “API_URL_FROM_AIRVISUAL”
with urllib.request.urlopen(myurl) as url:
data = json.loads(url.read().decode())
co_level=data[‘current’][‘co’]

and started paring out the stuff I wouldn’t need from your integration and putting mine in, but quickly got lost in the intricacies of the home assistant wrappers and working in PyPi and GitHub. There was a lot there I didn’t yet understand It would take me quite some time to skill up on all that, I’m sure you will do it far quicker and bette than I ever could, or probably even than the amount of help someone would have to give me to do it.

But I’m a good tester, I can at least do that :slight_smile:

No worries! Love the fact that you’re getting involved. How about this: once I have what I think is the correct integration place, I can pass along to you and you can actually test it with your account. Does that work?

Of course, that should be the minimum I would offer ;-). I have 3 nodes to test, 2 indoor and an outdoor, so should get a pretty good sampling.

Just to confirm: do you have a Startup API key? The default, free one – Community – can’t do individual stations. If so, could I use that for some testing?

You don’t need any paid or special API to access the node. You get an API link for the node once it’s setup in your account. The process is described here. To my knowledge, this is a completely separate API from the public one you are currently using (though the JSON data structure has some similarities). I don’t think you will be able to access it via the API for the public node, hence why I was thinking to make a totally separate integration for it… It’s a far simpler API than the one you’ve already integrated to, but it is also different enough that it will have to be written separate from the other one. I’ve sent you an API key by PM.

Hey, did you get the link I sent you? Have you been able to access the device? The code I sent you previously worked for me.

Yes, I got it and it works for me. I didn’t get a chance to work on this through the holidays, but will aim to get there soon. Thanks!

1 Like

Cool, no hurry, just wanted to make sure you had everything you needed.

1 Like

Hi Folks, any update about this ? I’m trying to do the same think : display my Air Visual monitor AQI on my Home Assistant.

Thanks!

1 Like

After 2 days of research and tests, I finally did it !
To give feedbacks on this if anyone wants to do the same. The API do not give the AQI, only the measurements so you need to get the data from the files shared by the AirVisual node using samba.

  1. I mounted the samba share on hassio using fstab : https://help.ubuntu.com/community/Samba/SambaClientGuide

  2. I created an shell_command to copy the 201903_AirVisual_values.txt into Latest_AirVisual_values.txt

shell_command:
  update_airvisual_data: "cp /config/airvisual/{{ now().strftime('%Y%m') }}_AirVisual_values.txt /config/airvisual/Latest_AirVisual_values.txt"
  1. Create automation to execute this shell_command periodically (every 5min for me):
- id: 'update_airvisualnode_data'
  alias: 'Update AirVisual node data'
  trigger:
    minutes: '/5'
    platform: time_pattern
  action:
  - service: shell_command.update_airvisual_data
  initial_state: 'on'
  1. Use the File Sensor to extract information, here is my configuration:
  - platform: file
    name: Airvisual node US AQI
    file_path: /config/airvisual/Latest_AirVisual_values.txt
    value_template: '{{ value.split(";")[4] }}'
    unit_of_measurement: 'AQI'

We cannot use template into file_path, that’s why I needed a predefined file_path that cannot change.
I can fully recommand you this custom card to display the AQI : https://github.com/dnguyen800/Air-Visual-Card

1 Like

Cool, that’s one way to do it… But the Airvisual Node API definitely provides AQI data directly from the node in real time, it comes through as aqius and aqicn. Below is the feed from my airvisual node API. I’d still rather see a component based on the API.

{"p2_sum":15.2,"p2_count":12,"p1_sum":0,"p1_count":12,"p01_sum":0,"p01_count":12,"co_sum":4783,"co_count":12,"hm_sum":631,"hm_count":12,"tp_sum":117.3,"tp_count":12,"voc_sum":0,"voc_count":12,"ts":"2019-03-05T09:00:00.000Z","outdoor_station":{"ts":"2019-03-05T09:00:00.000Z","aqius":31,"mainus":"o3","aqicn":24,"maincn":"o3","p2":{"conc":6,"aqius":25,"aqicn":9},"p1":{"conc":3,"aqius":3,"aqicn":3},"o3":{"conc":75.2,"aqius":31,"aqicn":24},"api_id":"8262"},"outdoor_weather":{"ts":"2019-03-05T09:00:00.000Z","hu":60,"ic":"04d","pr":1005,"tp":6,"wd":250,"ws":10.8}},{"p2_sum":18,"p2_count":11,"p1_sum":0,"p1_count":11,"p01_sum":0,"p01_count":11,"co_sum":4394,"co_count":11,"hm_sum":617,"hm_count":11,"tp_sum":96.10000000000001,"tp_count":11,"voc_sum":0,"voc_count":11,"ts":"2019-03-05T08:00:00.000Z","outdoor_station":{"ts":"2019-03-05T08:00:00.000Z","aqius":30,"mainus":"o3","aqicn":23,"maincn":"o3","p2":{"conc":5,"aqius":21,"aqicn":7},"p1":{"conc":4,"aqius":4,"aqicn":4},"o3":{"conc":72.4,"aqius":30,"aqicn":23},"api_id":"8262"},"outdoor_weather":{"ts":"2019-03-05T08:00:00.000Z","hu":65,"ic":"02d","pr":1004,"tp":5,"wd":240,"ws":10.3}},{"p2_sum":23,"p2_count":12,"p1_sum":0,"p1_count":12,"p01_sum":0,"p01_count":12,"co_sum":4784,"co_count":12,"hm_sum":733,"hm_count":12,"tp_sum":91.49999999999999,"tp_count":12,"voc_sum":0,"voc_count":12,"ts":"2019-03-05T07:00:00.000Z","outdoor_station":{"ts":"2019-03-05T07:00:00.000Z","aqius":27,"mainus":"o3","aqicn":21,"maincn":"o3","p2":{"conc":2,"aqius":8,"aqicn":3},"p1":{"conc":5,"aqius":5,"aqicn":5},"o3":{"conc":67,"aqius":27,"aqicn":21},"api_id":"8262"},"outdoor_weather":{"ts":"2019-03-05T07:00:00.000Z","hu":68,"ic":"02d","pr":1004,"tp":4,"wd":240,"ws":9.8}},{"p2_sum":43,"p2_count":18,"p1_sum":0,"p1_count":18,"p01_sum":0,"p01_count":18,"co_sum":7158,"co_count":18,"hm_sum":1265,"hm_count":18,"tp_sum":112,"tp_count":18,"voc_sum":0,"voc_count":18,"ts":"2019-03-05T06:00:00.000Z","outdoor_station":{"ts":"2019-03-05T06:00:00.000Z","aqius":30,"mainus":"o3","aqicn":23,"maincn":"o3","p2":{"conc":3,"aqius":12,"aqicn":4},"p1":{"conc":7,"aqius":6,"aqicn":7},"o3":{"conc":72.6,"aqius":30,"aqicn":23},"api_id":"8262"},"outdoor_weather":{"ts":"2019-03-05T06:00:00.000Z","hu":69,"ic":"01d","pr":1004,"tp":4,"wd":250,"ws":12.3}},

Have a closer look, the aqicn and aqius come from the outdoor_station node.
Plus, I already ask to the AirVisual API support team and here is their answer :

My node that I got that data from is an outdoor station… Here is from an indoor one at my office… Outdoor station by my work never got below 20 today.

07T19:00:00.000Z","aqius":29,"mainus":"p2","aqicn":13,"maincn":"p1","p2":{"conc":7,"aqius":29,"aqicn":10,"estimated":1},"p1":{"conc":13,"aqius":12,"aqicn":13},"n2":{"conc":21.2,"aqius":3,"aqicn":11},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T19:00:00.000Z","hu":66,"ic":"02n","pr":1005,"tp":9,"wd":220,"ws":9.3}},{"p2_sum":19,"p2_count":11,"p1_sum":0,"p1_count":11,"p01_sum":0,"p01_count":11,"co_sum":4789,"co_count":11,"hm_sum":306,"hm_count":11,"tp_sum":269.20000000000005,"tp_count":11,"voc_sum":0,"voc_count":11,"ts":"2019-03-07T18:00:00.000Z","outdoor_station":{"ts":"2019-03-07T18:00:00.000Z","aqius":45,"mainus":"p2","aqicn":21,"maincn":"p1","p2":{"conc":11,"aqius":45,"aqicn":16,"estimated":1},"p1":{"conc":21,"aqius":19,"aqicn":21},"n2":{"conc":27,"aqius":4,"aqicn":14},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T18:00:00.000Z","hu":66,"ic":"02n","pr":1004,"tp":9,"wd":210,"ws":6.2}},{"p2_sum":25,"p2_count":12,"p1_sum":0,"p1_count":12,"p01_sum":0,"p01_count":12,"co_sum":5627,"co_count":12,"hm_sum":336,"hm_count":12,"tp_sum":294.5,"tp_count":12,"voc_sum":0,"voc_count":12,"ts":"2019-03-07T17:00:00.000Z","outdoor_station":{"ts":"2019-03-07T17:00:00.000Z","aqius":59,"mainus":"p2","aqicn":30,"maincn":"p1","p2":{"conc":16,"aqius":59,"aqicn":23,"estimated":1},"p1":{"conc":30,"aqius":27,"aqicn":30},"n2":{"conc":24.3,"aqius":4,"aqicn":12},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T17:00:00.000Z","hu":61,"ic":"01n","pr":1003,"tp":10,"wd":210,"ws":8.2}},{"p2_sum":23,"p2_count":12,"p1_sum":0,"p1_count":12,"p01_sum":0,"p01_count":12,"co_sum":5954,"co_count":12,"hm_sum":333,"hm_count":12,"tp_sum":295.3,"tp_count":12,"voc_sum":0,"voc_count":12,"ts":"2019-03-07T16:00:00.000Z","outdoor_station":{"ts":"2019-03-07T16:00:00.000Z","aqius":61,"mainus":"p2","aqicn":30,"maincn":"p1","p2":{"conc":17,"aqius":61,"aqicn":24,"estimated":1},"p1":{"conc":30,"aqius":27,"aqicn":30},"n2":{"conc":22.4,"aqius":4,"aqicn":11},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T16:00:00.000Z","hu":61,"ic":"01d","pr":1002,"tp":10,"wd":200,"ws":6.2}},{"p2_sum":22,"p2_count":11,"p1_sum":0,"p1_count":11,"p01_sum":0,"p01_count":11,"co_sum":5265,"co_count":11,"hm_sum":298,"hm_count":11,"tp_sum":270.29999999999995,"tp_count":11,"voc_sum":0,"voc_count":11,"ts":"2019-03-07T15:00:00.000Z","outdoor_station":{"ts":"2019-03-07T15:00:00.000Z","aqius":70,"mainus":"p2","aqicn":34,"maincn":"p1","p2":{"conc":21,"aqius":70,"aqicn":30,"estimated":1},"p1":{"conc":34,"aqius":31,"aqicn":34},"n2":{"conc":19.3,"aqius":3,"aqicn":10},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T15:00:00.000Z","hu":57,"ic":"01d","pr":1002,"tp":11,"wd":210,"ws":9.3}},{"p2_sum":41,"p2_count":12,"p1_sum":0,"p1_count":12,"p01_sum":0,"p01_count":12,"co_sum":5633,"co_count":12,"hm_sum":324,"hm_count":12,"tp_sum":292.90000000000003,"tp_count":12,"voc_sum":0,"voc_count":12,"ts":"2019-03-07T14:00:00.000Z","outdoor_station":{"ts":"2019-03-07T14:00:00.000Z","aqius":70,"mainus":"p2","aqicn":38,"maincn":"p1","p2":{"conc":21,"aqius":70,"aqicn":30,"estimated":1},"p1":{"conc":38,"aqius":35,"aqicn":38},"n2":{"conc":25.8,"aqius":4,"aqicn":13},"api_id":"8275"},"outdoor_weather":{"ts":"2019-03-07T14:00:00.000Z","hu":54,"ic":"10d","pr":1002,"tp":12,"wd":240,"ws":11.3}},

Ah yes, now I see what you are saying, it includes the PM2.5 and PM10 numbers directly as p2 and p1 rather than the AQI… More details below.

http://support.airvisual.com/knowledgebase/articles/1827346-node-pro-api-returned-json-format

You would have to do a little math to derive the AQI.

Here is the AQI scale and one of many online calculators which you could probably leverage as a web service to get AQI from the PM2.5 and PM10 numbers (nice to get PM10 too as the node doesn’t have that in it’s UI).

That was the historical data which would be easy to convert… For real time it just gives you how many PM counts it gets for each 5 minute time slice, so you’d have to total those up and divide by the amount of time and do the conversion to AQI with each new slice… So a bit of work…

{"settings":{"node_name":"work"},"current":{"ts":"2019-03-07T20:16:55.037Z","tp":24,"hm":28,"p2":1,"co":413},"historical":{"instant":[{"ts":"2019-03-07T20:16:55.037Z","tp":24,"hm":28,"p2":1,"co":413},{"ts":"2019-03-07T20:01:42.016Z","tp":24,"hm":28,"p2":1,"co":414},{"ts":"2019-03-07T19:46:32.132Z","tp":24.1,"hm":28,"p2":3,"co":418},{"ts":"2019-03-07T19:31:22.968Z","tp":24.2,"hm":28,"p2":2,"co":419},{"ts":"2019-03-07T19:16:12.253Z","tp":24.3,"hm":28,"p2":2,"co":422},{"ts":"2019-03-07T19:01:02.103Z","tp":24.4,"hm":28,"p2":2,"co":426},{"ts":"2019-03-07T18:55:52.195Z","tp":24.5,"hm":28,"p2":1,"co":427},{"ts":"2019-03-07T18:50:42.103Z","tp":24.5,"hm":28,"p2":2,"co":428},{"ts":"2019-03-07T18:45:33.016Z","tp":24.4,"hm":28,"p2":2,"co":430},{"ts":"2019-03-07T18:40:22.177Z","tp":24.4,"hm":28,"p2":2,"co":432},{"ts":"2019-03-07T18:35:12.172Z","tp":24.4,"hm":28,"p2":2,"co":433},{"ts":"2019-03-07T18:30:02.547Z","tp":24.5,"hm":27,"p2":2,"co":435},{"ts":"2019-03-07T18:24:52.122Z","tp":24.5,"hm":28,"p2":1,"co":436},{"ts":"2019-03-07T18:19:42.151Z","tp":24.5,"hm":28,"p2":2,"co":439},{"ts":"2019-03-07T18:14:32.116Z","tp":24.5,"hm":27,"p2":2,"co":439},{"ts":"2019-03-07T18:09:22.181Z","tp":24.5,"hm":28,"p2":2,"co":444},{"ts":"2019-03-07T18:04:12.159Z","tp":24.5,"hm":28,"p2":1,"co":446},{"ts":"2019-03-07T17:59:02.217Z","tp":24.5,"hm":28,"p2":2,"co":450},{"ts":"2019-03-07T17:53:52.192Z","tp":24.5,"hm":28,"p2":2,"co":452},{"ts":"2019-03-07T17:48:42.238Z","tp":24.5,"hm":28,"p2":3,"co":456},{"ts":"2019-03-07T17:43:32.844Z","tp":24.5,"hm":28,"p2":2,"co":460},{"ts":"2019-03-07T17:38:22.170Z","tp":24.5,"hm":28,"p2":2,"co":465},{"ts":"2019-03-07T17:33:12.205Z","tp":24.5,"hm":28,"p2":2,"co":465},{"ts":"2019-03-07T17:28:02.355Z","tp":24.5,"hm":28,"p2":2,"co":468},{"ts":"2019-03-07T17:22:52.205Z","tp":24.6,"hm":28,"p2":2,"co":471},{"ts":"2019-03-07T17:17:42.212Z","tp":24.6,"hm":28,"p2":1,"co":479},{"ts":"2019-03-07T17:12:32.246Z","tp":24.6,"hm":28,"p2":2,"co":484},{"ts":"2019-03-07T17:07:22.469Z","tp":24.6,"hm":28,"p2":3,"co":489},{"ts":"2019-03-07T17:02:12.380Z","tp":24.6,"hm":28,"p2":2,"co":488},{"ts":"2019-03-07T16:57:02.252Z","tp":24.6,"hm":28,"p2":1,"co":485},{"ts":"2019-03-07T16:51:52.235Z","tp":24.6,"hm":28,"p2":2,"co":503},{"ts":"2019-03-07T16:46:42.276Z","tp":24.6,"hm":28,"p2":1,"co":529},{"ts":"2019-03-07T16:41:32.259Z","tp":24.7,"hm":28,"p2":2,"co":505},{"ts":"2019-03-07T16:36:22.398Z","tp":24.6,"hm":28,"p2":2,"co":502},{"ts":"2019-03-07T16:31:13.326Z","tp":24.6,"hm":28,"p2":3,"co":493},{"ts":"2019-03-07T16:26:02.259Z","tp":24.6,"hm":28,"p2":3,"co":491},{"ts":"2019-03-07T16:20:52.339Z","tp":24.6,"hm":28,"p2":2,"co":498},{"ts":"2019-03-07T16:15:42.356Z","tp":24.6,"hm":28,"p2":2,"co":483},{"ts":"2019-03-07T16:10:32.306Z","tp":24.6,"hm":27,"p2":2,"co":486},{"ts":"2019-03-07T16:05:22.275Z","tp":24.6,"hm":27,"p2":2,"co":488},{"ts":"2019-03-07T16:00:12.369Z","tp":24.6,"hm":27,"p2":1,"co":491},{"ts":"2019-03-07T15:55:05.369Z","tp":24.6,"hm":28,"p2":2,"co":499},{"ts":"2019-03-07T15:49:52.316Z","tp":24.6,"hm":27,"p2":2,"co":482},{"ts":"2019-03-07T15:44:42.303Z","tp":24.6,"hm":27,"p2":1,"co":481},{"ts":"2019-03-07T15:39:32.375Z","tp":24.6,"hm":27,"p2":2,"co":479},{"ts":"2019-03-07T15:34:22.378Z","tp":24.6,"hm":27,"p2":3,"co":471},{"ts":"2019-03-07T15:29:12.408Z","tp":24.6,"hm":27,"p2":2,"co":469},{"ts":"2019-03-07T15:24:02.367Z","tp":24.6,"hm":27,"p2":3,"co":474},{"ts":"2019-03-07T15:18:52.328Z","tp":24.6,"hm":27,"p2":2,"co":475},{"ts":"2019-03-07T15:13:42.345Z","tp":24.5,"hm":27,"p2":1,"co":477},{"ts":"2019-03-07T15:08:32.369Z","tp":24.5,"hm":27,"p2":2,"co":479},{"ts":"2019-03-07T15:03:22.385Z","tp":24.5,"hm":27,"p2":2,"co":479},{"ts":"2019-03-07T14:58:12.343Z","tp":24.5,"hm":27,"p2":2,"co":478},{"ts":"2019-03-07T14:53:02.501Z","tp":24.5,"hm":27,"p2":3,"co":479},{"ts":"2019-03-07T14:47:52.418Z","tp":24.5,"hm":27,"p2":3,"co":480},{"ts":"2019-03-07T14:42:42.410Z","tp":24.4,"hm":27,"p2":3,"co":489},{"ts":"2019-03-07T14:37:32.555Z","tp":24.5,"hm":27,"p2":4,"co":492},{"ts":"2019-03-07T14:32:22.412Z","tp":24.4,"hm":27,"p2":4,"co":478},{"ts":"2019-03-07T14:27:12.392Z","tp":24.4,"hm":27,"p2":3,"co":479},{"ts":"2019-03-07T14:22:02.500Z","tp":24.4,"hm":27,"p2":4,"co":471}],

Haha yes, I give you the formula : https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI

With this complexity, I prefer let AirVisual calcul it and extract directly the AQI measure from samba.

There are many calculators already on the web which I’m sure could be utilized as a web service to do this without implementing the formula. Most only need to know the current µg/m3 as given by the API.

Again I direct you to https://aqicn.org/calculator/

1 Like

So I’m trying to get it going your way… Let me ask you, how did you mount the samba folder within hass.io? If you put it within the fstab inside the container, it will be overwritten whenever restarting, no? If you do it on the filesystem, you’d need to map it to the container… I tried to map it inside the share directory, but it wasn’t visible from inside the container.