Several Ubiquiti Unifi cameras showing AI detection of a car, dog, and license plate
Home » Blog » What Your UniFi Cameras Can Actually Do in Home Assistant

What Your UniFi Cameras Can Actually Do in Home Assistant


📅 Published: April 2026 | ✍️ By Brad Andrews | ⏱️ 9 min read


Most people set up a security camera and stop at the recording. They get motion clips in an app, maybe a notification here and there, and call it done.

Here’s what they’re missing: when you integrate UniFi Protect with Home Assistant, those cameras stop being passive recorders and start being active inputs for your entire home. Package detected. Someone at the door. Person in the backyard. Dog barking. Each one can trigger something useful, something automated, something that would have taken a human decision before.

I have a G4 Doorbell at my front door, a G4 Pro watching the pool, an AI360 covering the side yard, and another G4 Pro on the deck. Together they power some of the automations I rely on most. Let me walk you through exactly how I’ve set them up.


Why UniFi Protect Makes Sense for a Local Setup

Before we get into the automations, the reason UniFi made sense for me comes down to one word: local.

The UniFi Protect integration in Home Assistant is fully local. No cloud relay, no subscription, no latency waiting for a round-trip to a server somewhere. When my G4 Doorbell detects a package, that state change hits Home Assistant in under a second. The announcement plays before I’ve even looked at my phone.

That matters for automations. Cloud-dependent cameras introduce a lag that makes presence and detection triggers feel sluggish. Local detection feels like the camera is actually thinking.

The other reason is the quality of Protect’s AI detection. Package detection, person detection, animal detection, vehicle detection, and barking detection are all surfaced as binary sensors directly in Home Assistant. Each camera gets its own sensor per detection type. That granularity is what makes every automation below possible.


Automation 1: Doorbell Ring — Rich Notification with Live Snapshot

This was the first automation I built after integrating the G4 Doorbell, and it immediately showed why UniFi is the right choice for this kind of setup.

What It Does

When the G4 Doorbell is pressed, both Kelly’s phone and mine get a push notification with a live snapshot from the front door camera attached. Tapping the notification opens directly to the front door camera view inside the Home Assistant companion app.

At the same time, two TTS announcements fire: one on the kitchen tablet, which covers the main floor, and one on the media room Sonos, which covers the basement. The speaker choice is deliberate — whoever is on either floor hears it without needing every speaker in the house to go off at once.

The key detail is how the snapshot is attached. Instead of saving an image to disk and pointing to a file path, this automation passes the camera’s entity_id directly in the notification payload. The iOS companion app fetches the image natively from your local HA instance when the notification arrives. No proxy URL, no file management, no intermediate step — and the image is as fresh as it can possibly be.

A Note on the YAML in This Article

Every code block below uses descriptive placeholders instead of my actual entity names. Anything in ALL_CAPS is something you need to replace with your own entity ID before pasting into Home Assistant.

For example: person.YOUR_NAME becomes my actual person entity in Home Assistant. media_player.YOUR_MAIN_FLOOR_SPEAKER becomes the entity ID for my kitchen tablet.

The easiest approach is to copy the YAML into a text editor, use Find and Replace to swap each placeholder for your real entity ID, then paste the result into the Home Assistant automation editor. Every placeholder appears consistently across all four automations, so one round of replacements covers the full set.

The Real YAML

alias: Doorbell - Notify YOUR_NAME & SPOUSE_NAME with Camera Snapshots
description: >
  When the front door doorbell rings: send a rich camera snapshot notification
  using entity_id attachment (no proxy URL) so iOS companion app fetches the
  image natively. Tapping opens directly to the front door camera.
mode: single
trigger:
  - platform: state
    entity_id: binary_sensor.front_door_doorbell
    to: "on"
action:
  - action: notify.mobile_app_YOUR_PHONE
    data:
      title: "🔔 Someone at the Front Door"
      message: "The doorbell just rang!"
      data:
        entity_id: camera.YOUR_DOORBELL_CAMERA
        url: "homeassistant://navigate/YOUR_CAMERA_DASHBOARD_PATH"
  - action: notify.mobile_app_SPOUSE_PHONE
    data:
      title: "🔔 Someone at the Front Door"
      message: "The doorbell just rang!"
      data:
        entity_id: camera.YOUR_DOORBELL_CAMERA
        url: "homeassistant://navigate/YOUR_CAMERA_DASHBOARD_PATH"
  - action: tts.speak
    data:
      cache: false
      media_player_entity_id: media_player.YOUR_MAIN_FLOOR_SPEAKER
      message: "Someone is at the front door."
    target:
      entity_id: tts.home_assistant_cloud
  - action: tts.speak
    data:
      cache: false
      media_player_entity_id: media_player.YOUR_BASEMENT_SPEAKER
      message: "Someone is at the front door."
    target:
      entity_id: tts.home_assistant_cloud

Why entity_id Instead of a File Snapshot

Most doorbell notification guides tell you to use camera.snapshot to save a JPEG to disk, then attach the file path in the notification data. That works, but it adds moving parts: the snapshot action has to complete before the notify actions run, the file has to exist on disk, and there’s no guarantee the image is current by the time the notification arrives on your phone.

Passing entity_id in the notification data skips all of that. The companion app requests the image directly from your local HA instance when the notification arrives. It’s simpler and fresher.

The url field in the notification data is what makes tapping the notification open the camera view directly. Swap the dashboard path for your own camera view, or remove the url field entirely if you don’t have a dedicated camera dashboard set up yet.


Automation 2: Package Delivery Alert with Visual Confirmation

This one came from a real problem. Packages were being left on the porch and I had no reliable way of knowing when they arrived, especially when I was working in my home office and Kelly was at school.

What It Does

When the G4 Doorbell detects a package on the front porch, three things happen:

  1. The current colour and brightness of both Voice PE LED rings are saved to helpers
  2. Both LED rings turn yellow as a persistent visual reminder
  3. The kitchen tablet announces that a package has arrived

When the package sensor clears, the automation restores both LED rings to exactly what they were before the alert fired.

A guard boolean (input_boolean.package_alert_package_currently_detected) prevents the restore sequence from running if the save never happened. Without it, a system restart mid-alert could cause the rings to try to restore from empty values.

The Real YAML

alias: Package Delivery - Front Door Alert
description: >
  When the Front Door camera detects a package: saves current RGB colour and
  brightness of both VA LED rings, turns both rings yellow, announces on Kitchen
  Tablet TTS, and sets a guard boolean. When package is no longer detected (only
  from 'on', ignoring unavailable transitions): restores both rings to saved
  colours/brightness and clears the guard boolean. Guard boolean prevents restore
  running if save never ran.
mode: restart
trigger:
  - platform: state
    entity_id: binary_sensor.front_door_package_detected
    to: "on"
    id: package_detected
  - platform: state
    entity_id: binary_sensor.front_door_package_detected
    from: "on"
    to: "off"
    for:
      seconds: 30
    id: package_gone
