Back to posts
UniFi Events in Home Assistant

UniFi Events in Home Assistant

A custom AppDaemon app and Lovelace card that brings UniFi Protect detection thumbnails into Home Assistant — with instant updates and minimal overhead.

Justin Wyne / March 23, 2026

I run UniFi Protect cameras around my house. The UniFi app has a clean home screen that shows a live grid of the most recent AI detection thumbnails — persons, vehicles, animals, packages — with fuzzy timestamps. It's the first thing I check when I hear something outside.

My Home Assistant dashboard runs on a Google Nest Hub in the kitchen. I wanted that same at-a-glance detection feed there, without opening the UniFi app. This is what I built.

How It Works

There are two pieces:

  1. AppDaemon app — a Python script that connects directly to the UniFi Protect API, fetches recent detection thumbnails, and writes them to disk along with a JSON manifest.
  2. Custom Lovelace card — a vanilla JS web component that reads that manifest and renders the detection grid. It watches a Home Assistant sensor for state changes to trigger instant refreshes.

The card never talks directly to UniFi Protect. It only reads a static JSON file and serves images that AppDaemon already downloaded. This keeps the card simple and fast.

Event-Driven Updates

The naive approach would be to poll UniFi Protect on a timer. The problem: thumbnails take a few seconds to process after a detection fires. If you poll too frequently you burn resources; too infrequently and you miss the moment.

Instead, the AppDaemon app listens to binary sensors in Home Assistant — the ones that fire when Protect detects a person, vehicle, etc. When a sensor triggers:

  1. A placeholder entry is injected into the JSON feed immediately, so the card shows an icon within seconds.
  2. After a configurable delay (trigger_delay, default 10s), the app fetches from the Protect API, where the thumbnail should now be ready.
  3. If it still isn't ready, it continues fast-polling every few seconds for up to a minute.

The result: a new thumbnail usually appears on the card within 15–20 seconds of the actual detection, without hammering the API.

Performance on Low-End Hardware

The Google Nest Hub is not a powerful device. A few design decisions keep the card running smoothly on it:

  • No framework. The card is plain vanilla JS — no React, no Vue, no build step. It's a single file you drop in your www folder.
  • DOM patching, not re-rendering. The grid cells are created once at build time. On each update, _patch() updates only the src attribute or placeholder visibility. No nodes are destroyed or recreated.
  • Cache-busting on fetch. The JSON manifest is fetched with ?_t=<timestamp> to bypass the browser cache, so stale data never sits around.
  • Entity-triggered refresh. The card listens to a single HA sensor (sensor.unifi_detections_updated). When its state changes, the card fetches. No polling loop inside the card.

The card is fast enough that I run it on the Nest Hub full-time with no performance issues.

The Card

Tap the card to open a lightbox with a larger grid of recent detections. Tap anywhere or press Escape to close.

Each cell shows the detection thumbnail with a fuzzy age label ("now", "3 m", "2 h"). If a thumbnail isn't ready yet — because the detection just fired — it shows an icon for the detection type instead.

Card configuration is minimal:

 
1
type: custom:unifi-events-card
2
url: /local/unifi_events/recent.json
3
entity: sensor.unifi_detections_updated
4
count: 3
5
lightbox_count: 9
6
cols: 3
7
refresh_interval: 300
OptionDefaultDescription
urlrequiredPath to recent.json served by AppDaemon
entityHA entity to watch for instant refresh triggers
count3Thumbnails shown in the main grid
lightbox_count6Thumbnails shown in the lightbox
cols3Columns per row
refresh_interval300Fallback polling interval in seconds

The AppDaemon App

AppDaemon runs as an add-on inside Home Assistant. The app connects to the UniFi Protect API using the uiprotect Python library, fetches recent smart detection events, downloads each thumbnail, and writes a recent.json manifest to the HA www directory (so the card can read it over HTTP).

