Skip to main content
NestGrid logoNestGrid

Build a DIY Wildfire Early-Warning System in Home Assistant

Learn how to combine an outdoor PM2.5/PM10 sensor, CO detector, local weather station, and public fire alert feeds in Home Assistant to detect approaching wildfire particulate changes up to 1,000 times earlier than indoor smoke alarms, with field-tested advance notice of up to 37 minutes — and how to set up conditional multi-factor triggers to avoid false alarms.

Indoor smoke alarms are the wrong first sensor for approaching wildfire smoke. They are still nonnegotiable life-safety devices, but they usually react after smoke has entered the building. The useful gap for smart home fire detection devices for wildfire safety is outside: particulate matter rising at the edge of the property, wind pushing from the known fire direction, a gas reading moving with it, and a public alert feed saying the wider threat context is real.

That is why the DHS Science and Technology Directorate wildfire sensor work is worth paying attention to. DHS describes wildfire detection sensors measuring particulate concentrations from 0 to 1000 micrograms per cubic meter, roughly 1,000 times more sensitive than a standard residential smoke alarm, and reports a Colorado field-test event where an alpha sensor provided up to 37 minutes of advance notice before 911 calls came in.[1] That is not a promise that a patio sensor will always buy 37 minutes. It is a field-tested reason to build an outdoor warning layer that is explicit about uncertainty instead of pretending an indoor alarm is the first available signal.

Home Assistant dashboard showing outdoor air quality, CO level, wind speed, and a wildfire smoke alert

The System You Are Actually Building

The recipe is a local, multi-input warning system. Home Assistant does not decide there is a wildfire because one PM2.5 number crossed a line. It watches several different signals and escalates only when they agree: outdoor particulate matter rises, CO or gas also moves, wind makes the source direction plausible, and an official or public alert feed raises the background risk.

The Firewize Home Assistant bushfire guide uses that same kind of conditional structure: IoT PM2.5/PM10 sensors, CO readings, weather thresholds, and public alert data combined into automations rather than treated as isolated dashboard tiles.[2] This article adapts that logic for a Home Assistant wildfire setup in the U.S. context, with EPA PM2.5 breakpoints as practical anchors and NWS or CAL FIRE-style alert feeds as the public-warning layer.

The complete parts stack can usually be assembled in the estimated $150-$400 range, depending on whether you already own a compatible hub, coordinator, and weather station.
RoleImplementation pathHome Assistant routeWhy it is in the system
Outdoor PM2.5/PM10 sensorMCO Home MH10-PM2.5, AirGradient Open Air, or similar particulate sensorZigbee2MQTT/ZHA for Zigbee; ESPHome or local Wi-Fi API for AirGradient-style devicesPrimary early smoke signal; DHS benchmarks make this category worth using before indoor alarms [1]
CO/gas sensorFibaro Smoke Sensor, Heiman Zigbee sensor, or another HA-visible gas/smoke deviceZ-Wave JS, Zigbee2MQTT, ZHA, or vendor-local integration where availableSecondary confirmation so dust, pollen, or single-sensor drift does not trigger the full response
Local wind and weatherEcowitt or Ambient Weather stationLocal API or Home Assistant integration, preferably without cloud-only dependencyConfirms that wind direction and speed make the particulate spike plausible
Public alert conditionNWS Alerts integration, CAL FIRE-style incident feed, or another reliable local fire-alert sourceHome Assistant integration, REST sensor, or alert entityRaises escalation only when the wider fire-danger context supports it
Optional flame confirmationIR flame sensor on ESPHomeESPHome binary sensorLate-stage confirmation near the property; not the core early-warning input

The important shopping distinction is not brand loyalty. It is whether the device exposes the right entity locally and predictably. A Zigbee PM sensor that reports through your own coordinator is more useful during a fire-weather evening than a prettier device whose app cloud is the only path into the data.

