From ESP32 Weather Station to Home Security Monitor with HC-SR501 PIR Sensor
A few months ago I cobbled together a simple ESP32 weather station that dutifully reported temperature and humidity to my Home Assistant dashboard. It worked great until my cat decided the breadboard was a better bed than the couch. While the weather station slowly transitioned into a fur-covered sensor graveyard, I needed something else to do with the spare ESP32 Development Board Module I had lying around. That’s when the idea hit me: why not repurpose the same hardware I used for a weather station into a dead‑simple motion alert system? I dug up an HC‑SR501 PIR sensor, grabbed a handful of LEDs, and within an hour I had a blinking security monitor that now guards my hallway. If you’ve ever built an ESP32 weather station, you already own most of the parts for this project. If you haven’t, don’t worry—the bill of materials is about as short as it gets.
This guide walks you through wiring, configuring ESPHome, and deploying the whole thing without touching a single line of C++. I’ll share the mistakes I made (I somehow shorted a red LED to 5 V and turned it into an expensive smoke pellet) and the troubleshooting steps that saved me from re‑flashing five times. By the end, you’ll have a way to know if someone—or something with four legs—is moving where they shouldn’t be.
Materials & Tools
You don’t need much for this project. In fact, if you already built an ESP32 weather station, you probably have half of it sitting on your desk right now.
- ESP32 Development Board Module – any variant with Wi‑Fi will do. I used a feather‑sized board but the classic DevKitC works perfectly. ESP32-DevKit board: Buy it here
- HC‑SR501 PIR Motion Sensor Module – ubiquitous, cheap, and surprisingly reliable. Comes with two trim pots for sensitivity and delay.
- LED ×3 – Red, Green, Blue (or any colors you like). The red indicates motion, green indicates idle, and blue tells me if Wi‑Fi is connected.
- Current‑limiting resistors – 220 Ω or 330 Ω, one per LED.
- Breadboard and jumper wires – I used a mini breadboard to keep everything from getting scratched by playful cats.
- Micro‑USB cable – for programming and power.
- Laptop or Raspberry Pi with ESPHome installed – I’m using the Docker version of ESPHome, but
pip install esphomeworks just as well.
Optional but highly recommended: a small 5 V USB power bank to place the monitor wherever you like.
Repurposing an ESP32 Weather Station: The Concept
This isn’t my first rodeo with an ESP32 weather station. When I built one with a Xiaomi Mijia sensor a while back, I learned two things: ESPHome handles low‑level sensor polling like a champ, and the ESP32’s Wi‑Fi is just bulletproof enough for always‑on use. The same principles apply here. Instead of reading temperature and humidity, we’ll read a binary sensor (the PIR’s digital output) and react to its state changes. Three LEDs will serve as a simple visual indicator—no dashboard required unless you want it.
The logic is dead simple:
- When the PIR sees motion, it pulls its output HIGH for a few seconds (adjustable via the delay pot).
- ESPHome catches that
on_pressevent and turns on the red LED, while turning off the green one. - When the signal goes LOW, we reverse the colors.
- The blue LED acts as a status light, solid when the ESP32 stays connected to Wi‑Fi.
Because we’re using ESPHome, we don’t need to write any code that manually polls GPIOs, manages debounce, or handles reconnection. We just describe what we want and press upload.
Wiring Guide
Avoid the mistake I made: double‑check that you’re connecting the PIR’s power to the 3.3 V pin, not 5 V. The HC‑SR501 works at 5 V but its logic output is 3.3 V tolerant. Feeding 5 V into the ESP32’s 3.3 V pin will release the magic smoke (ask me how I know). Skip the 5 V pin entirely if you can—most HC‑SR501 modules even run happily on 3.3 V, just test yours first.
Here’s the exact mapping I used:
| Component Pin | ESP32 GPIO Pin | Notes |
|---|---|---|
| HC‑SR501 VCC | 3.3 V | Do not connect to 5 V. |
| HC‑SR501 GND | GND | |
| HC‑SR501 OUT | GPIO13 | PIR digital output |
| Red LED Anode | GPIO12 | Through 220 Ω resistor to GND |
| Green LED Anode | GPIO14 | Through 220 Ω resistor to GND |
| Blue LED Anode | GPIO27 | Through 220 Ω resistor to GND |
If you’re using a different board, just pick any capable GPIOs and update the ESPHome YAML. I chose these because they were far enough apart on the breadboard to avoid physical shorts—GPIO12 is especially prone to boot issues if pulled low at startup, but when used as an output with a 220 Ω resistor it’s fine.
Software Setup: ESPHome Instead of Arduino IDE
There are plenty of tutorials that show how to set up the IDE ESP32 environment in Arduino with manual board manager URLs and library installations. While that’s perfectly capable, it feels like eating soup with a fork compared to ESPHome for this kind of project. So I’m leaning into it.
If you don’t have ESPHome installed yet, the official installation guide covers everything from Docker to Home Assistant add‑ons. I run the Docker container like this:
docker run --rm -v "${PWD}":/config -it esphome/esphome wizard motion-alert.yaml
This starts an interactive wizard that asks for the device name, board type, and Wi‑Fi credentials. Choose esp32 as the board and fill in your network details. After the wizard finishes, you’ll get a motion-alert.yaml file that we’ll tweak.
Open that file and replace its contents with the configuration below. I’ll walk through each section right after.
esphome:
name: motion-alert
platform: ESP32
board: esp32dev
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Motion Alert Fallback"
password: "fallback123"
captive_portal:
logger:
api:
password: !secret api_password
ota:
password: !secret ota_password
# Status LED (blue) – solid when connected, blinks on disconnect
status_led:
pin: GPIO27
binary_sensor:
- platform: gpio
pin: GPIO13
name: "HC-SR501 Motion Sensor"
device_class: motion
on_press:
then:
- light.turn_on: red_led
- light.turn_off: green_led
on_release:
then:
- light.turn_on: green_led
- light.turn_off: red_led
output:
- platform: gpio
pin: GPIO12
id: red_output
- platform: gpio
pin: GPIO14
id: green_output
light:
- platform: binary
name: "Red LED"
output: red_output
id: red_led
- platform: binary
name: "Green LED"
output: green_output
id: green_led
What’s happening in this YAML
esphome,wifi,api,ota: Standard boilerplate. I use!secretto keep passwords out of version control, but you can hardcode them if you’re the only one touching the file. Theapfallback creates a hotspot if Wi‑Fi fails—super handy for recovery.status_led: One line of config turns the blue LED into a connection indicator. When the ESP32 connects to Wi‑Fi, it goes solid; when it disconnects, it blinks. No extra code.binary_sensor: The PIR is declared as agpiobinary sensor withdevice_class: motion. Theon_pressandon_releaseautomations handle the LED dances. You can also send events to Home Assistant via the native API, but out of the box you get working visual feedback.output+light: Each LED is defined as abinarylight backed by a GPIO output. This lets us reference them by IDred_ledandgreen_ledin the automations. I deliberately left the blue LED as astatus_ledto keep things simple.
One thing I initially forgot: the HC‑SR501 needs a minute to stabilize after power‑up. During that time it might fire a few false positives. If you find the red LED blinking wildly at boot, either add a delayed_on filter or just ignore it—the sensor calms down on its own.
Upload & Test
With the YAML saved, plug in your ESP32 via USB. If you already have a working ESP32 weather station image on there, you can flash over USB without entering boot mode manually—ESPHome’s flashing tool handles it.
Open a terminal in the directory containing your motion-alert.yaml and run:
esphome run motion-alert.yaml
The first compilation will download all dependencies, which can take a few minutes. Once the binary compiles, ESPHome will automatically connect to the board and perform the upload. If you get a “Timed out waiting for upload port” error, try holding down the BOOT button while plugging in the USB cable, then release it after the upload starts. I’ve had finicky ESP32s that require this every time, especially when they previously ran another firmware.
After the upload completes, watch the serial logs with:
esphome logs motion-alert.yaml
You’ll see lines like:
[19:23:45][D][binary_sensor:036]: 'HC-SR501 Motion Sensor': Sending state ON
[19:23:48][D][binary_sensor:036]: 'HC-SR501 Motion Sensor': Sending state OFF
Wave your hand in front of the sensor. Within a second (adjustable by the delay pot), the red LED should light up and the green LED should turn off. Stand still, and after the PIR’s internal timeout the green LED comes back on. The blue LED should be solid, confirming the board is happily talking to your Wi‑Fi.
If you don’t see any logs, double‑check the PIR’s output pin with a multimeter. I once spent 20 minutes re‑flashing only to discover the jumper wire had come loose from the breadboard.
Troubleshooting Common Pitfalls
Blank Serial Output / Upload Failures
Make sure the USB cable supports data transfer—many cheap cables are power‑only. Test the cable on another device, or try a different cable. Also ensure you have the correct driver for CP210x or CH340 chips. On macOS, I’ve had to run sudo kextunload /Library/Extensions/SiLabsUSBDriver.kext before the device shows up.
False Motion Triggers
The HC‑SR501 is sensitive to temperature changes, air currents, and even Wi‑Fi signals. Adjust the sensitivity pot (small orange trimmer) counter‑clockwise to reduce range. If you’re testing on a breadboard, watch out for pets—my cat triggered the sensor so many times I thought I’d invented a feline detection algorithm by accident. A 100 nF capacitor between VCC and GND can clean up some noise if you’re running long wires.
Red LED Burns Out Immediately
You used too small a resistor or—like me—connected it straight to 5 V bypassing the ESP32 GPIO. Always use a current‑limiting resistor and confirm the voltage. The ESP32’s GPIO outputs 3.3 V, and a 220 Ω resistor keeps the current around 10 mA, safe for most standard LEDs.
PIR Stays HIGH Forever
The delay pot might be maxed out. Turn it counter‑clockwise to reduce the hold‑time. If the sensor still stays on indefinitely, it could be in re‑triggering mode (selected with a jumper on some modules) causing it to extend the pulse. Set the jumper to “non‑repeatable trigger” for our simple on/off logic.
“NaN” Sensor Readings? Not Here
You won’t see NaN since we’re only reading a binary state, but I’ve fought NaN in ESP32 weather station builds when sensor wires were loose. If you later add a DHT to this project and see those dreaded letters, check wiring and confirm the sensor’s VCC is stable.
Enhancements & Next Steps
Once you have the blinking lights working, you can take this project in a dozen directions:
Home Assistant Integration: ESPHome natively talks to Home Assistant via the API. The binary sensor automatically shows up as
binary_sensor.hc_sr501_motion_sensor. Create automations to send phone notifications, trigger a siren, or turn on cameras. I use it to pipe motion data into a Grafana alerting setup with custom messages that texts me when I’m not home.Add a Buzzer: Replace the red LED with a small piezo buzzer for an audible alarm. You can even configure ESPHome’s
rtttlcomponent to play a tune when motion is detected—nothing quite like a doorbell version of Super Mario to scare a visitor.Deep Sleep with Wake Stub: If you’re running on batteries, deep‑sleep is a must. The HC‑SR501 can wake the ESP32 via an interrupt pin. My next article will cover exactly how to build an ESP32 weather station that sleeps between readings—the same approach works here.
Temperature & Humidity Add‑on: Since you already have an ESP32, why not pop a DHT22 or BME280 onto the breadboard and turn it back into a full weather station? I described a similar combination in my ESP32 + Xiaomi Mijia temperature guide that might spark some ideas.
Zigbee Alliance: If Wi‑Fi feels too chatty, you can migrate the motion sensor to a Zigbee network. My Zigbee2MQTT Aqara temperature sensor walkthrough covers the hardware you need to get started with Zigbee.
FAQ
What if I don’t want to use ESPHome? Can I code this in Arduino IDE?
Absolutely. You’d write a sketch that polls the PIR pin and sets LED outputs accordingly, and handle Wi‑Fi manually. Search for “IDE ESP32 setup” to find the board manager URL and instructions. That said, ESPHome simplifies everything, and once you try it you’ll wonder why you ever babysat WiFi.begin() loops.
The blue status LED doesn’t turn on. Is my ESP32 broken?
Probably not. The status_led component only lights up after a successful Wi‑Fi connection. Verify your SSID and password in the secrets file. You can check the serial logs for connection errors. If the board can’t connect, it will blink the LED instead of keeping it solid.
Can I use a different PIR sensor, like the AM312?
Yes, just wire its output to a different GPIO and change the pin number in the YAML. The logic is identical. The AM312 is smaller and draws less power, but the HC-SR501 is easier to tweak with the potentiometers.
Why is my red LED on and green off all the time after flashing?
Your PIR might be seeing constant motion (cats) or the sensitivity is too high. Turn the sensitivity pot fully counter‑clockwise and test. Also, some HC-SR501 modules wake up in a triggered state—wait about 60 seconds for it to settle.
How can I turn this back into an ESP32 weather station later?
Just add a temperature/humidity sensor like the DHT22 to the YAML and remove the PIR. The hardware foundation is identical. You’d define a sensor: block and read the temperature and humidity, then maybe log it to Home Assistant. I’ve done exactly that in my ESP32 weather station projects several times.
Do I need the green LED if I have the red one? Seems redundant.
You don’t need it, but it’s nice to have instant visual confirmation that the system is alive and no motion is happening. Without it, you’d only see the blue status LED, and you wouldn’t know at a glance whether the last motion event timed out properly. I find the traffic‑light color scheme oddly satisfying.
With an hour of tinkering, a bit of YAML, and a cat that no longer sits on breadboards, you’ll have a dependable motion monitor that owes its existence to your old ESP32 weather station parts. No proprietary hubs, no cloud subscriptions—just an ESP32, a PIR sensor, and a handful of LEDs doing exactly what you told them to do. If your hallway lights up red in the middle of the night, you’ll know something is stirring. And if it’s just the cat again, well, at least the monitor works.