ESP32-S3 Camera Pan-Tilt Security Tracker: Build a Smart ESP32 Weather Station
I set out to build a pan-tilt security camera with an ESP32-S3 and ended up creating a fully-fledged ESP32 weather station while I was at it. The idea was simple: I wanted to keep an eye on the packages that couriers keep leaving in the rain, but also to know exactly how wet and miserable those packages were getting. Adding a cheap temperature and humidity sensor to the camera mount turned the project into something far more useful—a smart weather station that could see and feel the conditions in my backyard.
I’d been tinkering with ESPHome for a while, having documented a similar sensor integration in my how-to on monitoring home temperature with ESP32 and Xiaomi Mijia. That project taught me how a handful of YAML lines could turn a microcontroller into a dependable data source for Home Assistant. This time, I wanted motion.
Here’s the full build—servos, camera, and all the environmental data—plus the mistakes I made so you don’t have to.
Materials & Tools
You’ll need a mix of hardware, most of which you can grab from the usual hobbyist stock. The star of the show is the ESP32-S3-CAM module, but I’ve also listed a standard ESP32 DevKitC that I used for initial prototyping. The camera board integrates everything, but having a separate dev board around for testing isolates problems faster.
- ESP32-S3-CAM Module – This board has an onboard OV2640 camera, PSRAM, and enough I/O to drive servos and sensors simultaneously. The default firmware from Espressif works, but I wiped it for ESPHome.
- Standard ESP32 Development Board (optional, for debugging) — ESP32-DevKit board: Buy it here
- 2 × SG90 Servo Motor – One for pan, one for tilt. Buy a pack of five; you’ll break a gear sooner or later.
- Pan-Tilt Bracket Kit – The acrylic or laser-cut kind that holds the camera and two micro servos. I spent 20 minutes trying to 3D‑print my own bracket before realising a $3 kit does the job better.
- DHT11 Temperature & Humidity Sensor (or DHT22 if you want more accuracy) – The secret ingredient that turns a security camera into an ESP32 weather station.
- SSD1306 128×64 OLED Display (optional) – I stuck one on the bracket to show live temperature and humidity without opening an app. It’s the kind of impractical-but-fun addition that makes you smile every time you glance at it.
- 10kΩ resistor – Pull-up for the DHT data line (already on many DHT breakout boards, but check yours).
- Breadboard and jumper wires
- USB‑C cable (for the ESP32-S3-CAM) – A good data cable, not a charge‑only one. I learned that the hard way.
- External 5V 2A power supply – The servos draw more current than the USB port on your laptop can provide. If you skip this, expect brownouts, jitter, and a camera that refuses to initialise.
I won’t pretend this is the cheapest project, but most of the components are reusable for other ESPHome-based sensors—and you’ll end up with a platform that blurs the line between a pan-tilt security camera and a smart weather station ESP32.
Wiring Guide
The ESP32-S3-CAM’s pin header is tight. Eight pins are dedicated to the camera, and a few others are tied to PSRAM or flash. That leaves us just enough free GPIOs for two servos, a DHT11, and an I2C OLED. Here’s the pin mapping that worked reliably after several rounds of trial and error.
Servo connections
Connect the SG90 signal wires to GPIOs that don’t interfere with the camera. I used GPIO 12 for pan and GPIO 14 for tilt. Both servos share a common 5V rail from the external supply, and their grounds tie back to the ESP32-S3-CAM GND pin. Never power servos from the 3.3V rail—the current spikes will knock the camera offline.
DHT11
Data line goes to GPIO 13 with a 10kΩ pull‑up to 3.3V. If you use a module with an onboard resistor, skip the extra component.
SSD1306 OLED (optional)
SDA → GPIO 21, SCL → GPIO 22. The display runs at 3.3V, and the camera board has built-in pull‑ups on those I2C pins, so it’s plug‑and‑play in most cases.
Here’s a clean wiring table:
| ESP32-S3-CAM Pin | Component | Connection |
|---|---|---|
| 3.3V | DHT11 VCC, OLED VCC | Power |
| GND | Servos GND, DHT11 GND, OLED GND, power supply GND | Common ground |
| GPIO 12 | Servo 1 (pan) | Signal (orange) |
| GPIO 14 | Servo 2 (tilt) | Signal (orange) |
| GPIO 13 | DHT11 | DATA |
| GPIO 21 | OLED | SDA |
| GPIO 22 | OLED | SCL |
The external 5V supply powers both servos directly—nothing else should touch those 5V lines. I added a 1000µF electrolytic capacitor across the servo power rails to smooth out the noise, which eliminated a rhythmic buzzing I could hear in the video feed.
Software Setup
I used ESPHome because it handles the camera, servos, and sensors with minimal configuration, and because I’m lazy. If you’ve never set up ESPHome, head over to the official installation guide and either use the Home Assistant add‑on or the Docker container. I do everything from the command line, but the YAML is the same regardless.
Create a new device configuration and a secrets.yaml for credentials:
# secrets.yaml
wifi_ssid: "YourNetwork"
wifi_password: "YourPassword"
ap_password: "fallbackhotspot"
ota_password: "ota_secret"
Now create esp32-weather-camera.yaml:
substitutions:
device_name: "backyard-cam-weather"
friendly_name: "Backyard Camera & Weather Station"
esphome:
name: ${device_name}
platform: ESP32
board: esp32-s3-devkitc-1
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: !secret api_encryption_key
ota:
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "${friendly_name} Fallback"
password: !secret ap_password
captive_portal:
# Camera configuration
esp32_camera:
name: "${friendly_name}"
external_clock:
pin: GPIO10
frequency: 20MHz
i2c_pins:
sda: GPIO17
scl: GPIO18
data_pins: [GPIO4, GPIO5, GPIO6, GPIO7, GPIO8, GPIO9, GPIO15, GPIO16]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO3
power_down_pin: GPIO0
resolution: 800x600
jpeg_quality: 12
max_framerate: 10 fps
idle_framerate: 1 fps
vertical_flip: true
horizontal_mirror: false
That gives you a working camera stream. Now we add the weather station bits.
Configuration Walkthrough – The ESPHome YAML That Turns a Camera into an ESP32 Weather Station
Here’s where the project becomes a smart weather station ESP32. I’ll layer in the DHT11 sensor, the servos, the OLED, and a few services that make the data useful.
Add this right after the camera block:
# DHT11 temperature & humidity sensor — the weather station core
sensor:
- platform: dht
pin: GPIO13
model: DHT11
temperature:
name: "${friendly_name} Temperature"
id: dht_temp
humidity:
name: "${friendly_name} Humidity"
id: dht_humid
update_interval: 60s
# Pan & tilt servos
servo:
- id: pan_servo
output: pan_pwm
min_level: 3%
max_level: 12%
- id: tilt_servo
output: tilt_pwm
min_level: 3%
max_level: 12%
output:
- platform: ledc
id: pan_pwm
pin: GPIO12
frequency: 50 Hz
- platform: ledc
id: tilt_pwm
pin: GPIO14
frequency: 50 Hz
# SSD1306 OLED display for local weather readout
i2c:
sda: GPIO21
scl: GPIO22
scan: true
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
lambda: |-
it.printf(0, 0, id(font_small), "Temp: %.1f°C", id(dht_temp).state);
it.printf(0, 20, id(font_small), "Hum: %.1f%%", id(dht_humid).state);
it.strftime(0, 50, id(font_small), "%H:%M %d/%m", id(esphome_time).now());
font:
- file: "gfonts://Roboto"
id: font_small
size: 14
time:
- platform: homeassistant
id: esphome_time
on_time:
- seconds: 0
minutes: /1
then:
- component.update: dht_temp
- component.update: dht_humid
# Services to control pan & tilt from Home Assistant or automations
api:
services:
- service: set_pan
variables:
level: float
then:
- servo.write:
id: pan_servo
level: !lambda 'return level / 100.0;'
- service: set_tilt
variables:
level: float
then:
- servo.write:
id: tilt_servo
level: !lambda 'return level / 100.0;'
I deliberately didn’t hard‑code any absolute servo angles because every bracket assembly centres differently. The set_pan and set_tilt services accept a percentage (0–100) that maps to the servo’s full range. I later automated motion tracking in Home Assistant, but even without that, I could reposition the camera manually from the dashboard.
The OLED lambda is minimal—it prints temperature, humidity, and a live clock synced from Home Assistant. If you’ve ever built a standalone ESP32 weather station with a tiny display, you know how satisfying that blinking colon is. No network dependency after boot.
Upload & Test
- Connect the ESP32-S3-CAM via USB‑C. Make sure your computer actually sees it (check
ls /dev/tty*on Linux or Device Manager on Windows). - Compile and upload:
esphome run esp32-weather-camera.yaml - Watch the logs. ESPHome will flash the firmware and then connect over Wi‑Fi. If the camera initialises correctly, you’ll see a line like
[esp32_camera:075]: Camera init successful. - Open Home Assistant—the device should appear in the integrations list. Add it, and you’ll get camera entity, temperature, humidity, and two service buttons.
- Test the weather station side: blow on the DHT11. The OLED (if connected) should update within a minute, and the Home Assistant history graph will start climbing.
At this point, I had a fully working pan-tilt security camera that was also an ESP32 weather station, pushing live temp/humidity to my dashboards. It wasn’t flawless—the first time I pivoted the camera 180°, the ribbon cable snagged and pulled the DHT11 off the breadboard. Cable management matters, lesson learned.
Troubleshooting
Camera init fails / “No PSRAM” error
Double-check your board selection. The ESP32-S3-CAM often works with board: esp32-s3-devkitc-1. If you see memory errors, try board: ai-thinker-esp32-cam as a fallback, but I got the best results with the devkitc definition. Also, ensure the camera ribbon is fully inserted—it takes more force than you expect.
DHT11 shows NaN or random spikes
- If you get
nan, the sensor isn’t responding. Add a 10kΩ pull‑up resistor between DATA and 3.3V. - Spikes often happen when the servo supply is noisy and the wiring is tangled. Route the DHT data wire away from the servo signal lines. I wrapped mine in aluminium tape and grounded it—yes, I’m that desperate.
- The DHT11 isn’t great; consider a DHT22 or BME280 for a proper smart weather station ESP32.
Servo jitter or brownouts
- The external 5V supply is non-negotiable. I initially tried powering one SG90 from the USB port and the camera crashed every time the servo moved.
- Add a 1000µF capacitor across the servo power rails.
- Reduce the PWM frequency to 50Hz (already set above). If you still get humming, tweak
min_levelandmax_levelgradually until the servo stops oscillating.
OLED stays blank
- Confirm the I2C address. Most SSD1306 modules use
0x3C, but some ship with0x3D. In ESPHome, seti2c: scan: trueand check the logs—it will tell you the address it found. - Verify SDA and SCL aren’t swapped. GPIO 21 = SDA, GPIO 22 = SCL. It’s a mistake I rewired three times before accepting.
Upload stuck at “Connecting…”
- Hold the BOOT button, press RESET briefly, then release BOOT. The ESP32-S3-CAM has finicky manual download mode.
- Use a short, high‑quality USB cable. I wasted an hour with a charge‑only cable that couldn’t even enumerate the serial port.
Enhancements & Next Steps
I didn’t stop at a basic weather station camera. Here’s where you can take this project next:
- True motion tracking: Combine the camera frames with software like ESP32-CAM-Motion or a Home Assistant
image_processingintegration. When movement is detected, call theset_pan/set_tiltservices to follow the target. It’s janky but fun. - Deep sleep for battery operation: If you run the ESP32 weather station on solar, put the camera to sleep and wake it every few minutes to report weather. The camera can enter deep sleep with
deep_sleep: run_duration: 30s sleep_duration: 10min. I wrote about optimising ESPHome for battery life in my Grafana alerting custom message guide, where I had to squeeze every joule out of an industrial sensor. - Upgrade sensors: Swap the DHT11 for a BME280 (temperature, humidity, pressure) or an SHT30. You can even add a rain sensor module to make a full‑featured smart weather station ESP32.
- Grafana dashboards: Stream the weather data over MQTT to InfluxDB and build beautiful dashboards. I covered the Grafana side in detail in my Grafana alerting article, so you can set up custom messages when the temperature drops below freezing.
- Integrate with Zigbee2MQTT: If you already have a Zigbee network (I do, with Aqara sensors), you can let the ESP32 weather station act as a local display for Zigbee data, bridging the two worlds.
FAQ
Can I use a DHT22 instead of the DHT11?
Absolutely. Replace model: DHT11 with model: DHT22 in the YAML. The DHT22 is more accurate and has a wider operating range, which makes your ESP32 weather station more reliable outdoors.
Why is my OLED display blank?
The most common culprits are wrong I2C address (0x3D instead of 0x3C) or swapped SDA/SCL wires. Enable scan: true in the I2C block and check the ESPHome logs. If the address doesn’t show up, the display isn’t getting power or the wiring is wrong.
How do I pan and tilt from Home Assistant?
After the ESPHome device is added, the services esphome.backyard_cam_weather_set_pan and esphome.backyard_cam_weather_set_tilt appear. You can call them with a level value between 0 and 100 in Developer Tools → Services. I then built a custom dashboard card with two sliders.
Why does the camera stream stutter when the servos move?
Servo motor spikes can ripple back into the power supply. Use separate power for the servos and the camera (the camera gets 3.3V from the board, servos get 5V from the external brick) with a common ground. Adding a large capacitor across the servo power input cleaned up my stream noticeably.
Can I record video locally?
ESPHome only provides an MJPEG stream and snapshot capability. For continuous recording, set up a separate service like MotionEye or Frigate to pull the stream from Home Assistant’s camera entity and save clips.
Is this project suitable for outdoor use?
You’ll need a weatherproof enclosure. I mounted mine under an eave with a 3D‑printed housing, keeping the DHT11 shielded from direct rain while allowing airflow. The camera lens fogged up once, but I treated it with anti-fog spray originally meant for car mirrors—and it actually worked.
Building this pan-tilt camera that doubles as a smart weather station ESP32 taught me more about ESPHome’s component interactions than any textbook could. The combination of live video, environmental data, and physical motion opens up a world of automation possibilities. Give it a try, and don’t worry if your first servo jerks or your DHT11 reports 110% humidity—troubleshooting is half the fun.