Dutch Air Quality Sensor

Hi y’all!

I wanted to be able to monitor air quality in the Netherlands.
Luchtmeetnet provides an API to do this.

Next to individual readings (for instance NO2, PM10) I wanted a general index for air quality ( Luchtkwaliteitsindex). I’ve modeled this the same way Luchtmeetnet does (see here).

To be able to use this piece of code, you need to find a measuring station ID, from a station that measures the particle measurement you want. I use multiple stations because the station near me did not measure O3.
The easiest is to first find the station you want on this map. When you know the names, you can get the station ID with this url:
https://api.luchtmeetnet.nl/open_api/stations?order_by=&organisation_id=&page=1
Change the page number to find more stations.
Make sure the station actually supports the measurement you need.

After this, you can add to your HASS config, and change the station ID numbers:

sensor:
  - platform: rest
    name: airquality_NO2
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL49007/measurements?page=&order=&order_direction=&formula=NO2
    unit_of_measurement: µg/m3
    value_template: '{{ value_json.data[0].value }}'
  - platform: rest
    name: airquality_PM10
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL49007/measurements?page=&order=&order_direction=&formula=PM10
    unit_of_measurement: µg/m3
    value_template: '{{ value_json.data[0].value }}'
  - platform: rest
    name: airquality_PM25
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL49007/measurements?page=&order=&order_direction=&formula=PM25
    unit_of_measurement: µg/m3
    value_template: '{{ value_json.data[0].value }}'
  - platform: rest
    name: airquality_O3
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL49014/measurements?page=&order=&order_direction=&formula=O3
    unit_of_measurement: µg/m3
    value_template: '{{ value_json.data[0].value }}'
  - platform: template
    sensors:
      airquality_no2_index:
        friendly_name: 'Luchtkwaliteit NO2 Index'
        entity_id: sensor.airquality_no2
        value_template: >
          {% set NO2 = states.sensor.airquality_no2.state %}
          {%- if NO2 | int < 10 -%}
          1 
          {%- elif NO2 | int < 20 -%}
          2
          {%- elif NO2 | int < 30 -%}
          3
          {%- elif NO2 | int < 45 -%}
          4
          {%- elif NO2 | int < 60 -%}
          5
          {%- elif NO2 | int < 75 -%}
          6
          {%- elif NO2 | int < 100 -%}
          7
          {%- elif NO2 | int < 125 -%}
          8
          {%- elif NO2 | int < 150 -%}
          9
          {%- elif NO2 | int < 200 -%}
          10
          {%- else %}
          11
          {%- endif %}
  - platform: template
    sensors:
      airquality_pm10_index:
        friendly_name: 'Luchtkwaliteit PM10 Index'
        entity_id: sensor.airquality_pm10
        value_template: >
          {% set PM10 = states.sensor.airquality_pm10.state %}
          {%- if PM10 | int < 10 -%}
          1 
          {%- elif PM10 | int < 20 -%}
          2
          {%- elif PM10 | int < 30 -%}
          3
          {%- elif PM10 | int < 45 -%}
          4
          {%- elif PM10 | int < 60 -%}
          5
          {%- elif PM10 | int < 75 -%}
          6
          {%- elif PM10 | int < 100 -%}
          7
          {%- elif PM10 | int < 125 -%}
          8
          {%- elif PM10 | int < 150 -%}
          9
          {%- elif PM10 | int < 200 -%}
          10
          {%- else %}
          11
          {%- endif %}
  - platform: template
    sensors:
      airquality_pm25_index:
        friendly_name: 'Luchtkwaliteit PM25 Index'
        entity_id: sensor.airquality_pm25
        value_template: >
          {% set PM25 = states.sensor.airquality_pm25.state %}
          {%- if PM25 | int < 10 -%}
          1 
          {%- elif PM25 | int < 15 -%}
          2
          {%- elif PM25 | int < 20 -%}
          3
          {%- elif PM25 | int < 30 -%}
          4
          {%- elif PM25 | int < 40 -%}
          5
          {%- elif PM25 | int < 50 -%}
          6
          {%- elif PM25 | int < 70 -%}
          7
          {%- elif PM25 | int < 90 -%}
          8
          {%- elif PM25 | int < 100 -%}
          9
          {%- elif PM25 | int < 140 -%}
          10
          {%- else %}
          11
          {%- endif %}
  - platform: template
    sensors:
      airquality_o3_index:
        friendly_name: 'Luchtkwaliteit O3 Index'
        entity_id: sensor.airquality_o3
        value_template: >
          {% set O3 = states.sensor.airquality_o3.state %}
          {%- if O3 | int < 15 -%}
          1 
          {%- elif O3 | int < 30 -%}
          2
          {%- elif O3 | int < 40 -%}
          3
          {%- elif O3 | int < 60 -%}
          4
          {%- elif O3 | int < 80 -%}
          5
          {%- elif O3 | int < 100 -%}
          6
          {%- elif O3 | int < 140 -%}
          7
          {%- elif O3 | int < 180 -%}
          8
          {%- elif O3 | int < 200 -%}
          9
          {%- elif O3 | int < 240 -%}
          10
          {%- else %}
          11
          {%- endif %}
  - platform: template
    sensors:
      airquality_combined_index:
        friendly_name: 'Luchtkwaliteit Index'
        unit_of_measurement: index
        value_template: >
          {% set NO2 = states.sensor.airquality_no2_index.state %}
          {% set O3 = states.sensor.airquality_o3_index.state %}
          {% set PM10 = states.sensor.airquality_pm10_index.state %}
          {% set PM25 = states.sensor.airquality_pm25_index.state %}
          
          {%set mylist = states("sensor.airquality_no2_index")|int,
                 states("sensor.airquality_o3_index")|int,
                 states("sensor.airquality_pm10_index")|int,
                 states("sensor.airquality_pm25_index")|int %}
                 
           {% if NO2 | int == O3 | int == PM10 | int == PM25 | int -%}
           {% set Index = NO2 | int + 1 %}
           {{Index | int}}
           {%- else -%}
           {{ mylist|max | int }}
           {%- endif %}

