Solved: Airvisual Node Pro Air Quality Monitor Support

Thanks for the perfect and detailed instructions. This made it really easy. Here’s the output I got:

INFO:SMB.SMBConnection:Authentication with remote machine "AIRVISUAL" for user "airvisual" will be using NTLM v2 authentication (with extended security)
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_NEGOTIATE" (command:0x72 flags:0x88 flags2:0xC841 TID:0 UID:0)
INFO:SMB.SMBConnection:SMB dialect negotiation successful (ExtendedSecurity:True)
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_SESSION_SETUP_ANDX" (command:0x73 flags:0x88 flags2:0xC803 TID:0 UID:16113)
INFO:SMB.SMBConnection:Performing NTLMv2 authentication (with extended security) with server challenge "<REDACTED>'"
DEBUG:SMB.SMBConnection:NT challenge response is "<REDACTED>'" (124 bytes)
DEBUG:SMB.SMBConnection:LM challenge response is "<REDACTED>'" (24 bytes)
INFO:SMB.SMBConnection:SMB signing deactivated. SMB messages will NOT be signed.
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_SESSION_SETUP_ANDX" (command:0x73 flags:0x88 flags2:0xC803 TID:0 UID:16113)
DEBUG:SMB.SMBConnection:SMB uid is now 16113
INFO:SMB.SMBConnection:Authentication (with extended security) successful!
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_TREE_CONNECT_ANDX" (command:0x75 flags:0x88 flags2:0xC803 TID:8935 UID:16113)
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_OPEN_ANDX" (command:0x2D flags:0x88 flags2:0xC803 TID:8935 UID:16113)
DEBUG:SMB.SMBConnection:Received SMB message "SMB_COM_READ_ANDX" (command:0x2E flags:0x88 flags2:0xC803 TID:8935 UID:16113)
DEBUG:pyairvisual.node:Node Pro measurements loaded: {'date_and_time': {'date': '2020/09/22', 'time': '18:11:21', 'timestamp': '1600798281'}, 'measurements': {'co2_ppm': '397', 'humidity_RH': '55', 'pm25_AQICN': '13', 'pm25_AQIUS': '38', 'pm25_ugm3': '9.0', 'temperature_C': '23.5', 'temperature_F': '74.3', 'voc_ppb': '-1'}, 'serial_number': 'SXP9JKS', 'settings': {'followed_station': '8262', 'is_aqi_usa': True, 'is_concentration_showed': True, 'is_indoor': False, 'is_lcd_on': True, 'is_network_time': True, 'is_temperature_celsius': True, 'language': 'en-GB', 'lcd_brightness': 40, 'node_name': 'hlubocepy', 'power_saving': {'2slots': [{'hour_off': 9, 'hour_on': 7}, {'hour_off': 22, 'hour_on': 18}], 'mode': 'yes', 'yes': [{'hour': 7, 'minute': 0}, {'hour': 21, 'minute': 0}]}, 'speed_unit': 'mph', 'timezone': 'Europe/Prague'}, 'status': {'app_version': '1.1532', 'battery': 100, 'datetime': 1600798281, 'model': '10', 'sensor_pm25_serial': '00000110030927030221', 'sync_time': 260000, 'system_version': 'KBG57F70', 'used_memory': 0, 'wifi_strength': 4}}
Traceback (most recent call last):
  File "examples/test_node_pro.py", line 26, in <module>
    asyncio.run(main())
  File "/usr/local/Cellar/[email protected]/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/[email protected]/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "examples/test_node_pro.py", line 20, in main
    _LOGGER.info(await node.async_get_latest_measurements())
  File "/Volumes/RAID/scstraus/pyairvisual/pyairvisual/node.py", line 208, in async_get_latest_measurements
    for pollutant, value in data["measurements"][0].items()
KeyError: 0

PS I’m splitting out the attributes into individual template sensors for the node that worked. Do you have any idea what units the “pm2_5_sensor_life” are in? Is that measurements, seconds, minutes?

Thanks for this follow-up. Interesting – your data looks like this:

