PurpleAir Local MQTT

TLDR: Using a Purple Air indoor sensor, I was able to see an enormous drop in smoke particulate count with a new filter installed.

Github: GitHub - PeterWoolery/PurpleAirLocalMQTT: A simple script to convert JSON payloads from PurpleAir

Setup:

  • PurpleAir Indoor Sensor on Wifi
  • LXC container on Proxmox running a JSON to MQTT script. It is rough, so it needs hourly reboots, but that’s where containers are great, right. Just a couple of cron jobs and I don’t think about it.
  • 3M Ultra-Fine 2800 / Merve 14 filters
  • Homeassitant running Mosquito.

With crazy wildfires becoming an annual thing, we decided to get a PurpleAir indoor sensor to see how the air quality is in our house and business. Today the 2.5 AQI outdoor in San Jose was 165, and my indoor was creeping up to 90. So I decided to swap out the “FilterBuy” MERV 13 filter with a 3m MERV 14 filter (2800 in their numbering). It was amazing to see the drop at different particulate sizes. Three hours later, we’re down below 50 AQI indoors. More impressive to me is the drop in 0.3um particulates

You could pull the PurpleAir via a REST sensor directly in HA.
Look at the PurpleAir threads (there are not many). There are some nice canvas gauges you can utilize.
We have had a good clear stretch for a week in Western Washington.
aqi

Thanks GlenHA, that gauge is awesome.

I tried the built in json parsing that I found on here, but had lots of problems with them. I couldn’t see the data. Do you know if there is a way to debug the built in jdon parsing to see where the issue was?

Since I use python and json in my day job, and already have a procmox homeland, it was much faster for me to spin up this little script.

https://github.com/GlennGoddard/CanvasGaugeBackgrounds

When I get back home I will post my sensor yaml for everything PurpleAir. I have a sensor that states the nomenclature of the air quality such as Good, Moderate, Unheathy…
I also have automations that announce the change from one category to another over smart speakers and notifications to phones.

Phones:

alias: Notification Air Quality Change - Phones
description: Phones
trigger:
  - platform: state
    entity_id: sensor.purpleair_description
    for: '0:10:00'
condition:
  - condition: time
    before: '20:30:00'
    after: '04:00:00'
action:
  - service: notify.mobile_app_glenn_s21
    data:
      message: >-
        {{states['sensor.time'].state}} Air Quality is
        {{states('sensor.purpleair_description') }} at AQI
        {{states('sensor.purpleair_aqi_a')}}
  - service: notify.mobile_app_karen_s21
    data:
      message: >-
        {{states['sensor.time'].state}} Air Quality is
        {{states('sensor.purpleair_description')}} at AQI
        {{states('sensor.purpleair_aqi_a')}}
  - service: notify.mobile_app_ktab
    data:
      message: >-
        {{states['sensor.time'].state}} Air Quality is
        {{states('sensor.purpleair_description')}} at AQI
        {{states('sensor.purpleair_aqi_a')}}
  - service: input_text.set_value
    target:
      entity_id: input_text.aqi_last
    data:
      value: '{{states(''sensor.purpleair_description'') | string}}'
mode: single

Speakers:

alias: Notification Air Quality Change - Speaker Office
description: WiFi Speaker
trigger:
  - platform: state
    entity_id: sensor.purpleair_description
    for: '0:10:00'
condition:
  - condition: state
    entity_id: light.leviton_light_office
    state: 'on'
action:
  - service: media_player.volume_set
    data:
      volume_level: 0.9
    entity_id: media_player.rpispeaker2
  - service: tts.marytts_say
    entity_id: media_player.rpispeaker2
    data_template:
      message: >-
        Air Quality Index is {{ states('sensor.purpleair_description') }} at {{
        states('sensor.purpleair_aqi_a') }}
mode: single

This tread has some good info:
https://community.home-assistant.io/t/purpleair-air-quality-sensor/146588

Here is my yaml for importing the local PurpleAir. Change the IP to whatever yours is at.
I don’t bring in the three you see commented out since I saw no need to.

### PurpleAir Local
- platform: rest
  name: 'PurpleAir'
  resource: http://10.74.1.198/json?live=false
  value_template: '{{ value_json.SensorId }}'
  scan_interval: 120
  json_attributes:
    #- Mem
    #- memfrag
    - uptime
    - rssi
    #- Adc
    - current_temp_f
    - current_humidity
    - current_dewpoint_f
    - pressure
    - p_0_3_um
    - p_0_3_um_b
    - p_0_5_um
    - p_0_5_um_b
    - p_1_0_um
    - p_1_0_um_b
    - pm1_0_atm
    - pm1_0_atm_b
    - p_2_5_um
    - p_2_5_um_b
    - pm2_5_atm
    - pm2_5_atm_b
    - p_5_0_um
    - p_5_0_um_b
    - p_10_0_um
    - p_10_0_um_b
    - pm10_0_atm
    - pm10_0_atm_b
    - pm2.5_aqi
    - pm2.5_aqi_b