I hope you like it, any questions of suggestions are welcome!

6 Likes

Nice addition! Unfortunately luchtmeetnet doesn’t have any stations available close to my house…

Yeah that’s too bad, although it seems the measuring stations are concentrated around polluted area’s. So it might be a good thing you don’t have them around :yum:?

Super cool project!

https://luftdaten.info/en/home-en/ is another project that helps you to setup your own air quality measurement station and become part of a world wide air quality network.

It is quite new, but it has already world wide users, although the majority is located in Europe:

You can put together the measurement station yourself. It is made of cheap hardware. Here is the instruction manual: https://luftdaten.info/en/construction-manual/

And maybe one day HA will also have integration for this project :slight_smile:

1 Like

I think Luftdaten is supported by HA!
It seems to only provide PM10 and PM25 sensors though.

Nice!

Yes, only those air quality metrics (particles 10 microns and below and particles 2.5 microns and below) together with temperature, pressure and humidity are provided. It does not report that extensive as the official air measurement stations provided by luchtmeetnet.

Thanks! Good stuff, will try it.

Did the API change, it’s no longer reporting and on the api site certain urls don’t seem to be working anymore…

Seems broken indeed.

Cached google results report something about the website and API being updated on Feb 10th (yesterday).
Can’t find it on the site itself.
Maybe they are busy with an update…

The API seems to be working again, luckily no changes to the sensors are needed :slight_smile:

No longer working :frowning:

I found an alternative, but needs some work: https://github.com/magtimmermans/home-assistant-sensor-luchtmeetnet

It’s still working on my end…

Yes, after some playing around it seems to work again. So must have been the station I’m pulling the data from :slight_smile:

1 Like

Great addition and very helpful. I have one question left.

Luchtmeetnet.nl uses 5 stages to assess the air quality index. Which graphic (meter) for my Lovelace dashboard can best be used to display these 5 stages in the right color?

Thanks!

1 Like

Glad you like it :slight_smile:

There’s a lot of options for lovelace…
Built into HA:

Addon customizable:

Thanks, I know this one of course. But they do not fully match the 5 stages of https://www.luchtmeetnet.nl/ How did you solve this?

Oke, I solved it like this. I have used the values in the table of https://www.luchtmeetnet.nl/ with this as a result.

Yeah cool!

Other custom cards have more options to define more severities, like the bar card I linked in my previous post.
Also this one might interest you:

1 Like

Seems super! I tried to integrate it, but there seems some issues caused by new versions of HA?
At these lines: entity_id: sensor.airquality_pm25, I get the error message:
Property entity_id is not allowed.yaml-schema: http://schemas.home-assistant.io/configuration
That link seems long-dead…
Has anyone a clue?

This is my present working code, you can read how the format must be nowadays to make it work:

sensor:
  - platform: rest
    name: airquality_no2
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=NO2
    unit_of_measurement: µg/m3
    scan_interval: 1800
    value_template: "{{ value_json['data'][0]['value'] | round(1) }}"
  - platform: rest
    name: airquality_pm10
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=PM10
    unit_of_measurement: µg/m3
    scan_interval: 1800
    value_template: "{{ value_json['data'][0]['value'] | round(1) }}"
  #  - platform: rest
  #    name: airquality_PM25
  #    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=PM25
  #    unit_of_measurement: µg/m3
  #    scan_interval: 900
  #    value_template: '{{ value_json.data[0].value | round(1) }}'
  #  - platform: rest
  #    name: airquality_ROET
  #    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=FN
  #    unit_of_measurement: µg/m3
  #    scan_interval: 900
  #    value_template: '{{ value_json.data[0].value | round(2) }}'
  - platform: rest
    name: airquality_o3
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=O3
    unit_of_measurement: µg/m3
    scan_interval: 1800
    value_template: "{{ value_json['data'][0]['value'] | round(1) }}"
  - platform: rest
    name: airquality_so2
    resource: https://api.luchtmeetnet.nl/open_api/stations/NL10722/measurements?page=&order=&order_direction=&formula=SO2
    unit_of_measurement: µg/m3
    scan_interval: 1800
    value_template: "{{ value_json['data'][0]['value'] | round(1) }}"

As you can see I haven’t updated the code for the sensor that I have commented out.

From these sensors I create the following sensors:

template:
  - sensor:
      - name: "Airquality no2 index"
        state: >
          {% set NO2 = states('sensor.airquality_no2') | int(0) %}
          {% if NO2 | int < 10 %}
          1 
          {% elif NO2 | int < 20 %}
          2
          {% elif NO2 | int < 30 %}
          3
          {% elif NO2 | int < 45 %}
          4
          {% elif NO2 | int < 60 %}
          5
          {% elif NO2 | int < 75 %}
          6
          {% elif NO2 | int < 100 %}
          7
          {% elif NO2 | int < 125 %}
          8
          {% elif NO2 | int < 150 %}
          9
          {% elif NO2 | int < 200 %}
          10
          {% else %}
          11
          {% endif %}
  - sensor:
      - name: "Airquality pm10 index"
        state: >
          {% set PM10 = states('sensor.airquality_pm10') | int(0) %}
          {% if PM10 | int < 10 %}
            1 
          {% elif PM10 | int < 20 %}
            2
          {% elif PM10 | int < 30 %}
            3
          {% elif PM10 | int < 45 %}
            4
          {% elif PM10 | int < 60 %}
            5
          {% elif PM10 | int < 75 %}
            6
          {% elif PM10 | int < 100 %}
            7
          {% elif PM10 | int < 125 %}
            8
          {% elif PM10 | int < 150 %}
            9
          {% elif PM10 | int < 200 %}
            10
          {% else %}
            11
          {% endif %}
  #  - platform: template
  #    sensors:
  #      airquality_pm25_index:
  #        friendly_name: 'Luchtkwaliteit PM2.5 - Index'
  #        #entity_id: sensor.airquality_pm25
  #        value_template: >
  #          {% set PM25 = states('sensor.airquality_pm25') %}
  #          {%- if PM25 | int < 10 -%}
  #          1
  #          {%- elif PM25 | int < 15 -%}
  #          2
  #          {%- elif PM25 | int < 20 -%}
  #          3
  #          {%- elif PM25 | int < 30 -%}
  #          4
  #          {%- elif PM25 | int < 40 -%}
  #          5
  #          {%- elif PM25 | int < 50 -%}
  #          6
  #          {%- elif PM25 | int < 70 -%}
  #          7
  #          {%- elif PM25 | int < 90 -%}
  #          8
  #          {%- elif PM25 | int < 100 -%}
  #          9
  #          {%- elif PM25 | int < 140 -%}
  #          10
  #          {%- else %}
  #          11
  #          {%- endif %}
  - sensor:
      - name: "Airquality o3 index"
        state: >
          {% set O3 = states('sensor.airquality_o3') | int(0) %}
          {% if O3 | int < 15 %}
            1 
          {% elif O3 | int < 30 %}
            2
          {% elif O3 | int < 40 %}
            3
          {% elif O3 | int < 60 %}
            4
          {% elif O3 | int < 80 %}
            5
          {% elif O3 | int < 100 %}
            6
          {% elif O3 | int < 140 %}
            7
          {% elif O3 | int < 180 %}
            8
          {% elif O3 | int < 200 %}
            9
          {% elif O3 | int < 240 %}
            10
          {% else %}
            11
          {% endif %}
  - sensor:
      - name: "Airquality combined index"
        state: >
          {% set NO2 = states('sensor.airquality_no2_index') | int(0)  %}
          {% set O3 = states('sensor.airquality_o3_index') | int(0)  %}
          {% set PM10 = states('sensor.airquality_pm10_index') | int(0)  %}

          {%set mylist = states('sensor.airquality_no2_index')|int,
                states('sensor.airquality_o3_index')|int,
                states('sensor.airquality_pm10_index')|int %}

          {% if NO2 | int == O3 | int == PM10 | int -%}
            {% set Index = NO2 | int + 1 %}
            {{Index | int}}
          {%- else -%}
            {{ mylist|max | int }}
          {%- endif %}
1 Like