System architecture showing particulate, gas, and weather inputs feeding Home Assistant actions

Particulate Sensing Is the Load-Bearing Input

PM2.5 is the main number to watch because wildfire smoke air-monitoring programs treat fine particulate matter as the key measurement for smoke exposure. The EPA WSMART program centers PM2.5 sensors in wildfire smoke monitoring and uses AQI breakpoints that are practical enough to turn into automation thresholds: PM2.5 above 55 micrograms per cubic meter corresponds to Unhealthy for Sensitive Groups, while above 150 micrograms per cubic meter corresponds to Unhealthy for All.[3]

Those are not magic numbers for detection. A local wildfire plume may matter before the EPA AQI category changes, and a dusty afternoon can move a low-cost sensor without any nearby fire. The values are useful because they give your automation a documented severity ladder instead of a random threshold copied from someone else’s dashboard.

EPA AQI thresholds are useful trigger anchors, but they should be combined with baseline movement and other sensors rather than used alone.
PM2.5 conditionSuggested Home Assistant useAction level
Above local 7-day baseline by a meaningful marginWatch mode; log event and compare with wind and gas readingsNo household interruption
>55 µg/m³ sustained for 5 minutesCandidate trigger if CO/gas and wind also support itNotify adults; prepare HVAC action
>150 µg/m³ sustained for 5 minutesHigh-severity trigger when paired with alert feed or confirmed wind directionShut down HVAC intake, notify, and run emergency scene

Dryad’s Silvanet wildfire sensor technical data is a helpful reality check for anyone expecting lab-instrument behavior from small particulate sensors. Dryad documents accuracy of ±10 micrograms per cubic meter in the 0-100 micrograms per cubic meter range and ±10% in the 101-1000 micrograms per cubic meter range.[4] Consumer devices are not identical to Dryad’s product, but the ranges are a useful reminder: the automation should care about sustained movement and agreement between signals, not one exact reading at one exact second.

Place the Sensors for Smoke Arrival, Not Dashboard Neatness

Put the outdoor particulate sensor where incoming air reaches the house before it reaches an open window, attic vent, or HVAC intake. Under an eave is usually better than fully exposed rain, but avoid trapping it in a dead-air pocket. If you live where the fire approach is usually from a known canyon, ridge, or prevailing wind corridor, place the sensor so it sees that air path early.

  • Do not mount the PM sensor directly above a barbecue, dryer vent, chimney, or idling driveway.
  • Keep it protected from direct water and hard sun if the enclosure was not designed for full exposure.
  • Mount the weather station where wind direction is not dominated by a wall, fence, or roof eddy.
  • Keep the CO/gas sensor close enough to share the air event, but not so close to the PM sensor that one local nuisance source fools both.
  • If you add an IR flame sensor, aim it at a specific high-risk zone; do not treat it as a wide-area early detector.

There is field evidence behind this ground-level approach. Oregon Hazards Lab describes DHS-supported wildfire smoke sensors co-located with detection cameras across 15 Oregon sites, with PM and gas sensors detecting wildfires before visual confirmation in the 2023-2024 deployment context.[5] That does not mean a residential sensor at one home gets the same coverage as a research network. It does support the basic bet: smoke and gas at ground level can be actionable before a person sees flame or before smoke fills the house.

Bring the Inputs Into Home Assistant Locally

The cleanest setup is boring: every sensor becomes a normal Home Assistant entity, every entity has a clear unit, and the automation still runs if a vendor app is down. For Zigbee devices, use a coordinator with Zigbee2MQTT or ZHA. For Z-Wave devices, use Z-Wave JS. For ESPHome devices, keep the firmware and entity names under your control. For weather stations, prefer a local API integration over a cloud polling path when your hardware supports it.