action:
  - choose:
      - conditions:
          - condition: trigger
            id: package_detected
        sequence:
          # Save current Office VA LED ring state
          - action: input_text.set_value
            data:
              value: >
                {{ state_attr('light.VOICE_PE_LOCATION1_LED_RING', 'rgb_color') | join(',')
                   if states('light.VOICE_PE_LOCATION1_LED_RING') == 'on' else '0,0,0' }}
            target:
              entity_id: input_text.package_alert_office_va_previous_rgb
          - action: input_number.set_value
            data:
              value: >
                {{ state_attr('light.VOICE_PE_LOCATION1_LED_RING', 'brightness') | int
                   if states('light.VOICE_PE_LOCATION1_LED_RING') == 'on' else 0 }}
            target:
              entity_id: input_number.package_alert_office_va_previous_brightness
          # Save current Master Bed VA LED ring state
          - action: input_text.set_value
            data:
              value: >
                {{ state_attr('light.VOICE_PE_LOCATION2_LED_RING', 'rgb_color') | join(',')
                   if states('light.VOICE_PE_LOCATION2_LED_RING') == 'on' else '0,0,0' }}
            target:
              entity_id: input_text.package_alert_master_bed_va_previous_rgb
          - action: input_number.set_value
            data:
              value: >
                {{ state_attr('light.VOICE_PE_LOCATION2_LED_RING', 'brightness') | int
                   if states('light.VOICE_PE_LOCATION2_LED_RING') == 'on' else 0 }}
            target:
              entity_id: input_number.package_alert_master_bed_va_previous_brightness
          # Set guard boolean and turn rings yellow
          - action: input_boolean.turn_on
            target:
              entity_id: input_boolean.package_alert_package_currently_detected
          - action: light.turn_on
            data:
              brightness: 255
              rgb_color: [255, 200, 0]
            target:
              entity_id:
                - light.VOICE_PE_LOCATION1_LED_RING
                - light.VOICE_PE_LOCATION2_LED_RING
          # Announce on kitchen tablet
          - action: tts.speak
            data:
              message: "A package has been delivered at the front door."
              media_player_entity_id: media_player.YOUR_MAIN_FLOOR_SPEAKER
            target:
              entity_id: tts.home_assistant_cloud

      - conditions:
          - condition: trigger
            id: package_gone
          - condition: state
            entity_id: input_boolean.package_alert_package_currently_detected
            state: "on"
        sequence:
          # Restore Office VA ring or turn off if it was off before
          - choose:
              - conditions:
                  - condition: numeric_state
                    entity_id: input_number.package_alert_office_va_previous_brightness
                    above: 0
                sequence:
                  - action: light.turn_on
                    data:
                      brightness: "{{ states('input_number.package_alert_office_va_previous_brightness') | int }}"
                      rgb_color:
                        - "{{ states('input_text.package_alert_office_va_previous_rgb').split(',')[0] | int }}"
                        - "{{ states('input_text.package_alert_office_va_previous_rgb').split(',')[1] | int }}"
                        - "{{ states('input_text.package_alert_office_va_previous_rgb').split(',')[2] | int }}"
                    target:
                      entity_id: light.VOICE_PE_LOCATION1_LED_RING
            default:
              - action: light.turn_off
                target:
                  entity_id: light.VOICE_PE_LOCATION1_LED_RING
          # Restore Master Bed VA ring or turn off if it was off before
          - choose:
              - conditions:
                  - condition: numeric_state
                    entity_id: input_number.package_alert_master_bed_va_previous_brightness
                    above: 0
                sequence:
                  - action: light.turn_on
                    data:
                      brightness: "{{ states('input_number.package_alert_master_bed_va_previous_brightness') | int }}"
                      rgb_color:
                        - "{{ states('input_text.package_alert_master_bed_va_previous_rgb').split(',')[0] | int }}"
                        - "{{ states('input_text.package_alert_master_bed_va_previous_rgb').split(',')[1] | int }}"
                        - "{{ states('input_text.package_alert_master_bed_va_previous_rgb').split(',')[2] | int }}"
                    target:
                      entity_id: light.VOICE_PE_LOCATION2_LED_RING
            default:
              - action: light.turn_off
                target:
                  entity_id: light.VOICE_PE_LOCATION2_LED_RING
          - action: input_boolean.turn_off
            target:
              entity_id: input_boolean.package_alert_package_currently_detected

Helpers Required

You’ll need these helpers created before the automation will work:

  • input_boolean.package_alert_package_currently_detected
  • input_text.package_alert_office_va_previous_rgb
  • input_text.package_alert_master_bed_va_previous_rgb
  • input_number.package_alert_office_va_previous_brightness (min 0, max 255)
  • input_number.package_alert_master_bed_va_previous_brightness (min 0, max 255)

The LED ring entity IDs will be specific to your Voice PE or Satellite hardware. Swap them for your own.


Automation 3: Backyard Security and Presence Awareness

The pool, deck, and side yard cameras aren’t just recording. They’re grouped together to give Home Assistant a single answer to a simple question: is anyone in the backyard right now?

The Group Sensor

I use a binary_sensor group called binary_sensor.person_in_backyard. It combines person detection from three cameras:

  • binary_sensor.pool_cam_person_detected
  • binary_sensor.side_yard_360_person_detected
  • binary_sensor.deck_person_detected

The group uses all: false, meaning it turns on when any one of the three fires and turns off only when all three have cleared. That single sensor powers multiple automations without each one needing to independently watch three separate triggers.

You create this under Settings > Helpers > Add Helper > Group. Select “Binary Sensor” as the type and add the three person detection sensors as members. Leave “All entities must be active” toggled off.

Intrusion Alert While Away (with Camera Snapshot)

When both Kelly and I are away and any backyard camera detects a person, both phones get a push notification with a snapshot from the camera that actually triggered. The automation uses a template variable to select the right camera based on which trigger fired, so the attached image always comes from the relevant angle.

alias: Alarm for Person in Backyard while away
description: >
  Alerts both phones when a person is detected in the backyard while nobody is home.
  Includes a snapshot from the camera that triggered the detection. Mode single + 5 min
  delay prevents duplicate notifications.
mode: single
variables:
  camera_entity: >
    {% if trigger.id == 'side_yard_360' %}
      camera.ai_360_high_resolution_channel
    {% elif trigger.id == 'pool_cam' %}
      camera.pool_cam_high_resolution_channel
    {% else %}
      camera.deck_high_resolution_channel
    {% endif %}
trigger:
  - platform: state
    entity_id: binary_sensor.side_yard_360_person_detected
    from: "off"
    to: "on"
    id: side_yard_360
  - platform: state
    entity_id: binary_sensor.pool_cam_person_detected
    from: "off"
    to: "on"
    id: pool_cam
  - platform: state
    entity_id: binary_sensor.deck_person_detected
    from: "off"
    to: "on"
    id: deck
condition:
  - condition: and
    conditions:
      - condition: not
        conditions:
          - condition: zone
            entity_id: person.YOUR_NAME
            zone: zone.home
      - condition: not
        conditions:
          - condition: state
            entity_id: person.SPOUSE_NAME
            state: home
action:
  - action: camera.snapshot
    data:
      filename: /tmp/backyard_alarm.jpg
    target:
      entity_id: "{{ camera_entity }}"
  - action: notify.mobile_app_YOUR_PHONE
    data:
      title: "Alarm: Person in Backyard"
      message: "Person spotted in Backyard while you are not home!"
      data:
        image: /tmp/backyard_alarm.jpg
  - action: notify.mobile_app_SPOUSE_PHONE
    data:
      title: "Alarm: Person in Backyard"
      message: "Person spotted in Backyard while you are not home!"
      data:
        image: /tmp/backyard_alarm.jpg
  - delay:
      minutes: 5

The 5-minute delay at the end is intentional. With mode: single, the automation won’t retrigger while it’s already running. This acts as a built-in cooldown without needing a separate timer helper.

Lights and Music Auto-Off at End of Night

When nobody has been detected in the backyard for 30 continuous minutes and the back door is locked, the backyard lights turn off and the deck and pool patio Sonos speakers pause automatically.

The locked door condition matters. If someone steps inside briefly and the presence sensor clears, the door being unlocked tells the automation that someone may still be out there. The automation only fires when the door is locked, confirming the last person is in for the night.

alias: Backyard Lights and Music Auto Off after Nobody present for 30m
description: >
  Turns off all backyard lights and pauses outdoor speakers when no person is
  detected in the backyard for 30 continuous minutes. Only runs if the lights
  are actually on or any outdoor speaker is actively playing, AND the back door
  is locked (meaning everyone is back inside).
mode: single
trigger:
  - platform: state
    entity_id: binary_sensor.person_in_backyard
    to: "off"
    for:
      minutes: 30
condition:
  - condition: state
    entity_id: lock.YOUR_BACK_DOOR
    state: locked
  - condition: or
    conditions:
      - condition: state
        entity_id: light.all_backyard_lights
        state: "on"
      - condition: state
        entity_id: media_player.YOUR_DECK_SPEAKER
        state: playing
      - condition: state
        entity_id: media_player.YOUR_POOL_SPEAKER
        state: playing
