ESP32-S3 Camera Pan-Tilt Security Tracker: Build a Smart ESP32 Weather Station

August 11, 2026 · esp32, esphome, weather station, pan tilt camera, esp32-s3, sg90 servo, dht11, smart home, home automation, iot

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.

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 PinComponentConnection
3.3VDHT11 VCC, OLED VCCPower
GNDServos GND, DHT11 GND, OLED GND, power supply GNDCommon ground
GPIO 12Servo 1 (pan)Signal (orange)
GPIO 14Servo 2 (tilt)Signal (orange)
GPIO 13DHT11DATA
GPIO 21OLEDSDA
GPIO 22OLEDSCL

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

  1. 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).
  2. Compile and upload:
    esphome run esp32-weather-camera.yaml
    
  3. 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.
  4. 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.
  5. 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

Servo jitter or brownouts

OLED stays blank

Upload stuck at “Connecting…”

Enhancements & Next Steps

I didn’t stop at a basic weather station camera. Here’s where you can take this project next:

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.