Device typeCoordinator or integrationEntity examples to create or rename
MCO Home-style Zigbee PM sensorZigbee2MQTT or ZHAsensor.outdoor_pm25, sensor.outdoor_pm10
AirGradient-style Wi-Fi sensorESPHome or local integrationsensor.outdoor_pm25, sensor.outdoor_pm10
Fibaro Z-Wave smoke/CO deviceZ-Wave JSsensor.outdoor_co_ppm, binary_sensor.smoke_alarm
Heiman Zigbee gas/smoke sensorZigbee2MQTT or ZHAsensor.outdoor_gas, binary_sensor.smoke_detected
Ecowitt or Ambient Weather stationLocal integration where availablesensor.wind_speed, sensor.wind_bearing, sensor.outdoor_humidity
NWS or CAL FIRE-style public feedNWS Alerts integration, REST sensor, or feed parserbinary_sensor.fire_alert_active, sensor.fire_danger_rating

If you already built API-driven weather automations, the public alert layer is the same pattern. The companion recipe Automate Tropical Depression Warnings in Home Assistant is the same mental model: let the official feed change the risk state, then let local sensors decide whether the house should act.

Before writing automations, normalize the names. A readable entity map matters when you are debugging at 1 a.m. with wind noise outside.

# configuration.yaml or a packages file
# Replace these entity IDs with your real sensor entities.
template:
  - binary_sensor:
      - name: wildfire_pm25_elevated
        unique_id: wildfire_pm25_elevated
        state: >
          {{ states('sensor.outdoor_pm25') | float(0) > 55 }}
        delay_on:
          minutes: 5

      - name: wildfire_public_alert_active
        unique_id: wildfire_public_alert_active
        state: >
          {{ is_state('binary_sensor.fire_alert_active', 'on')
             or states('sensor.fire_danger_rating') in ['Extreme', 'Catastrophic'] }}

      - name: wildfire_co_elevated
        unique_id: wildfire_co_elevated
        state: >
          {{ states('sensor.outdoor_co_ppm') | float(0) >
             states('input_number.wildfire_co_baseline') | float(0) + 5 }}
        delay_on:
          minutes: 5

That first template uses the EPA 55 micrograms per cubic meter breakpoint as a candidate trigger, not as the full alarm.[3] The CO template compares against your own baseline because a fixed value will be brittle across sensor types and mounting locations.

Give the System a First Week Before You Trust It

The first seven days are not wasted time. They are the difference between a system that sees your place and a system that only knows a data sheet. During that week, log PM2.5, PM10, CO or gas, wind speed, wind bearing, humidity, temperature, and public alert state. Do not connect the automation to locks, HVAC, or sirens yet.

The goal is to identify the normal local nuisances: cooking exhaust that drifts past the side yard, a neighbor’s fire pit, road dust, pollen, sensor warm-up behavior, and the daily humidity swing. Dryad describes AI-based classification for distinguishing wildfire signatures from other smoke sources in its own system, but a Home Assistant build should use a simpler rule: no major escalation until several independent conditions agree and remain true long enough to matter.[4]

  • Create helpers for PM2.5 baseline, CO baseline, and normal wind approach direction.
  • Use History or Recorder data to find the normal range at the sensor location, not just the manufacturer’s expected range.
  • Mark known nuisance events in a logbook so you can compare the sensor pattern later.
  • Leave high-consequence actions disabled until the system has survived at least one normal week.
  • Treat the first real smoke event as another calibration event unless official evacuation information says otherwise.

A simple way to handle baselines is to store them manually after the first week. If you want to get fancier, use statistics sensors, but manual helpers are easier to audit during an emergency.

input_number:
  wildfire_pm25_baseline:
    name: Wildfire PM2.5 baseline
    min: 0
    max: 200
    step: 1
    unit_of_measurement: "µg/m³"

  wildfire_co_baseline:
    name: Wildfire CO baseline
    min: 0
    max: 100
    step: 1
    unit_of_measurement: "ppm"

  wildfire_wind_from_min:
    name: Wildfire wind bearing min
    min: 0
    max: 359
    step: 1
    unit_of_measurement: "°"

  wildfire_wind_from_max:
    name: Wildfire wind bearing max
    min: 0
    max: 359
    step: 1
    unit_of_measurement: "°"

The Trigger Should Be Conditional, Not Dramatic

The tempting automation is one line: if PM2.5 is high, sound the alarm. That is how you build a nuisance machine. The better version is a cascade. PM2.5 has to rise and stay elevated. CO or gas has to move with it. Wind has to be strong enough and coming from a plausible direction. A public alert or fire-danger condition has to be active before the automation touches anything disruptive.

Decision flow for PM2.5, CO, wind, and public alert conditions before wildfire escalation

Firewize’s guide uses weather thresholds including temperature at or above 35°C, humidity at or below 40%, and wind at or above 35 km/h as part of bushfire automation logic.[2] In a U.S. Home Assistant setup, you can keep those as an imported default or adjust them to local fire-weather guidance. The point is not that every climate must use the same values. It is that wind and humidity are part of the decision, not decorative dashboard cards.

template:
  - binary_sensor:
      - name: wildfire_wind_matches_risk_direction
        unique_id: wildfire_wind_matches_risk_direction
        state: >
          {% set bearing = states('sensor.wind_bearing') | float(999) %}
          {% set speed_mph = states('sensor.wind_speed') | float(0) %}
          {% set min_b = states('input_number.wildfire_wind_from_min') | float(0) %}
          {% set max_b = states('input_number.wildfire_wind_from_max') | float(359) %}
          {% set fast_enough = speed_mph >= 22 %}
          {% if min_b <= max_b %}
            {{ fast_enough and min_b <= bearing <= max_b }}
          {% else %}
            {{ fast_enough and (bearing >= min_b or bearing <= max_b) }}
          {% endif %}

      - name: wildfire_weather_supports_spread
        unique_id: wildfire_weather_supports_spread
        state: >
          {{ states('sensor.outdoor_humidity') | float(100) <= 40
             and is_state('binary_sensor.wildfire_wind_matches_risk_direction', 'on') }}
        delay_on:
          minutes: 5

The wind-speed conversion in that example is approximate: 35 km/h is about 22 mph. If your station reports meters per second, convert before comparing. If your terrain creates dangerous downslope winds below that threshold, tune the helper after watching real weather data rather than trusting the imported value.

A Copyable Escalation Automation

This automation has two outputs: a warning path and a high-severity path. The warning path notifies people and records context. The high-severity path can shut down HVAC intake, unlock doors, flash exterior lights, and announce over speakers. Start with notifications only. Add the physical actions after you have tested entity names, household expectations, and failure modes.