action:
  - action: light.turn_off
    target:
      entity_id: light.all_backyard_lights
  - action: media_player.media_pause
    target:
      entity_id:
        - media_player.YOUR_DECK_SPEAKER
        - media_player.YOUR_POOL_SPEAKER

Automation 4: Clarke Barking Alert

Clarke is our dog. He goes outside through the kitchen back door when someone lets him out. If he starts barking, I want to know — but I also want the automation to be smart enough to confirm he was actually outside first.

The Verification Step

Before any notification fires, the automation runs a template condition that checks whether any backyard camera’s animal sensor triggered after the back door was last opened. If no animal detection happened since the door last opened, the alert skips entirely. This prevents false positives from a neighbour’s dog or ambient noise.

Presence-Aware Routing

Once Clarke is confirmed to be outside and barking, the automation branches based on who is home: both away sends to both phones, only one person home gets a kitchen TTS announcement plus their phone, and both home gets TTS plus both phones.

The notification uses a persistent tag so it auto-clears from both phones once the barking has been gone for 30 seconds.

alias: YOUR_PET_NAME Barking - Alert and Announce
description: >
  When the barking detection sensor triggers between 7am–10pm: first confirms
  YOUR_PET_NAME was actually let outside (animal sensor fired since the back door
  last opened). Then branches on presence: both away → notify both phones; only
  one home → main floor TTS + that person's phone; both home → main floor TTS +
  both phones. Sends a persistent notification that auto-clears once barking has
  been gone for 30 seconds.
mode: single
trigger:
  - platform: state
    entity_id: binary_sensor.side_yard_360_barking_detected
    to: "on"
condition:
  - condition: time
    after: "07:00:00"
    before: "22:00:00"
action:
  # Confirm YOUR_PET_NAME was outside when barking started
  - alias: YOUR_PET_NAME must have been outside since back door last opened
    condition: template
    value_template: >
      {% set door_last_opened = states.binary_sensor.YOUR_BACK_DOOR_CONTACT_SENSOR.last_changed %}
      {% set animal_sensors = [
        states.binary_sensor.deck_animal_detected,
        states.binary_sensor.pool_cam_animal_detected,
        states.binary_sensor.side_yard_360_animal_detected
      ] %}
      {{ animal_sensors | selectattr('last_changed', '>', door_last_opened) | list | count > 0 }}

  - alias: Branch on who is home
    choose:
      - alias: Both YOUR_NAME and SPOUSE_NAME are away
        conditions:
          - condition: not
            conditions:
              - condition: state
                entity_id: person.YOUR_NAME
                state: home
          - condition: not
            conditions:
              - condition: state
                entity_id: person.SPOUSE_NAME
                state: home
        sequence:
          - action: notify.mobile_app_YOUR_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking
          - action: notify.mobile_app_SPOUSE_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking

      - alias: Only YOUR_NAME is home
        conditions:
          - condition: state
            entity_id: person.YOUR_NAME
            state: home
          - condition: not
            conditions:
              - condition: state
                entity_id: person.SPOUSE_NAME
                state: home
        sequence:
          - action: tts.speak
            data:
              media_player_entity_id: media_player.YOUR_MAIN_FLOOR_SPEAKER
              message: YOUR_PET_NAME is barking outside.
            target:
              entity_id: tts.home_assistant_cloud
          - action: notify.mobile_app_YOUR_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking

      - alias: Only SPOUSE_NAME is home
        conditions:
          - condition: state
            entity_id: person.SPOUSE_NAME
            state: home
          - condition: not
            conditions:
              - condition: state
                entity_id: person.YOUR_NAME
                state: home
        sequence:
          - action: tts.speak
            data:
              media_player_entity_id: media_player.YOUR_MAIN_FLOOR_SPEAKER
              message: YOUR_PET_NAME is barking outside.
            target:
              entity_id: tts.home_assistant_cloud
          - action: notify.mobile_app_SPOUSE_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking

      - alias: Both YOUR_NAME and SPOUSE_NAME are home
        conditions:
          - condition: state
            entity_id: person.YOUR_NAME
            state: home
          - condition: state
            entity_id: person.SPOUSE_NAME
            state: home
        sequence:
          - action: tts.speak
            data:
              media_player_entity_id: media_player.YOUR_MAIN_FLOOR_SPEAKER
              message: YOUR_PET_NAME is barking outside.
            target:
              entity_id: tts.home_assistant_cloud
          - action: notify.mobile_app_YOUR_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking
          - action: notify.mobile_app_SPOUSE_PHONE
            data:
              title: YOUR_PET_NAME Alert
              message: "YOUR_PET_NAME is barking outside!"
              data:
                tag: YOUR_PET_NAME_barking

  # Wait up to 1 hour for barking to stop, then clear the notification
  - alias: Wait for barking to stop
    wait_for_trigger:
      - platform: state
        entity_id: binary_sensor.side_yard_360_barking_detected
        to: "off"
        for:
          seconds: 30
    timeout:
      hours: 1
    continue_on_timeout: true

  - alias: Clear notification from both phones
    parallel:
      - action: notify.mobile_app_YOUR_PHONE
        data:
          message: clear_notification
          data:
            tag: YOUR_PET_NAME_barking
      - action: notify.mobile_app_SPOUSE_PHONE
        data:
          message: clear_notification
          data:
            tag: YOUR_PET_NAME_barking

