Connect the ESP32-CAM to Home Assistant with ESPHome or an NVR

A single ESP32-CAM viewed in a browser tab is a gadget. Several of them inside Home Assistant, with snapshots on the dashboard, a flash switch, motion-based automations and recordings handled by network video recorder (NVR) software, start to look like a real home camera system, at a small fraction of the cost of commercial cameras.

This tutorial shows two routes. The first flashes the board with ESPHome, which talks to Home Assistant through its native API and needs no Arduino code. The second keeps your own Arduino streaming firmware and connects its MJPEG URL to Home Assistant or to NVR software such as Frigate, motionEye or Blue Iris.

Which Route Should You Choose?

RouteHow it connectsStrengthsLimits
ESPHome firmwareNative API, encrypted, auto-discoveredYAML only, OTA built in, easy to add sensors and the flash LEDLess control over the streaming code
Arduino firmware + MJPEG integrationHome Assistant polls the /stream and /jpg URLsReuse any custom sketchNo encryption on the local HTTP stream
Arduino or ESPHome + NVRNVR pulls MJPEG and records itContinuous recording, motion and object detection on a PCNeeds a server with storage and CPU

Both routes depend on a stable board. Before connecting anything to Home Assistant, check that the camera streams reliably in a browser for an hour on its final power supply and in its final location.

Route 1: ESPHome Configuration

Install the ESPHome add-on (or the ESPHome command-line tool), create a new device and replace its YAML with the configuration below. The pin numbers are the same AI-Thinker map used by Arduino sketches: XCLK on GPIO 0, SCCB on GPIO 26 and 27, data Y2 to Y9 on GPIO 5, 18, 19, 21, 36, 39, 34 and 35, VSYNC 25, HREF 23, PCLK 22 and power-down on 32. Store passwords and keys in the secrets.yaml file.

ESPHome YAML for an AI-Thinker ESP32-CAM with flash LED and web stream
esphome:
  name: frontdoor-cam

esp32:
  board: esp32cam
  framework:
    type: arduino

logger:

api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome
    password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

# AI-Thinker ESP32-CAM camera pins
esp32_camera:
  name: Front Door Camera
  external_clock:
    pin: GPIO0
    frequency: 20MHz
  i2c_pins:
    sda: GPIO26
    scl: GPIO27
  data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
  vsync_pin: GPIO25
  href_pin: GPIO23
  pixel_clock_pin: GPIO22
  power_down_pin: GPIO32
  resolution: 800x600
  jpeg_quality: 12
  max_framerate: 10 fps     # limit stream rate to Home Assistant
  idle_framerate: 0.1 fps   # snapshot rate when nobody is watching

# Optional: plain HTTP stream and snapshot for NVR software
esp32_camera_web_server:
  - port: 8080
    mode: stream
  - port: 8081
    mode: snapshot

# Flash LED on GPIO 4 as a dimmable light
output:
  - platform: ledc
    pin: GPIO4
    channel: 2              # keep clear of the camera clock channel
    id: flash_output

light:
  - platform: monochromatic
    name: Front Door Flash
    output: flash_output

Connect the board with the USB-TTL adapter for the first install; after that ESPHome updates it over Wi-Fi. Home Assistant discovers the device, and after you enter the API encryption key the camera entity and flash light appear. ESPHome options change between releases, so if validation complains about a key, check the current esp32_camera documentation for your ESPHome version.

Route 2: MJPEG URLs in Home Assistant and NVRs

If you run your own Arduino firmware, such as the CameraWebServer example or a custom MJPEG server, the camera exposes two useful URLs: a stream such as http://192.168.1.60:81/stream and a still image such as http://192.168.1.60/capture or /jpg. Give the camera a fixed IP address with a DHCP reservation in your router so these URLs never change.

  • Home Assistant: add the MJPEG IP Camera integration from Settings > Devices and Services, enter the stream URL as the MJPEG URL and the still URL as the still image URL.
  • motionEye: add a network camera of type MJPEG with the stream URL; it then handles motion detection and recording.
  • Frigate: Frigate is built around ffmpeg inputs. It can read an HTTP MJPEG input using ffmpeg input arguments for MJPEG sources; check the Frigate documentation for the MJPEG preset in your version, and keep detection at a low frame rate such as 5 fps.
  • Blue Iris and similar Windows NVRs: add a generic MJPEG/HTTP camera with the stream path.

Example Automation: Snapshot on Doorbell Press

Once the camera entity exists, Home Assistant's camera.snapshot action can save an image whenever something happens. The YAML below saves a timestamped JPEG when a doorbell button entity turns on. Replace the entity IDs with your own and make sure the target folder is listed under allowlist_external_dirs in the Home Assistant configuration.

Home Assistant automation that saves a camera snapshot on a doorbell press
alias: Doorbell snapshot
triggers:
  - trigger: state
    entity_id: binary_sensor.doorbell_button
    to: "on"
actions:
  - action: light.turn_on
    target:
      entity_id: light.front_door_flash
    data:
      brightness_pct: 60
  - delay: "00:00:01"
  - action: camera.snapshot
    target:
      entity_id: camera.front_door_camera
    data:
      filename: "/config/www/doorbell/{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
  - action: light.turn_off
    target:
      entity_id: light.front_door_flash

Troubleshooting

  • Camera entity unavailable after a while: weak Wi-Fi. Check the signal, fit an external antenna or add a mesh node nearby.
  • ESPHome log shows camera init errors: a pin is wrong or the ribbon is loose; compare every pin with the AI-Thinker map.
  • Dashboard image updates slowly: that is the idle frame rate at work; the full rate is used only while the stream is open.
  • NVR records nothing: the stream is already in use by another client, or the URL lacks the port number.

Summary

ESPHome is the most integrated way to put an ESP32-CAM into Home Assistant: a short YAML file with the AI-Thinker pin map gives an encrypted camera entity, OTA updates and a dimmable flash. Custom Arduino firmware works too, through the MJPEG IP Camera integration or an NVR that records the stream. Plan for the board's one-stream limit, reserve fixed IP addresses, and use automations to take snapshots when events happen.