{
  "date_and_time": {
    "date": "2020/09/22",
    "time": "18:11:21",
    "timestamp": "1600798281"
  },
  "measurements": {
    "co2_ppm": "397",
    "humidity_RH": "55",
    "pm25_AQICN": "13",
    "pm25_AQIUS": "38",
    "pm25_ugm3": "9.0",
    "temperature_C": "23.5",
    "temperature_F": "74.3",
    "voc_ppb": "-1"
  },
  "serial_number": "SXP9JKS",
  "settings": {
    "followed_station": "8262",
    "is_aqi_usa": true,
    "is_concentration_showed": true,
    "is_indoor": false,
    "is_lcd_on": true,
    "is_network_time": true,
    "is_temperature_celsius": true,
    "language": "en-GB",
    "lcd_brightness": 40,
    "node_name": "hlubocepy",
    "power_saving": {
      "2slots": [
        {
          "hour_off": 9,
          "hour_on": 7
        },
        {
          "hour_off": 22,
          "hour_on": 18
        }
      ],
      "mode": "yes",
      "yes": [
        {
          "hour": 7,
          "minute": 0
        },
        {
          "hour": 21,
          "minute": 0
        }
      ]
    },
    "speed_unit": "mph",
    "timezone": "Europe/Prague"
  },
  "status": {
    "app_version": "1.1532",
    "battery": 100,
    "datetime": 1600798281,
    "model": "10",
    "sensor_pm25_serial": "00000110030927030221",
    "sync_time": 260000,
    "system_version": "KBG57F70",
    "used_memory": 0,
    "wifi_strength": 4
  }
}

…whereas mine looks like this:

{
  "date_and_time": {
    "date": "2020/09/24",
    "time": "12:40:00",
    "timestamp": "1600951200"
  },
  "measurements": [
    {
      "co2_ppm": "409",
      "humidity_RH": "36",
      "pm01_ugm3": "5",
      "pm10_ugm3": "6",
      "pm25_AQICN": "9",
      "pm25_AQIUS": "25",
      "pm25_ugm3": "6.0",
      "temperature_C": "24.6",
      "temperature_F": "76.3",
      "voc_ppb": "-1"
    }
  ],
  "serial_number": "PV6WLM7",
  "settings": {
    "follow_mode": "station",
    "followed_station": "0",
    "is_aqi_usa": true,
    "is_concentration_showed": true,
    "is_indoor": true,
    "is_lcd_on": true,
    "is_network_time": true,
    "is_temperature_celsius": false,
    "language": "en-US",
    "lcd_brightness": 80,
    "node_name": "Office",
    "power_saving": {
      "2slots": [
        {
          "hour_off": 9,
          "hour_on": 7
        },
        {
          "hour_off": 22,
          "hour_on": 18
        }
      ],
      "mode": "yes",
      "running_time": 99,
      "yes": [
        {
          "hour": 8,
          "minute": 0
        },
        {
          "hour": 21,
          "minute": 0
        }
      ]
    },
    "sensor_mode": {
      "custom_mode_interval": 3,
      "mode": 1
    },
    "speed_unit": "",
    "timezone": "America/Denver",
    "tvoc_unit": "ppb"
  },
  "status": {
    "app_version": "1.1731",
    "battery": 100,
    "datetime": 1600951200,
    "device_name": "AIRVISUAL-PV6WLM7",
    "ip_address": "172.16.11.180",
    "mac_address": "88835db41b3b",
    "model": "20",
    "sensor_life": {
      "pm25": 1558672276249
    },
    "sensor_pm25_serial": "00000005050224011145",
    "sync_time": 250000,
    "system_version": "KBG63F84",
    "used_memory": 3,
    "wifi_strength": 5
  }
}

Notice how my measurements are a list, whereas yours are a straight dict – so, I can make a code adjustment in pyairvisual to check for both possibilities.

Unfortunately, I’m not sure – I tried reaching out AirVisual with this same question, but never heard back. If you should discover the answer, let me know!

1 Like

Great, I will watch out for the next release! Maybe it has something to do with that that one is an outdoor unit which is being shared with the AirVisual community. I guess they change the data to make it compliant for sharing or something. PS, I’m originally from Denver too!

Just submitted a PR to HASS: https://github.com/home-assistant/core/pull/40554 :+1:

Props to Denver! :raised_hands:

1 Like

Good day, I have the same “weird” data structure. I am on 0.116.2 and still getting errors.

Here comes the logs:

