Skip to content

jpettitt/purpleair-local

Repository files navigation

PurpleAir Local

HACS Custom GitHub Release License Maintenance Validate Tests

A Home Assistant custom integration that polls PurpleAir PA-II (and compatible) sensors directly on the LAN — no cloud, no API key.

Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.   Open your Home Assistant instance and start setting up a new integration.

See DESIGN.md for architecture and the decisions behind the implementation.

Why local

  • Works with the internet down.
  • No PurpleAir cloud rate limits, no API key, no third-party data sharing.
  • Sub-minute polling is possible (sensor minimum is 10 s; we default to 120 s to match the sensor's natural averaging cadence).

What it provides

Per configured sensor:

  • PM1.0 / PM2.5 / PM10 mass concentration (µg/m³, ATM density) — always a primary entity; channel A and channel B added on dual-laser units. Primary on dual is the simple A/B average for v0.1.
  • PM2.5 AQI — one entity per enabled correction, using the 2024-revised US EPA breakpoint table. Default: raw (no concentration correction) and EPA (Barkjohn 2021). AQandU and LRAPA available via the options flow.
  • Temperature, humidity, dewpoint, pressure — only when the sensor has a BME280 or BME680. BME680 values preferred when both are present. Note: the temperature reading runs a few degrees high. The BME sits inside the PurpleAir enclosure and picks up heat from the laser counters and the ESP processor; PurpleAir's own guidance for outdoor units is to subtract roughly 8 °F (≈ 4.4 °C) from the reported value.
  • VOC resistance (Ω) — only when the sensor has a BME680.
  • Particle counts in six size bins, primary only, disabled by default. Enable individually from the device page when you want them.
  • Diagnostics — WiFi signal, uptime, free heap, firmware version, last reported timestamp. Free heap and firmware are disabled by default.
  • Online binary sensor — reflects the coordinator's last poll status.
  • Channel disagreement binary sensor — dual-laser only. Trips when both PurpleAir thresholds are crossed (default ≥5 µg/m³ AND ≥70 %), configurable in options.

Single-laser sensors (some indoor PA-II units) skip the channel-B entities and the disagreement binary sensor automatically.

Install

The quick way — two clicks if your Home Assistant browser session is already authenticated:

Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.   Open your Home Assistant instance and start setting up a new integration.

The first button opens HACS pointed at this repo (you'll still need to click Download in HACS and then restart Home Assistant). The second button opens the integration's config flow once it's installed.

The manual equivalent:

  1. HACS → Integrations → ⋮ → Custom repositories.
  2. Add https://github.com/jpettitt/purpleair-local as an Integration.
  3. Install "PurpleAir Local" from the list, then restart Home Assistant.
  4. Settings → Devices & Services → Add Integration → "PurpleAir Local".
  5. Enter the sensor's IP. Repeat for each sensor.

If a sensor's IP later changes (DHCP), edit it from the integration's Configure screen — the integration verifies the SensorId still matches and updates the host in place. No need to delete and re-add.

Dashboard examples

Each AQI entity carries two extra attributes that any card supporting templates can read:

  • category — a stable snake-case label for the current band (good, moderate, unhealthy, etc., or the UK DAQI form low_1very_high_10 if you've selected that scheme).
  • category_color — the official hex colour for that band.

The colour scheme is per-user, selected in the integration's options flow. Default is US EPA (AirNow); EU EAQI and UK DAQI are available.

Mushroom — icon coloured by AQI

mushroom-template-card is the lightest-weight way to get a coloured icon next to the AQI number. Pick the entity for whichever correction you want (_aqi_raw, _aqi_epa, _aqi_aqandu, _aqi_lrapa).

type: custom:mushroom-template-card
entity: sensor.outdoor_0119_aqi_epa
icon: mdi:weather-hazy
icon_color: |
  {{ state_attr('sensor.outdoor_0119_aqi_epa', 'category_color') }}
primary: |
  {{ states('sensor.outdoor_0119_aqi_epa') }} AQI
secondary: |
  {{ state_attr('sensor.outdoor_0119_aqi_epa', 'category')
       | replace('_', ' ') | title }}

apexcharts-card — coloured line, banded background, or both

apexcharts-card has a built-in color_threshold for series so the line itself changes colour as the AQI moves through the bands:

type: custom:apexcharts-card
header:
  show: true
  title: Outdoor AQI (24h)
graph_span: 24h
series:
  - entity: sensor.outdoor_0119_aqi_epa
    name: AQI (EPA)
    type: line
    stroke_width: 3
    color_threshold:
      - value: 0
        color: "#00e400"
      - value: 51
        color: "#ffff00"
      - value: 101
        color: "#ff7e00"
      - value: 151
        color: "#ff0000"
      - value: 201
        color: "#8f3f97"
      - value: 301
        color: "#7e0023"

For a single-point sparkline coloured by the current band, drive the colour from the entity attribute directly (apexcharts evaluates EVAL: strings as JS against the hass object):

type: custom:apexcharts-card
chart_type: radialBar
series:
  - entity: sensor.outdoor_0119_aqi_epa
    color: |
      EVAL:hass.states['sensor.outdoor_0119_aqi_epa'].attributes.category_color

button-card — full tile with the category as a background

custom:button-card lets you push the category colour all the way to the card background for a glanceable indoor/outdoor at-a-glance:

type: custom:button-card
entity: sensor.outdoor_0119_aqi_epa
name: Outdoor AQI
show_state: true
styles:
  card:
    - background-color: "[[[ return entity.attributes.category_color ]]]"
    - color: white
    - font-weight: bold
    - padding: 16px
  state:
    - font-size: 2em

Built-in cards

The standard HA Entity / Tile cards don't take templates in their color field today, so for the built-ins you'll either use one of the custom cards above or wrap a template sensor — see HA's template sensor docs for the pattern.

Development

Test suite

python3 -m venv .venv
.venv/bin/pip install -r requirements_test.txt
make test

Local HA testbed (Docker)

A docker-compose.yml at the repo root mounts custom_components/purpleair_local/ read-only into a disposable Home Assistant container, so edits to the integration are picked up by HA on the next reload. Requires Docker (Desktop or compatible).

make ha-up        # boots HA at http://localhost:8123 (first start ~1 min)
make ha-logs      # tail container logs
make ha-restart   # restart HA after editing the integration
make ha-down      # stop, keep config
make ha-reset     # wipe runtime config — back to the onboarding screen

First boot walks through HA's user-creation flow; after that, add the integration from Settings → Devices & Services → Add Integration → "PurpleAir Local". Runtime data (database, secrets, logs) lives under .dev/ha-config/ and is gitignored.

License

MIT.

About

Home Assistant custom integration for local-only polling of PurpleAir PA-II sensors. No cloud, no API key.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors