Clothes dryers start something like 14,000 house fires a year in the US, and the overwhelming majority of them trace back to one boring, preventable cause: lint. Lint collects in the exhaust duct, restricts airflow, and the dryer’s heating element keeps dumping heat into air that can no longer carry it away. Temperatures climb, and eventually you have a fire hazard sitting behind a machine nobody looks at. I got tired of relying on a vague “clean your vent sometime” mental note, so I built a fully local ESPHome device that watches my dryer’s exhaust temperature and screams at me through Home Assistant when something looks wrong. No cloud, no subscription, about twelve dollars in parts. Here is exactly how I did it.
Why dryer fires actually happen
It helps to understand the failure mode before you try to detect it. A clothes dryer works by blowing hot air through wet clothes and carrying the moisture, and a surprising amount of shed fiber, out through the exhaust duct. The lint trap catches a chunk of that fiber, but not all of it. The rest coats the inside of the duct, the transition hose, and the exterior vent flap. Over months, that coating narrows the effective diameter of the duct.
Here is the dangerous part. The dryer’s heating element and thermostat are calibrated around a normal rate of airflow. When lint restricts the duct, less air moves, so less heat gets carried away from the drum and the exhaust. The air temperature rises. On most machines the high-limit thermostat will eventually cycle the heat, but that thermostat can stick, and even when it works, you are now running your dryer hot and long every single load. Add a pile of dry, fluffy, flammable lint sitting in a hot metal tube and you have the recipe for those fires.
The insight I built the whole project around: a clogged vent shows up as elevated exhaust temperature and longer run times well before it becomes a fire. That is a measurable, trendable signal. All I needed was a cheap sensor on the duct and something to watch the numbers.
The hardware
The whole build is deliberately boring, which is what you want for a fire-safety device. Fewer moving parts, fewer failure modes. Here is my parts list:
- An ESP32 dev board. I like the ESP32 over an ESP8266 here because it has more headroom and rock-solid WiFi, and they come cheap in multi-packs so you have spares.
- A DS18B20 waterproof temperature probe. The stainless-steel-tipped version on a lead is perfect. It handles the temperature range easily, uses the simple 1-Wire protocol, and the sealed tip is easy to mount.
- A 4.7k ohm resistor as the 1-Wire pull-up between the data line and 3.3V. Most probe kits include one.
- A short USB power supply and a small project box.
- Optional: a differential pressure sensor module if you want to measure airflow restriction directly later.
- A roll of aluminum foil dryer vent tape to mount the probe and seal any duct openings.
Wiring is trivial. The DS18B20 has three leads: red to 3.3V, black to ground, and yellow (data) to a GPIO pin, with the 4.7k resistor bridging data and 3.3V. I used GPIO4. That is the entire circuit. If you can push three wires into header sockets, you can build this.
A note on the DS18B20’s temperature ceiling. The bare sensor is rated to about 125C (257F), and the waterproof probe assemblies are usually good to around 105C to 125C depending on the cable jacket. Dryer duct surface temperatures in normal operation sit far below that, typically 120F to 150F, so you have plenty of margin. If your dryer is somehow pushing the probe past its rating, that itself is the emergency you are trying to detect.
Mounting the probe on the duct
Placement matters more than anything else in this build. You want the probe reading air that represents what the dryer is actually exhausting, which means as close to the dryer outlet as you can get, in the first few feet of duct.
I went with a surface mount rather than drilling into the airflow. I cleaned a spot on the metal transition duct near the dryer outlet, laid the stainless probe tip flat against the metal, and wrapped it tightly with foil tape so the tip has good thermal contact with the duct wall. Surface temperature runs a bit cooler than the actual air inside, but it tracks the air temperature faithfully, and for trend detection that is all you need. It also means zero new holes in your exhaust path, which is the correct instinct: you never want to introduce a leak point where hot, lint-laden air can escape into the wall cavity.
If you prefer measuring air temperature directly, you can drill a small hole, insert the probe tip a couple of centimeters into the airflow, and seal the entry point completely with foil tape. It reads a few degrees hotter and responds faster, but I did not think the extra sensitivity was worth cutting into the duct. Whichever you choose, keep the probe out of the exterior run where cold outdoor air will skew your readings.
The ESPHome config
This is where ESPHome earns its keep. The entire firmware is a YAML file, and once flashed, the device auto-discovers in Home Assistant with no cloud account and no integration glue. Here is the config I run. Note the !secret references for WiFi credentials, which live in your secrets.yaml and never get baked into the shared config.
esphome:
name: dryer-vent-monitor
friendly_name: Dryer Vent Monitor
esp32:
board: esp32dev
framework:
type: arduino
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Dryer-Vent-Fallback"
password: !secret fallback_password
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
# 1-Wire bus for the DS18B20 probe
one_wire:
- platform: gpio
pin: GPIO4
sensor:
- platform: dallas_temp
name: "Dryer Exhaust Temperature"
id: dryer_exhaust_temp
update_interval: 10s
filters:
# smooth out sensor jitter
- sliding_window_moving_average:
window_size: 6
send_every: 6
# Rolling average the automations can trend on
- platform: template
name: "Dryer Exhaust Temp Average"
id: dryer_exhaust_avg
unit_of_measurement: "F"
lambda: "return id(dryer_exhaust_temp).state;"
update_interval: 60s
filters:
- exponential_moving_average:
alpha: 0.1
send_every: 1
# Derive a simple running/idle state from temperature
binary_sensor:
- platform: template
name: "Dryer Running"
id: dryer_running
lambda: |-
if (id(dryer_exhaust_temp).state > 95.0) {
return true;
} else if (id(dryer_exhaust_temp).state < 85.0) {
return false;
} else {
return id(dryer_running).state;
}
A few things worth calling out. I report the temperature in Fahrenheit because that is how I think about it; if you prefer Celsius, drop the unit_of_measurement override and adjust the thresholds. The sliding_window_moving_average filter kills the sensor noise that would otherwise cause false spikes. The template binary sensor gives me a free “dryer is running” signal derived purely from temperature, with hysteresis (95F to turn on, 85F to turn off) so it does not flap. That means I did not need a separate power sensor to know when the dryer is active, though a smart plug energy monitor would be more precise if you have one.
Flashing is the usual ESPHome dance: install the ESPHome add-on or CLI, plug the ESP32 in over USB for the first flash, and every update after that goes over the air. Within a minute of booting, Home Assistant offered to adopt the device and I had sensor.dryer_exhaust_temperature live on my dashboard.
Establishing a baseline
Do not skip this part. The single most useful thing you can do is run the sensor for a week or two before you set a single alert, because “normal” is specific to your dryer, your duct length, and even your typical laundry loads. A short duct venting straight through a wall behaves very differently from a 25-foot run with three elbows.
I ran mine through a dozen loads and watched the History graph in Home Assistant. My dryer settled into a clear pattern: a ramp from room temperature up to a plateau around 135F to 145F during the heated portion of a cycle, a dip during the cool-down, and a return to ambient afterward. Heavy loads like towels ran a little hotter and longer than a light load of shirts, which is exactly the kind of natural variance you want to characterize before you draw a threshold line.
Two numbers came out of that baseline period. First, my normal peak surface temperature, about 145F. Second, my normal cycle duration, roughly 50 minutes. Those became the anchors for every automation below. If you want to be rigorous, note your peaks in a spreadsheet across a few different load types and take the highest normal value as your reference.
You can also apply a calibration offset in ESPHome if you want the reading to match actual air temperature. Tape a reference thermometer probe into the airflow, compare it to the surface reading, and add a - offset: filter under the sensor. I did not bother, because for detecting a rising trend the absolute accuracy is irrelevant. Consistency is what matters.
Home Assistant automations
Now the payoff. I built three layers of alerting, from “pay attention” to “shut it down.”
The first is the hard ceiling. If the exhaust ever exceeds my normal peak by a healthy margin, I want to know immediately regardless of anything else. I set this at 175F, about 30F above my 145F normal.
automation:
- alias: "Dryer vent overheat alert"
trigger:
- platform: numeric_state
entity_id: sensor.dryer_exhaust_temperature
above: 175
for:
minutes: 2
action:
- service: notify.mobile_app_phone
data:
title: "Dryer vent running HOT"
message: >-
Exhaust hit {{ states('sensor.dryer_exhaust_temperature') }}F.
Check the vent for blockage before running another load.
data:
priority: high
ttl: 0
The for: minutes: 2 guard is important. It prevents a single noisy reading from paging you; the temperature has to genuinely hold above the ceiling. I send it as a high-priority notification so it breaks through my phone’s do-not-disturb, because this is the alert I actually care about.
The second layer watches run time. A vent that is starting to clog does not always spike the peak temperature, but it does make the dryer run longer to get clothes dry, and it keeps the duct hot longer. This automation fires if the dryer has been in its running state past my normal cycle length.
- alias: "Dryer cycle running abnormally long"
trigger:
- platform: state
entity_id: binary_sensor.dryer_running
to: "on"
for:
minutes: 75
action:
- service: notify.mobile_app_phone
data:
title: "Dryer has run 75+ minutes"
message: >-
Long cycle can indicate restricted airflow. Worth checking
the lint trap and vent.
Seventy-five minutes is my 50-minute normal plus a generous buffer. If it trips regularly, that is itself a signal the vent needs attention.
The cleaning reminder that actually works
This is my favorite automation and the one I think most people overlook. A single hot cycle is worth an alert, but the real value is catching the slow creep. As lint accumulates over months, the average peak temperature drifts upward a few degrees at a time. No single load looks alarming, but the trend is unmistakable if you are tracking it.
I use a Home Assistant statistics sensor to compute the mean of the daily peak exhaust temperature over a rolling 30-day window, then compare recent weeks against it.
sensor:
- platform: statistics
name: "Dryer Peak Temp 30d Average"
entity_id: sensor.dryer_exhaust_temperature
state_characteristic: mean
max_age:
days: 30
sampling_size: 2000
Then a template-based automation nudges me when the recent average climbs meaningfully above the longer baseline:
automation:
- alias: "Dryer vent cleaning reminder"
trigger:
- platform: numeric_state
entity_id: sensor.dryer_peak_temp_30d_average
above: 155
action:
- service: notify.mobile_app_phone
data:
title: "Time to clean the dryer vent"
message: >-
30-day average exhaust temp has climbed to
{{ states('sensor.dryer_peak_temp_30d_average') }}F.
Airflow is likely getting restricted by lint.
When that reminder fires, I pull out the dryer vent cleaning brush kit, disconnect the duct, and run the brush through the full length. The satisfying part is watching the 30-day average drop right back down afterward on the History graph. That before-and-after is the proof the whole system works: I can see the airflow restriction accumulate, and I can see it disappear after cleaning. It turns “clean the vent sometime” into a data-driven maintenance schedule.
Adding airflow sensing (optional)
Temperature is a proxy for airflow, and a good one, but if you want to measure restriction more directly you can add a differential pressure sensor module. The idea is to measure the pressure differential across a section of duct while the dryer runs. As lint builds and airflow drops, the pressure signature shifts. ESPHome supports several I2C pressure sensors, so you wire it to the same ESP32 and expose it as another sensor.
I built the temperature-only version first and honestly have not felt the need to add pressure sensing. It is more plumbing, it needs its own calibration, and temperature has caught everything so far. But if you have a long, convoluted duct run where restriction builds subtly, a second independent signal is a reasonable upgrade. Treat it as a phase two, not a requirement.
Honest limitations
I need to be blunt about what this device is and is not, because fire safety is not a place for wishful thinking.
This is an early-warning aid. It is not a substitute for actually cleaning your vent, and it is absolutely not a substitute for a real smoke alarm. A DS18B20 on a duct tells you the exhaust is running hot; it does not detect combustion, it does not detect smoke, and it will not wake you up in a fire the way a UL-listed smoke detector will. Keep working smoke alarms in your laundry area regardless. If you do not already have local smoke and CO alerting wired into Home Assistant, that is a more important project than this one.
There are also real edge cases. The temperature-derived “dryer running” sensor is a clever hack, not a ground truth; a very short or no-heat cycle can confuse it, and a dedicated energy monitor would be more reliable. The probe only sees one point in the duct, so a blockage far downstream near the exterior vent may show up weaker than one near the dryer. And the whole thing depends on your ESP32 staying powered and connected, so I added a Home Assistant automation that alerts me if the device goes unavailable for more than an hour, because a fire monitor that silently died is worse than useless.
Finally, thresholds are personal. Every number in this post is calibrated to my dryer. Copy the structure, not the values, and spend the time to establish your own baseline.
The Bottom Line
For about twelve dollars in parts and an afternoon of work, I turned my dryer from an opaque fire risk into something I can actually monitor and trend. The ESP32 and DS18B20 combination is cheap, reliable, and completely local, and ESPHome makes the Home Assistant integration effortless. The three-layer alerting, a hard overheat ceiling, a long-run-time warning, and a data-driven cleaning reminder, has already changed how I maintain my vent: instead of guessing, I clean when the 30-day average tells me airflow is dropping, and I watch it recover afterward.
Is it a magic fire preventer? No. It is an early-warning instrument that makes an invisible, slowly-worsening hazard visible, and that is genuinely valuable. Pair it with regular cleaning and a real smoke alarm and you have covered the problem from three angles instead of zero. If you run Home Assistant and you have a clothes dryer, this is one of the highest-value-per-dollar builds I have done. Grab an ESP32 and a waterproof probe and build it this weekend.