- platform: template
  sensors:
   # purpleair_memory:
   #   friendly_name: "PurpleAir Memory"
   #   value_template: '{{ states.sensor.purpleair.attributes["Mem"] }}'
   # purpleair_adc:
   #   friendly_name: "PurpleAir ADC"
   #   value_template: '{{ states.sensor.purpleair.attributes["Adc"] }}'
    purpleair_uptime:
      friendly_name: "PA Uptime"
      value_template: '{{ states.sensor.purpleair.attributes["uptime"] }}'
      unit_of_measurement: "seconds"
   # purpleair_memory_fragmentation:
   #   friendly_name: "PurpleAir Memory Fragmentation"
   #   value_template: '{{ states.sensor.purpleair.attributes["memfrag"] }}'
    purpleair_wifi_rssi:
      friendly_name: "PA Wifi RSSI"
      value_template: '{{ states.sensor.purpleair.attributes["rssi"] }}'
      device_class: signal_strength
    purpleair_temp:
      friendly_name: "PA Temperature"
      value_template: '{{ states.sensor.purpleair.attributes["current_temp_f"] }}'
      unit_of_measurement: "F"
      device_class: temperature
    purpleair_humidity:
      friendly_name: "PA Humidity"
      value_template: '{{ states.sensor.purpleair.attributes["current_humidity"] }}'
      unit_of_measurement: "%"
      device_class: humidity
    purpleair_dewpoint:
      friendly_name: "PA Dewpoint"
      value_template: '{{ states.sensor.purpleair.attributes["current_dewpoint_f"] }}'
      unit_of_measurement: "F"
      device_class: temperature
    purpleair_pressure:
      friendly_name: "PA Pressure"
      value_template: '{{ states.sensor.purpleair.attributes["pressure"] }}'
      unit_of_measurement: "mbar"
      device_class: pressure
    purpleair_aqi_a:
      friendly_name: "PA AirQuality A"
      value_template: '{{ states.sensor.purpleair.attributes["pm2.5_aqi"] }}'
      unit_of_measurement: "AQI"
    purpleair_aqi_b:
      friendly_name: "PA AirQuality B"
      value_template: '{{ states.sensor.purpleair.attributes["pm2.5_aqi_b"] }}'
      unit_of_measurement: "AQI"
    purpleair_p_0_3_um_a:
      friendly_name: "PA .3um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_0_3_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_0_3_um_b:
      friendly_name: "PurpleAir .3um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_0_3_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_0_5_um_a:
      friendly_name: "PA .5um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_0_5_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_0_5_um_b:
      friendly_name: "PA .5um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_0_5_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_1_0_um_a:
      friendly_name: "PA 1.0um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_1_0_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_1_0_um_b:
      friendly_name: "PA 1.0um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_1_0_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_2_5_um_a:
      friendly_name: "PA 2.5um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_2_5_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_2_5_um_b:
      friendly_name: "PA 2.5um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_2_5_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_5_0_um_a:
      friendly_name: "PA 5.0um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_5_0_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_5_0_um_b:
      friendly_name: "PA 5.0um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_5_0_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_10_0_um_a:
      friendly_name: "PA 10.0um Partical Count A"
      value_template: '{{ states.sensor.purpleair.attributes["p_10_0_um"] }}'
      unit_of_measurement: "um/dl"
    purpleair_p_10_0_um_b:
      friendly_name: "PA 10.0um Partical Count B"
      value_template: '{{ states.sensor.purpleair.attributes["p_10_0_um_b"] }}'
      unit_of_measurement: "um/dl"
    purpleair_pm10_atm_a:
      friendly_name: "PA 10.0um Mass A"
      value_template: '{{ states.sensor.purpleair.attributes["pm10_0_atm"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_pm10_atm_b:
      friendly_name: "PA 10.0um Mass B"
      value_template: '{{ states.sensor.purpleair.attributes["pm10_0_atm_b"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_pm1_atm_a:
      friendly_name: "PA 1.0um Mass A"
      value_template: '{{ states.sensor.purpleair.attributes["pm1_0_atm"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_pm1_atm_b:
      friendly_name: "PA 1.0um Mass B"
      value_template: '{{ states.sensor.purpleair.attributes["pm1_0_atm_b"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_pm2_5_atm_a:
      friendly_name: "PA 2.5um Mass A"
      value_template: '{{ states.sensor.purpleair.attributes["pm2_5_atm"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_pm2_5_atm_b:
      friendly_name: "PA 2.5um Mass B"
      value_template: '{{ states.sensor.purpleair.attributes["pm2_5_atm_b"] }}'
      unit_of_measurement: "ug/m3"
    purpleair_description:
      friendly_name: 'PA AQI Description'
      unique_id: purpleair_description
      value_template: >
        {% set aqi = states('sensor.purpleair_aqi_a')|float %}
        {% if aqi >= 401.0 %}
          Very Hazardous
        {% elif aqi >= 301.0 %}
          Hazardous
        {% elif aqi >= 201.0 %}
          Very Unhealthy
        {% elif aqi >= 151.0 %}
          Unhealthy
        {% elif aqi >= 101.0 %}
          Unhealthy for Sensitive Groups
        {% elif aqi >= 51.0 %}
          Moderate
        {% elif aqi >= 0.0 %}
          Good
        {% else %}
          {{states('sensor.purpleair_description')}}
        {% endif %}
      entity_id: sensor.purpleair
      #availability_template: '{{ not is_state("sensor.purpleair", "unavailable") }}'

1 Like