2020-10-12 22:51:53 ERROR (MainThread) [homeassistant.components.sensor] Error while setting up airvisual platform for sensor
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 201, in _async_setup_platform
    await asyncio.gather(*pending)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 310, in async_add_entities
    await asyncio.gather(*tasks)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 356, in _async_add_entity
    if entity.unique_id is not None:
  File "/usr/src/homeassistant/homeassistant/components/airvisual/sensor.py", line 252, in unique_id
    return f"{self.coordinator.data['serial_number']}_{self._kind}"
TypeError: 'NoneType' object is not subscriptable
2020-10-12 22:51:53 ERROR (MainThread) [homeassistant.components.airvisual] Unexpected error fetching Node/Pro data data: 'sensor_life'
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 143, in async_refresh
    self.data = await self._async_update_data()
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 131, in _async_update_data
    return await self.update_method()
  File "/usr/src/homeassistant/homeassistant/components/airvisual/__init__.py", line 275, in async_update_data
    return await node.async_get_latest_measurements()
  File "/usr/local/lib/python3.8/site-packages/pyairvisual/node.py", line 219, in async_get_latest_measurements
    for pollutant, value in data["status"]["sensor_life"].items()
KeyError: 'sensor_life'
2020-10-12 22:51:53 ERROR (MainThread) [homeassistant.components.air_quality] Error while setting up airvisual platform for air_quality
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 201, in _async_setup_platform
    await asyncio.gather(*pending)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 310, in async_add_entities
    await asyncio.gather(*tasks)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 356, in _async_add_entity
    if entity.unique_id is not None:
  File "/usr/src/homeassistant/homeassistant/components/airvisual/air_quality.py", line 95, in unique_id
    return self.coordinator.data["serial_number"]
TypeError: 'NoneType' object is not subscriptable

And here is latest_config_measurements.json

{
    "date_and_time": {
        "date": "2020/10/12",
        "time": "22:30:30",
        "timestamp": "1602541830"
    },
    "measurements": {
        "co2_ppm": "705",
        "humidity_RH": "50",
        "pm10_ugm3": "15",
        "pm25_AQICN": "10",
        "pm25_AQIUS": "29",
        "pm25_ugm3": "7.0",
        "temperature_C": "25.3",
        "temperature_F": "77.5",
        "voc_ppb": "-1"
    },
    "serial_number": "!secret",
    "settings": {
        "follow_mode": "station",
        "followed_station": "!secret",
        "is_aqi_usa": true,
        "is_concentration_showed": false,
        "is_indoor": true,
        "is_lcd_on": false,
        "is_network_time": true,
        "is_temperature_celsius": true,
        "language": "en-GB",
        "lcd_brightness": 0,
        "node_name": "!secret",
        "power_saving": {
            "2slots": [
                {
                    "hour_off": 9,
                    "hour_on": 7
                },
                {
                    "hour_off": 22,
                    "hour_on": 18
                }
            ],
            "mode": "yes",
            "running_time": 99,
            "yes": [
                {
                    "hour": 9,
                    "minute": 0
                },
                {
                    "hour": 22,
                    "minute": 0
                }
            ]
        },
        "sensor_mode": {
            "custom_mode_interval": 3,
            "mode": 1
        },
        "speed_unit": "mph",
        "timezone": "!secret"
    },
    "status": {
        "app_version": "1.1623",
        "battery": 100,
        "datetime": 1602541830,
        "device_name": "AIRVISUAL-!secret",
        "ip_address": "!secret",
        "mac_address": "!secret",
        "model": "20",
        "sensor_pm25_serial": "!secret",
        "sync_time": 850000,
        "system_version": "!secret",
        "used_memory": 3,
        "wifi_strength": 4
    }
}

Yes, I am able to add the outdoor node now on .116.3, but not able to actually get it to build sensors.