The continue_on_timeout: true on the wait step means if YOUR_PET_NAME is still going an hour later, the automation finishes anyway and clears the notification. It also means the automation can retrigger on the next barking event rather than hanging open indefinitely.

Note on the notification tag: The tag: YOUR_PET_NAME_barking value must be a single string with no spaces. When you replace YOUR_PET_NAME, use lowercase and underscores — for example tag: clarke_barking. The tag value must match exactly across the notify and clear steps, so find and replace handles this correctly as long as you’re consistent.


What’s Coming Next: Licence Plate and Driveway Tracking

I’m upgrading my garage and driveway cameras sometime mid-to-late summer — pending the final stage of any significant home tech purchase, which is spouse approval. Camera model is still TBD.

The goal is to add licence plate detection to my presence logic. Right now I rely on phone-based geofencing to know when our cars leave and come home. It works well, but there are edge cases: a phone left in a car, geofencing lag when arriving, or the car leaving without the phone. Pairing licence plate detection with phone presence gives me a double confirmation that my specific car is home — not just someone else’s vehicle pulling into the street.

Since the cameras only cover my own driveway, the only plates being read are ours. The detection stays local and the use case is genuinely practical: know when either car left, confirm I actually departed and didn’t just leave my phone behind, and trigger arrival automations only when my car physically crosses into the driveway.

The other thing I want to build alongside this is known-person recognition. When family or friends we see regularly pull up, I want a different kind of alert — not “unknown vehicle detected” but “my in-laws are here” announced on the relevant speakers, or a notification that uses their name. The camera system and Home Assistant together make that possible. It’s a meaningful step up from a generic motion alert.

I’ll document the full setup once the cameras are in and the automations have had time to prove themselves. I won’t write it until I’ve lived with it.


The Integration You Need

If you’re running UniFi Protect and haven’t connected it to Home Assistant yet, the integration is in the core integrations list. Go to Settings > Devices & Services > Add Integration and search for “UniFi Protect.” You’ll need your UniFi console’s local IP address and a local account (not your Ubiquiti cloud account). The integration will pull in all your cameras and their AI detection sensors automatically.

From there, the entities you’ll be working with follow a consistent naming pattern:

  • binary_sensor.[camera_name]_person_detected
  • binary_sensor.[camera_name]_package_detected
  • binary_sensor.[camera_name]_animal_detected
  • binary_sensor.[camera_name]_barking_detected
  • binary_sensor.[camera_name]_vehicle_detected

Each one is a standard binary sensor you can use as a trigger, condition, or state check in any automation. This is the kind of integration that changes how you think about what a security camera actually is. They’re not recorders. They’re sensors.


Smart Home Secrets is reader-supported. We may earn a commission if you buy through our links.