AQI From PMS7003 pm2.5 sensor

EDIT 06/2024: The US AQI thresholds have been updated and you should use the updated script, below

I live in Northern Californa, and for this fire season I recently got a couple PMS7003 particulate sensor from China, along with a couple of ESP32 dev boards. This sensor has a laser diode that can measure the particulate concentration at pm2.5 (particles > 2.5µm) and pm10 (particles > 10µm), and ESPHome has a config that knows how to read the data and send it to HA via the ESPHome integration.

However, as far as I can tell HA doesn’t have any sort of filter to convert the concentration readings to AQI, which is a farily simple but non-linear calculation. So here’s the template I whipped up, based on The AQI Equation (2015 - Obsolete on May 6th, 2024) - Air Quality and AQI Info - AirNow Discussion Forum

templates:
- sensor:
    - name: "Family Room AQI (pm2.5)"
      state: >-
        {% macro aqi(val, val_l, val_h, aqi_l, aqi_h) -%}
          {{(((aqi_h-aqi_l)/(val_h - val_l) * (val - val_l)) + aqi_l)|round(0)}}
        {%- endmacro %}
        {% set v = states('sensor.family_room_pm2_5')|round(1) %}
        {% if v <= 12.0 %}
           {{aqi(v, 0, 12.0, 0, 50)}}
        {% elif 12.0 < v <= 35.4 %}
           {{aqi(v, 12.1, 35.4, 51, 100)}}
        {% elif 35.4 < v <= 55.4 %}
           {{aqi(v, 35.5, 55.4, 101, 150)}}
        {% elif 55.4 < v <= 150.5 %}
           {{aqi(v, 55.5, 150.4, 151, 200)}}
        {% elif 150.4 < v <= 250.4 %}
           {{aqi(v, 150.4, 250.4, 201, 300)}}
        {% elif 250.5 < v <= 500.4 %}
           {{aqi(v, 250.5, 500.4, 301, 500)}}
        {% else %}
           Holy shit it's bad.  Get out!
        {% endif %}
      unit_of_measurement: AQI
      device_class: aqi

Relatedly, here’s a fragment of the ESPHome config I’m using for the sensor. It takes readings every second, which is way too much data. This chops that down to every minute, I could really probably get away with every 5 or 10 minutes and be just fine:

sensor:
  - platform: pmsx003
    type: PMSX003
    pm_2_5:
      name: "pm2.5"
      unit_of_measurement: "µg/m³"
      icon: "mdi:face-mask-outline"
      state_class: "measurement"
      filters:
        - sliding_window_moving_average:
            window_size: 60
            send_every: 60
    pm_10_0:
      name: "pm10"
      unit_of_measurement: "µg/m³"
      icon: "mdi:face-mask-outline"
      state_class: "measurement"
      filters:
        - sliding_window_moving_average:
            window_size: 60
            send_every: 60
10 Likes

Thank you SO much for this :slight_smile:

As of May 2024 the US AQI uses new calculations, here’s an updated template

templates:
- sensor:
    - name: "Family Room AQI (pm2.5)"
      state: >-
        {% macro aqi(val, val_l, val_h, aqi_l, aqi_h) -%}
          {{(((aqi_h-aqi_l)/(val_h - val_l) * (val - val_l)) + aqi_l)|round(0)}}
        {%- endmacro %}
        {% set v = states('sensor.family_room_pm2_5')|round(1) %}
        {% if v <= 9.0 %}
           {{aqi(v, 0, 9.0, 0, 50)}}
        {% elif 9.0 < v <= 35.4 %}
           {{aqi(v, 9.1, 35.4, 51, 100)}}
        {% elif 35.4 < v <= 55.4 %}
           {{aqi(v, 35.5, 55.4, 101, 150)}}
        {% elif 55.4 < v <= 125.4 %}
           {{aqi(v, 55.5, 125.4, 151, 200)}}
        {% elif 125.4 < v <= 225.4 %}
           {{aqi(v, 125.5, 225.4, 201, 300)}}
        {% elif 250.5 < v <= 500.4 %}
           {{aqi(v, 250.5, 500.4, 301, 500)}}
        {% else %}
           Holy shit it's bad.  Get out!
        {% endif %}
      unit_of_measurement: AQI
      device_class: aqi
4 Likes