automation:
  - alias: Wildfire Early Warning - Multi Sensor Escalation
    id: wildfire_early_warning_multi_sensor_escalation
    mode: single

    trigger:
      - platform: state
        entity_id:
          - binary_sensor.wildfire_pm25_elevated
          - binary_sensor.wildfire_co_elevated
          - binary_sensor.wildfire_weather_supports_spread
          - binary_sensor.wildfire_public_alert_active
        to: "on"
        for:
          minutes: 2

    condition:
      - condition: state
        entity_id: binary_sensor.wildfire_pm25_elevated
        state: "on"
      - condition: state
        entity_id: binary_sensor.wildfire_co_elevated
        state: "on"
      - condition: state
        entity_id: binary_sensor.wildfire_weather_supports_spread
        state: "on"

    action:
      - choose:
          - conditions:
              - condition: state
                entity_id: binary_sensor.wildfire_public_alert_active
                state: "on"
              - condition: numeric_state
                entity_id: sensor.outdoor_pm25
                above: 150
            sequence:
              - service: notify.mobile_app_your_phone
                data:
                  title: "Wildfire smoke escalation"
                  message: >
                    PM2.5 is {{ states('sensor.outdoor_pm25') }} µg/m³,
                    CO is {{ states('sensor.outdoor_co_ppm') }} ppm,
                    wind is {{ states('sensor.wind_speed') }} mph from
                    {{ states('sensor.wind_bearing') }} degrees, and a public
                    fire alert condition is active.
              - service: climate.turn_off
                target:
                  entity_id: climate.main_hvac
              - service: lock.unlock
                target:
                  entity_id:
                    - lock.front_door
                    - lock.garage_entry
              - service: light.turn_on
                target:
                  entity_id: light.exterior_lights
                data:
                  flash: long
              - service: media_player.volume_set
                target:
                  entity_id: media_player.house_speakers
                data:
                  volume_level: 0.8
              - service: tts.speak
                target:
                  entity_id: tts.home_assistant_cloud
                data:
                  media_player_entity_id: media_player.house_speakers
                  message: >
                    Wildfire smoke conditions are detected outside. Check official alerts now.

        default:
          - service: notify.mobile_app_your_phone
            data:
              title: "Wildfire smoke watch"
              message: >
                Outdoor PM2.5, CO, and wind conditions are elevated, but the
                public fire alert condition is not active or PM2.5 is below the
                high-severity threshold. Check the dashboard before taking action.

The public alert condition is deliberately required only for the high-severity branch in this version. That keeps the system useful when smoke arrives before a feed updates, while preventing a single local sensor event from unlocking doors or interrupting HVAC. If your household would rather never run physical actions without an official alert, move the public-alert condition into the main condition block.

For emergency-stack builders, this pairs naturally with load-shedding logic from Automate Power Outage Load Shedding with Home Assistant. A wildfire warning and a grid instability warning should not fight over the same HVAC, battery, or door-lock actions.

False Alarms Are a Design Problem, Not an Afterthought

A false push notification is annoying. A false HVAC shutdown during bad heat, a false siren, or an unnecessary unlocked door is a real household consequence. That is why the automation should escalate in stages and leave a visible audit trail.

Signal patternLikely interpretationAutomation response
PM2.5 spike only; no CO movement; no wind matchDust, sensor settling, pollen, or local nuisance sourceLog only or low-priority dashboard badge
PM2.5 and CO rise; wind does not match risk directionNearby smoke source, barbecue, fire pit, or drifting neighborhood smokeNotify as watch; no physical actions
PM2.5 and CO rise; wind matches; no public alertPossible early local smoke arrival before public feed updatesNotify adults with context; require manual confirmation for HVAC and locks
PM2.5 and CO rise; wind matches; public alert activeHigh-confidence wildfire smoke conditionRun escalation scene according to household plan

The automation should also reset gracefully. Once PM2.5 and CO drop below your chosen thresholds for a sustained period, clear the watch state, leave a logbook entry, and return HVAC control to normal only if that is safe for your system. Avoid rapid cycling. Smoke plumes can pulse.

automation:
  - alias: Wildfire Watch Clear
    id: wildfire_watch_clear
    mode: single

    trigger:
      - platform: template
        value_template: >
          {{ states('sensor.outdoor_pm25') | float(0) < 35
             and is_state('binary_sensor.wildfire_co_elevated', 'off') }}
        for:
          minutes: 30

    action:
      - service: logbook.log
        data:
          name: Wildfire watch
          message: >
            Conditions cleared. PM2.5 is {{ states('sensor.outdoor_pm25') }} µg/m³
            and CO elevated state is {{ states('binary_sensor.wildfire_co_elevated') }}.
      - service: notify.mobile_app_your_phone
        data:
          title: "Wildfire smoke watch cleared"
          message: "Outdoor particulate and CO conditions have remained below watch levels for 30 minutes."

What to Automate, and What to Leave Manual