020-10-16 00:01:53 DEBUG (MainThread) [pyairvisual.node] Node Pro measurements loaded: {'date_and_time': {'date': '2020/10/15', 'time': '23:47:20', 'timestamp': '1602805640'}, 'measurements': {'co2': '402', 'humidity': '89', 'aqi_cn': '6', 'aqi_us': '17', 'pm2_5': '4.0', 'temperature_C': '10.8', 'temperature_F': '51.4', 'voc': '-1'}, 'serial_number': 'SXP9JKS', 'settings': {'followed_station': '8262', 'is_aqi_usa': True, 'is_concentration_showed': True, 'is_indoor': False, 'is_lcd_on': False, 'is_network_time': True, 'is_temperature_celsius': True, 'language': 'en-GB', 'lcd_brightness': 40, 'node_name': 'hlubocepy', 'power_saving': {'2slots': [{'hour_off': 9, 'hour_on': 7}, {'hour_off': 22, 'hour_on': 18}], 'mode': 'yes', 'yes': [{'hour': 7, 'minute': 0}, {'hour': 21, 'minute': 0}]}, 'speed_unit': 'mph', 'timezone': 'Europe/Prague'}, 'status': {'app_version': '1.1532', 'battery': 100, 'datetime': 1602805640, 'model': '10', 'sensor_pm25_serial': '00000110030927030221', 'sync_time': 860000, 'system_version': 'KBG57F70', 'used_memory': 0, 'wifi_strength': 4}}
2020-10-16 00:01:53 ERROR (MainThread) [homeassistant.components.airvisual] Unexpected error fetc
hing Node/Pro data data: 'sensor_life'
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 143, in async_r
efresh
    self.data = await self._async_update_data()
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 131, in _async_
update_data
    return await self.update_method()
  File "/usr/src/homeassistant/homeassistant/components/airvisual/__init__.py", line 275, in asyn
c_update_data
    return await node.async_get_latest_measurements()
  File "/usr/local/lib/python3.8/site-packages/pyairvisual/node.py", line 219, in async_get_lates
t_measurements
    for pollutant, value in data["status"]["sensor_life"].items()
KeyError: 'sensor_life'
2020-10-16 00:01:53 DEBUG (MainThread) [homeassistant.components.airvisual] Finished fetching Nod
e/Pro data data in 0.114 seconds
2020-10-16 00:01:53 INFO (SyncWorker_14) [homeassistant.loader] Loaded air_quality from homeassis
tant.components.air_quality
2020-10-16 00:01:53 INFO (MainThread) [homeassistant.setup] Setting up air_quality
2020-10-16 00:01:53 INFO (MainThread) [homeassistant.setup] Setup of domain air_quality took 0.0 
seconds
2020-10-16 00:01:54 DEBUG (MainThread) [pyairvisual.node] Node Pro measurements loaded: {'date_an
d_time': {'date': '2020/10/16', 'time': '00:00:39', 'timestamp': '1602806439'}, 'measurements': {
'co2': '670', 'humidity': '52', 'pm0_1': '0', 'pm1_0': '2', 'aqi_cn': '3', 'aqi_us': '8', 'pm2_5': '2.0', 'temperature_C': '22.4', 'temperature_F': '72.4', 'voc': '-1'}, 'serial_number': 'TKKGJWV', 'settings': {'follow_mode': 'device', 'followed_station': 'qhofpkt2', 'is_aqi_usa': True, 'is_concentration_showed': False, 'is_indoor': True, 'is_lcd_on': False, 'is_network_time': True, 'is_temperature_celsius': True, 'language': 'en-GB', 'lcd_brightness': 100, 'node_name': 'Home', 'power_saving': {'2slots': [{'hour_off': 9, 'hour_on': 7}, {'hour_off': 22, 'hour_on': 18}], 'mode': 'yes', 'running_time': 99, 'yes': [{'hour': 8, 'minute': 0}, {'hour': 21, 'minute': 0}]}, 'sensor_mode': {'custom_mode_interval': 5, 'mode': 2}, 'speed_unit': 'mph', 'timezone': 'Europe/Prague'}, 'status': {'app_version': '1.1651', 'battery': 100, 'datetime': 1602806439, 'device_name': 'AIRVISUAL-TKKGJWV', 'ip_address': '10.10.10.140', 'mac_address': '54c9dfd65a20', 'model': '20', 'sensor_life': {'pm2_5': 1527033680395}, 'sensor_pm25_serial': '00000005041102031047', 'sync_time': 250000, 'system_version': 'KBG60F82', 'used_memory': 3, 'wifi_strength': 3}}
2020-10-16 00:01:54 DEBUG (MainThread) [homeassistant.components.airvisual] Finished fetching Node/Pro data data in 0.289 seconds
2020-10-16 00:01:54 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=air_quality.home, old_state=None, new_state=<state air_quality.home=2.0; air_quality_index=8, carbon_dioxide=670, particulate_matter_0_1=0, particulate_matter_10=2, particulate_matter_2_5=2.0, attribution=Data provided by AirVisual, voc=-1, pm2_5_sensor_life=1527033680395, unit_of_measurement=<C2><B5>g/m<C2><B3>, friendly_name=Home Node/Pro: Air Quality, icon=mdi:chemical-weapon @ 2020-10-16T00:01:54.615719+02:00>>
2020-10-16 00:01:54 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=system_log, service=write, service_data=logger=frontend.js.latest.202010012, message=http://10.10.10.20:8123/hacsfiles/lovelace-weather-card-chart/weather-card-chart.js:298:43 Uncaught TypeError: Cannot read property 'forecast' of undefined>
2020-10-16 00:01:54 ERROR (MainThread) [frontend.js.latest.202010012] http://10.10.10.20:8123/hacsfiles/lovelace-weather-card-chart/weather-card-chart.js:298:43 Uncaught TypeError: Cannot read property 'forecast' of undefined

Updated to 0.117.6 – everything works fine now, big thanks!

Yep working for me too.

Hi, CO2 sensor in AirVisual NodePro integration on 0.118.1 don’t show up . Other sensors look good. Can someone help me?

Unfortunately, there is no separate CO2 sensor.
But there is a corresponding attribute in “Air Quality” sensor. You can define your custom sensor like this:

sensor:
  - platform: template
    sensors:
      air_quality_co2:
        entity_id: sensor.air_quality_co2
        friendly_name: "Airvisual CO2"
        unit_of_measurement: 'ppm'
        icon_template: mdi:molecule-co2
        value_template: "{{ state_attr('air_quality.home_node_pro_air_quality', 'carbon_dioxide') }}"

@bachya Could you please also extract data from “date_and_time” section of latest_config_measurements.json?

It would be nice to see the time measurement was made. Now HA can only show the time when the values were read.

I can! :+1:t2: I’ll follow up soon.

This PR has been created: https://github.com/home-assistant/core/pull/44801 – note that the HASS standard is to use UTC everywhere, so the PR takes the timestamp from the JSON file and converts it to UTC before adding it as an attribute to AirVisual air_quality entities.

@bachya Thanks a lot, looking forward the merge.

By the way.
Why isn’t “carbon dioxide” a separate sensor, but an attribute in another sensor? So one needs to write yaml to show the value (see above)?

This is how air_quality platforms are designed: https://github.com/home-assistant/core/blob/3a32e16f4dc1eee4e3bf38fe10f0a6a15d0292a0/homeassistant/components/air_quality/init.py

1 Like

So, having carbon_dioxide as an attribute of air_quality is “standard”.

Why not adding a separate sensor in addition, so that one can use it without creating custom sensor in yaml?

Because, as mentioned, that’s the standard for air_quality platforms.

HASS uses it’s architecture repo to discuss these types of issues, larger changes, etc. It looks like there’s a proposal to do exactly what you’re saying (deprecate/remove air_quality and use a bundle of sensor entities instead): https://github.com/home-assistant/architecture/issues/362

I recommend you contribute to that proposal. If it gets approved, I will certainly make the changes to AirVisual.

EDIT: after chatting with the core team, I got approval to extend the AirVisual integration by adding sensors alongside the existing air_quality platform, which avoids any sort of breaking change and doesn’t need to wait on an architecture proposal.

So, I’ll get started soon!

3 Likes

As I can see, PR was closed as “not relevant to have this information”.

But it is really relevant.
AirVisual Node integration does not read data from device sensors directly.
The device makes measurements and write values to local storage once in several minutes (5 minutes in day time, 15 minutes at night).
HA integration reads values from device local storage. We can read them once a minute, or even once a second - they will not change until the device itself will make a new measurement.
Now in HA we have no information about the time when measurement was really made - only the time values were read. So it is not possible to use “data really updated” event in automation.

How can I explain it? The issue is closed and cannot be commented…

1 Like

Some good points made here. The integration via SMB does induce delay in the reading of measurements so that they are not shown in real time or updated at the actual time that they are read. We would need to access the API directly on the unit to do that, but no one feels like doing the conversions needed to get a nice AQI value out of them.

Generally in my experience, 5 minutes doesn’t make a huge difference unless someone sets a pile of tires on fire in front of my house, but for those kind of cases (people do burn piles of leaves around here), more real time data can be useful.