Build a Room Occupancy Tracker with ESP32 and Ultrasonic Sensors
I’ve been tinkering with smart home automation for years, always looking for clever ways to make my home more responsive. Commercial room occupancy sensors work fine, but they’re expensive and often limited in functionality. After one too many instances of my smart lights turning off while I was still reading in my favorite armchair, I decided to build my own room occupancy tracker using an ESP32 and ultrasonic sensors.
The beauty of this approach? You get precise, real-time occupancy data that integrates seamlessly with Home Assistant, all for under $15 in components. Let me show you how I built mine.
Why I Chose Ultrasonic Sensors Over PIR
Most commercial occupancy sensors use Passive Infrared (PIR) technology, which detects motion through heat changes. The problem? PIR sensors have a fatal flaw: if you stay still for too long, they think you’ve left the room. Not ideal for home offices or reading nooks.
Ultrasonic sensors work by emitting high-frequency sound waves and measuring how long they take to bounce back. By monitoring subtle changes in distance readings, we can detect stationary presence with remarkable accuracy. It’s like having a miniature sonar system for your room.
What You’ll Need
Before we dive in, here’s the shopping list:
- ESP32 Development Board (Amazon | AliExpress) - The brains of the operation
- HC-SR04 Ultrasonic Sensor (Amazon | AliExpress) - For distance measurement
- Breadboard and Jumper Wires - For easy prototyping
- Micro-USB Cable - For power and programming
- 5V Power Supply - Optional for permanent installation
Hardware Setup: Connecting the Dots
Let’s wire everything up. The HC-SR04 has four pins: VCC, Trig, Echo, and GND.
Here’s how to connect them to your ESP32:
- HC-SR04 VCC → ESP32 5V pin
- HC-SR04 GND → ESP32 GND pin
- HC-SR04 Trig → ESP32 GPIO 5 (via voltage divider)
- HC-SR04 Echo → ESP32 GPIO 18 (via voltage divider)
The Essential Voltage Divider
Since the HC-SR04’s Echo pin outputs 5V, we need to step it down to 3.3V for the ESP32. Create a simple voltage divider with two resistors:
- Connect a 1kΩ resistor between Echo and GPIO 18
- Connect a 2kΩ resistor between GPIO 18 and GND
This will reduce the 5V signal to approximately 3.3V, keeping your ESP32 safe from damage.
ESPHome Configuration: The Magic Sauce
Now for the software part. I’m using ESPHome because it makes deploying to ESP32 devices incredibly simple and integrates perfectly with Home Assistant.
Create a new ESPHome configuration file called room-occupancy.yaml
:
esphome:
name: room-occupancy-tracker
platform: ESP32
board: nodemcu-32s
wifi:
ssid: "Your_WiFi_SSID"
password: "Your_WiFi_Password"
# Enable logging for debugging
logger:
# Enable Home Assistant API
api:
encryption:
key: "your_encryption_key_here"
ota:
password: "your_ota_password"
# Ultrasonic sensor configuration
sensor:
- platform: ultrasonic
trigger_pin: GPIO5
echo_pin: GPIO18
name: "Room Distance"
id: room_distance
update_interval: 1s
unit_of_measurement: "cm"
filters:
- median:
window_size: 5
send_every: 3
- throttle: 500ms
on_value:
then:
- if:
condition:
- lambda: 'return (id(room_distance).state > 30.0) && (id(room_distance).state < 300.0);'
then:
- binary_sensor.template.publish:
id: room_occupied
state: ON
else:
- binary_sensor.template.publish:
id: room_occupied
state: OFF
# Binary sensor to represent occupancy
binary_sensor:
- platform: template
name: "Room Occupied"
id: room_occupied
device_class: occupancy
Let me break down the key parts:
- The
median
filter smooths out erratic readings by taking the middle value of the last 5 measurements throttle
prevents the sensor from updating too frequently- The lambda function checks if the distance is within our “occupied” range (30cm to 300cm in this example)
- The template binary sensor provides a clean ON/OFF occupancy state for Home Assistant
Calibration is Key
You’ll need to adjust the distance thresholds based on your room layout. Here’s how I calibrated mine:
# Alternative calibration method with multiple zones
sensor:
- platform: ultrasonic
# ... previous configuration
on_value:
then:
- if:
condition:
- lambda: |-
static float empty_distance = 400.0; // Distance when room is empty
float current = id(room_distance).state;
return (abs(empty_distance - current) > 50.0) && (current < 350.0);
then:
- binary_sensor.template.publish:
id: room_occupied
state: ON
Home Assistant Integration
Once your ESP32 is running ESPHome, it should automatically appear in Home Assistant. If it doesn’t, check your ESPHome logs and make sure both devices are on the same network.
Create a simple automation to test your new sensor:
# In your Home Assistant automations.yaml
- alias: "Turn on lights when room occupied"
trigger:
platform: state
entity_id: binary_sensor.room_occupied
to: "on"
action:
service: light.turn_on
entity_id: light.room_lights
data:
brightness: 255
- alias: "Turn off lights when room empty"
trigger:
platform: state
entity_id: binary_sensor.room_occupied
to: "off"
for:
minutes: 5
action:
service: light.turn_off
entity_id: light.room_lights
Troubleshooting Common Issues
I’ve built several of these sensors, and here are the problems I’ve encountered:
Problem: Inconsistent readings or sensor timeout
- Solution: Increase the
update_interval
to 2s and ensure your voltage divider is correctly wired. The HC-SR04 needs time between measurements.
Problem: False positives (says occupied when empty)
- Solution: Adjust your distance thresholds and add a longer median filter window. Moving objects like ceiling fans can trigger the sensor.
Problem: ESP32 won’t connect to WiFi
- Solution: Check your WiFi credentials and signal strength. The ESP32 can be fussy about 5GHz networks, so try 2.4GHz if you’re having issues.
Problem: Sensor readings are consistently wrong
- Solution: Measure the actual empty-room distance and update your thresholds accordingly. Room geometry affects ultrasound reflection patterns.
Taking It Further
Once you have basic occupancy tracking working, here are some enhancements I’ve implemented:
Multiple Sensor Array: Place sensors at different heights and angles for more accurate whole-room coverage.
Presence Probability: Instead of simple ON/OFF, calculate a probability score based on multiple sensors and recent activity patterns.
Energy Monitoring Integration: Combine with smart plug energy data to distinguish between “person present” and “just electronics running.”
Time-based Sensitivity: Reduce sensitivity during nighttime hours to prevent false triggers from pets or house noises.
Frequently Asked Questions
Q: How accurate is ultrasonic occupancy detection compared to PIR? A: Ultrasonic is significantly better for detecting stationary presence but can be more susceptible to false triggers from air movement or vibrations. For most home use, it’s a worthwhile trade-off.
Q: Can I use multiple sensors in one room? A: Absolutely! You’ll need to configure them on different GPIO pins and potentially add delays between measurements to prevent interference.
Q: What’s the maximum range for reliable detection? A: The HC-SR04 works best within 2-4 meters. For larger spaces, consider using multiple sensors or upgrading to industrial-grade ultrasonic sensors.
Q: How does this compare to mmWave sensors? A: mmWave radar sensors (like the LD2410) are more accurate and can detect breathing, but they’re also more expensive and complex to set up. Ultrasonic strikes a great balance of cost and performance.
Q: Can I battery-power this setup? A: The ESP32 and HC-SR04 are relatively power-hungry. For battery operation, consider using deep sleep mode and only taking measurements periodically.
Building your own room occupancy tracker is not just about saving money—it’s about having complete control over your smart home’s behavior. The satisfaction of walking into a room and having the lights turn on automatically, knowing you built the system yourself, is hard to beat.
If you enjoyed this project, you might want to check out my other ESP32 tutorials like monitoring home temperature with ESP32 and Xiaomi sensors or setting up Zigbee2MQTT with Aqara sensors.
Happy building!