Notifications are easy. Physical actions deserve more restraint. A useful wildfire automation tells you exactly why it fired, then takes the low-regret actions first: close motorized vents if you have them, turn off whole-house fans, stop fresh-air intake, flash exterior lights for visibility, and wake the adults who are responsible for decisions.

  • Good automatic actions: push notification, dashboard alarm card, logbook entry, exterior light flashing, HVAC fan or fresh-air intake shutdown where your equipment supports it safely.
  • Actions to confirm manually first: unlocking all doors, opening garage doors, sounding sirens, notifying neighbors, or waking children with speaker announcements.
  • Actions to avoid: anything that contradicts an official evacuation order, disables required life-safety alarms, or assumes a single sensor is more reliable than public emergency information.

If you use smart locks, decide the lock behavior before fire season. Some households want doors unlocked during a high-confidence warning so evacuation is simpler. Others prefer a notification that puts the lock buttons in front of the adults but leaves the doors secured. Home Assistant can do either; the hard part is choosing the consequence you can live with when the system is right and when it is wrong.

Optional Flame Confirmation

An IR flame sensor on ESPHome can be useful near a detached shed, a vulnerable fence line, or a known vegetation edge. It is not the early-warning core. Flame confirmation is late compared with particulate and gas movement. Use it as an additional high-severity confirmation for a specific area, not as a substitute for the PM2.5, CO, wind, and alert-feed logic.

# Example ESPHome-style binary sensor name after adoption into Home Assistant:
# binary_sensor.north_fence_ir_flame

template:
  - binary_sensor:
      - name: wildfire_flame_confirmation
        unique_id: wildfire_flame_confirmation
        state: >
          {{ is_state('binary_sensor.north_fence_ir_flame', 'on')
             and is_state('binary_sensor.wildfire_pm25_elevated', 'on') }}
        delay_on:
          seconds: 10

Testing Before Fire Weather

Test the recipe with helper overrides, not real smoke. Temporarily lower the PM threshold, set a test fire-alert helper to on, and confirm the notification text includes PM2.5, CO, wind speed, wind bearing, and alert state. Then test every physical action separately. HVAC entities, lock entities, and speaker services fail in different ways.

  1. Verify each sensor updates in Home Assistant without opening a vendor app.
  2. Confirm units: PM2.5 in µg/m³, CO in ppm, wind in the unit your template expects, and bearing in degrees.
  3. Run the first week in notify-only mode and collect baseline data.
  4. Trigger the warning branch with test helpers and confirm no physical action runs.
  5. Trigger the high-severity branch with test helpers and watch every action complete.
  6. Restore real thresholds and leave a dashboard card showing current sensor values and the four decision states.

If YAML is the slow part, the recipe Build a Gemini Gem as Your Home Assistant Automation Coach is useful for turning your real entity names into draft automations. Still read the generated YAML before pasting it. This is not a lighting scene.

The Careful Claim

A Home Assistant wildfire early-warning system can give a household earlier local information than indoor smoke alarms when it combines outdoor PM2.5/PM10 sensing, CO or gas movement, wind context, and a public alert condition. DHS field results make the sensor category credible, including a reported Colorado event with up to 37 minutes of advance notice before 911 calls, and EPA PM2.5 thresholds give practical trigger anchors.[1][3]

It is still not a certified life-safety replacement for smoke alarms, evacuation orders, public alerts, or human judgment. The useful version is calibrated, conditional, local, and honest about what each signal proves. That is enough to make it worth building.

References

  1. Technology to Reduce the Impacts of Wildfires, DHS Science and Technology Directorate
  2. Home Assistant Bushfire Detection Guide, Firewize, 2025
  3. Wildfire Smoke Air Monitoring Response Technology (WSMART), EPA
  4. Silvanet Wildfire Sensor Technical Data, Dryad Networks
  5. Wildfire Smoke Sensors, Oregon Hazards Lab

Related reading

Feedback / Question

Did a step not work as written? Let us know so it can be corrected.

Blogarama - Blog Directory