It also maintains the sensor.unifi_detections_updated entity in HA, updating its state timestamp whenever a fresh fetch completes. That state change is what triggers the card to reload.

 
1
recent_detections:
2
module: recent_detections
3
class: RecentDetections
4
5
host: !secret unifi_protect_host port: 443 username: !secret
6
unifi_protect_username password: !secret unifi_protect_password verify_ssl:
7
false
8
9
hours: 2 # how far back to search each run count: 6 # max thumbnails in the feed
10
interval: 300 # seconds between scheduled runs
11
12
trigger_delay: 10 # seconds after sensor fires before fetching
13
trigger_poll_interval: 5 # seconds between fast polls trigger_poll_count: 12 #
14
max fast polls (12×5s = 60s window)
15
16
# trigger_sensors:
17
18
# - binary_sensor.cam_person_detected
19
20
# - binary_sensor.cam_animal_detected
21
22
output_dir: /homeassistant/www/unifi_events web_root: /local/unifi_events
23

The trigger_sensors list should match the binary sensors that your UniFi Protect integration exposes — one per camera and detection type. When any of them flips to on, the fast-trigger flow kicks in.

All configuration options:

OptionDefaultDescription
hostrequiredUniFi Protect host IP
port443HTTPS port
username / passwordrequiredLocal admin credentials
verify_sslfalseSSL verification
hours2How far back to look for events
countnoneMax thumbnails to keep in feed
typesallFilter to person, animal, vehicle, package
interval300Seconds between scheduled fetches
trigger_delay120Seconds to wait after a sensor trigger
trigger_poll_interval5Seconds between fast polls after trigger
trigger_poll_count12Max fast polls before giving up
trigger_sensors[]HA binary sensors to watch
output_dirWhere to write thumbnails and JSON
web_rootURL prefix for thumbnail paths in JSON

Installation

Prerequisites

  • HACS installed in Home Assistant
  • AppDaemon add-on installed via Settings → Add-ons → Add-on Store
  • AppDaemon app discovery enabled in HACS: Settings → Devices & Services → HACS → Configure → enable AppDaemon apps discovery

Step 1 — Point AppDaemon at the HACS app directory

Edit the AppDaemon add-on configuration file:

 
1
2
/mnt/data/supervisor/addon_configs/a0d7b954_appdaemon/appdaemon.yaml
3

Set app_dir to:

 
1
app_dir: /homeassistant/appdaemon/apps

This only needs to be done once.

Step 2 — Add Python dependencies

In the AppDaemon add-on configuration, add:

 
1
python_packages:
2
- uiprotect
3
- aiofiles

Step 3 — Install via HACS

  1. HACS → three-dot menu → Custom repositories
  2. Paste https://github.com/wyne/ha-unifi-events, category AppDaemon, click Add
  3. Find UniFi Recent Detections in HACS and click Download

HACS places the app at /homeassistant/appdaemon/apps/recent_detections/.

Step 4 — Install the custom card

  1. Download unifi-events-card.js from the repo and copy it to /homeassistant/www/
  2. Settings → Dashboards → three-dot menu → Resources
  3. Add resource → URL: /local/unifi-events-card.js → type: JavaScript module

Step 5 — Add credentials to secrets.yaml

 
1
unifi_protect_host: 192.168.1.1
2
unifi_protect_username: localadmin
3
unifi_protect_password: your_password_here

Step 6 — Configure apps.yaml

Paste the recent_detections block into /homeassistant/appdaemon/apps/apps.yaml. Adjust trigger_sensors to match your camera binary sensor entity IDs.

Step 7 — Restart AppDaemon

After restart, check the AppDaemon log. You should see:

 
1
Starting apps: ['recent_detections', ...]
2
Connected. Fetching events from the last 2h...
3
Event feed saved -> /homeassistant/www/unifi_events/recent.json (6 entries)

Step 8 — Add the card to your dashboard

Switch your dashboard to edit mode, add a Manual card, and paste the YAML config from above.

Local Testing

The AppDaemon app also runs as a standalone CLI script — useful for validating your credentials and checking what events come back before setting everything up in HA.

 
1
pip install -r requirements.txt
2
cp local_config.example.py local_config.py
3
# edit local_config.py with your credentials
4
5
cd apps/recent_detections
6
python3 recent_detections.py --count 6
7
8
# then serve the output and open the test card:
9
cd ../..
10
python3 -m http.server 8080
11
# open http://localhost:8080/test_card.html

Supported flags: --hours, --count, --web-root, --types.

Source

The full source is on GitHub: wyne/ha-unifi-events. Issues and PRs welcome.