Commit Graph
90 Commits
Author SHA1 Message Date
Dermot Duffy a31816c168 feat: Add event-based automation triggers (#2537) 2026-06-30 17:45:13 -07:00
Dermot DuffyandClaude Opus 4.8 b701366762 feat: Align automations with Home Assistant triggers and conditions (#2527)
Split automations into HA-style `triggers`, ongoing `conditions`, and
`actions`, with compatibility migrations for existing Advanced Camera
Card configs.

## Summary

At a glance (details below):

- **Added** `triggers:` -- a required, HA-shaped block: stock `state` /
`numeric_state` / `template` plus card-specific triggers (`camera`,
`view`, `fullscreen`, ...).
- **Added** the HA-native `if` / `then` / `else` action.
- **Removed** `actions_not` (replaced by `if` / `then` / `else`).
- **Removed** the ambient `advanced_camera_card` template namespace (use
`acc` instead).
- **Changed** the trigger template surface to a top-level `trigger.*`
variable (as in HA); the nested `acc.trigger.*` paths are removed.
- **Changed** `conditions:` to ongoing gates only -- they no longer wake
an automation, and change-only forms (`config`, valueless `camera` /
`view` / `state`) become triggers, not conditions.
- **Changed** action templates to render per step, so a later action
sees state an earlier one changed.
- **Compatibility:** HA-shaped YAML is accepted (singular keys,
single-or-list, `and` / `or` / `not` shorthand, `entity` / `entity_id`).
- **Migration:** existing configs upgrade automatically; anything that
cannot be converted faithfully is recorded under `__UPGRADE_FAILURE__`
for manual fixup.

## Breaking Changes

### 1. Automations now require triggers

Before this PR, `automations[].conditions` served two roles:

- They decided whether the automation should run.
- They also acted as the thing that woke the automation up.

After this PR:

- `triggers` wake the automation.
- `conditions` only gate it at the instant a trigger fires.

Most existing automations are migrated automatically from `conditions:`
to `triggers:`.

### 2. `actions_not` is retired

Legacy `actions_not` is replaced by an HA-style `if` action with `then`
/ `else`.

Faithful conversions are automatic. Cases that cannot be faithfully
converted are recorded under `__UPGRADE_FAILURE__.automations` and must
be migrated manually.

### 3. Template surface aligned with Home Assistant

Two related template changes, both auto-migrated:

- **Top-level `trigger.*`.** Automation actions now receive a top-level
`trigger` template variable, like Home Assistant. Legacy nested paths
such as `acc.trigger.state.to` and
`advanced_camera_card.trigger.camera.to` are migrated automatically when
they appear inside template strings.
- **The ambient `advanced_camera_card` template namespace is removed.**
The long-form ambient namespace (`advanced_camera_card.camera`,
`advanced_camera_card.view`, `advanced_camera_card.config`) is retired
in favour of its shorter `acc` alias -- supported since v7.1.0, and the
only spelling the new trigger surface uses. Existing templates are
migrated automatically by rewriting the `advanced_camera_card.` prefix
to `acc.`.

### 4. Trigger-only condition forms are no longer valid conditions

Some legacy "conditions" were really change detectors. These are now
triggers only:

- `condition: config`
- valueless `camera`
- valueless `view`
- valueless `state` / picture-elements state condition with neither
`state` nor `state_not`

These are automatically promoted in automations and stripped from
overrides/elements where they would no longer be meaningful as ongoing
conditions.

### 5. Template truthiness now follows Home Assistant behavior

Template conditions and template triggers intentionally use different
truthiness rules, matching HA:

- A template condition passes only when the rendered value is `true`
(case-insensitive), matching HA's `condition.py`.
- A template trigger uses HA's broader `result_as_boolean` coercion: a
non-zero number, or `1` / `true` / `yes` / `on` / `enable`
(case-insensitive), counts as true.

### 6. Action templates render when each action executes

Action templates are now rendered per action step, not once for the
whole sequence. This means a later action can see card-local state
changed by an earlier action in the same sequence.

The `trigger` context is fixed for the automation run. HA entity state
updates still depend on the frontend receiving updated HASS state over
the websocket.

## Automatic Migrations

### Automation `conditions:` to `triggers:`

Simple legacy automation:

```yaml
# Before
automations:
  - conditions:
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

State conditions become HA-style state triggers:

```yaml
# Before
automations:
  - conditions:
      - condition: state
        entity_id: binary_sensor.front_door
        state: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: state
        entity_id: binary_sensor.front_door
        to: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
```

Multiple conditions become both triggers and ongoing conditions:

```yaml
# Before
automations:
  - conditions:
      - condition: camera
        cameras: [front_door]
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: camera
        cameras: [front_door]
      - trigger: fullscreen
        fullscreen: true
    conditions:
      - condition: camera
        cameras: [front_door]
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

The flattened trigger list is an implicit OR. The retained `conditions:`
list is an implicit AND checked when any trigger fires.

### Trigger-only legacy conditions

Legacy `config` conditions become `config` triggers:

```yaml
# Before
automations:
  - conditions:
      - condition: config
        paths: [menu.style]
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: status_bar
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: config
        paths: [menu.style]
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: status_bar
```

Trigger-only leaves are removed from retained `conditions:` blocks
because they no longer describe an ongoing state.

### `actions_not` to `if` / `then` / `else`

```yaml
# Before
automations:
  - conditions:
      - condition: state
        entity_id: input_boolean.camera_alerts
        state: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
    actions_not:
      - action: none
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: state
        entity_id: input_boolean.camera_alerts
    actions:
      - if:
          - condition: state
            entity_id: input_boolean.camera_alerts
            state: 'on'
        then:
          - action: custom:advanced-camera-card-action
            advanced_camera_card_action: live
        else:
          - action: none
```

If the legacy automation had no conditions, or only trigger-only
conditions, `actions_not` is dropped because the old `else` branch could
not be reproduced as an ongoing predicate.

### Trigger template paths

```yaml
# Before
message: 'Door is {{ acc.trigger.state.to }} from {{ acc.trigger.state.from }}'
```

```yaml
# After, automatic
message: 'Door is {{ trigger.to_state.state }} from {{ trigger.from_state.state }}'
```

Path rewrites performed automatically:

| Old path                   | New path                   |
| -------------------------- | -------------------------- |
| `acc.trigger.state.entity` | `trigger.entity_id`        |
| `acc.trigger.state.from`   | `trigger.from_state.state` |
| `acc.trigger.state.to`     | `trigger.to_state.state`   |
| `acc.trigger.camera.from`  | `trigger.from_acc.camera`  |
| `acc.trigger.camera.to`    | `trigger.to_acc.camera`    |
| `acc.trigger.view.from`    | `trigger.from_acc.view`    |
| `acc.trigger.view.to`      | `trigger.to_acc.view`      |
| `acc.trigger.config.from`  | `trigger.from_acc.config`  |
| `acc.trigger.config.to`    | `trigger.to_acc.config`    |

The same rewrites are applied for the older
`advanced_camera_card.trigger.*` namespace.

### Ambient template namespace

Any remaining long-form ambient `advanced_camera_card.*` references
(outside the trigger surface) are rewritten to the `acc.*` alias:

```yaml
# Before
title: 'Now viewing {{ advanced_camera_card.camera }}'
```

```yaml
# After, automatic
title: 'Now viewing {{ acc.camera }}'
```

## Manual Migration Cases

### `__UPGRADE_FAILURE__.automations`

If a legacy automation cannot be converted faithfully, the original
automation is recorded under:

```yaml
__UPGRADE_FAILURE__:
  automations:
    - ...
```

These entries require manual migration.

The main known case is legacy `actions_not` with a condition whose
trigger can only fire on a rising edge, such as:

- `condition: template`
- `condition: screen`
- `condition: numeric_state` without an entity-backed state to watch

Those conditions can start the `then` branch, but cannot reliably start
the `else` branch when they stop matching.

### Unsupported HA conditions and triggers

This PR aligns the card with HA where supported, but it is not a full HA
automation engine.

Unsupported HA condition families include:

- `time`
- `zone`
- `sun`
- `location`
- `device`
- `condition: trigger`

Unsupported HA trigger platforms include:

- `event`
- `time`
- `time_pattern`
- `sun`
- `zone`
- `calendar`
- `webhook`
- `tag`
- `device`
- `mqtt`

The card-specific camera `triggers:` feature (which auto-selects and
wakes the card on camera events such as motion) is a separate feature
from automation `triggers:`, despite the shared word.

### Trigger IDs and variables

HA keys such as `id`, `alias`, and `variables` are accepted so pasted HA
YAML validates, but they are ignored by the card. There is no
`trigger.id` support in this PR.

## New Compatibility Features

This PR also makes card config more forgiving for HA-style YAML:

- `trigger`, `condition`, and `action` singular keys are accepted and
normalized to `triggers`, `conditions`, and `actions`.
- Single trigger, condition, and action objects are accepted where lists
are expected.
- `if`, `then`, and `else` accept a single item or a list.
- Composite condition shorthand is accepted:
  - `{ and: [...] }`
  - `{ or: [...] }`
  - `{ not: [...] }`
  - `{ condition: [...] }` as an implicit AND
- State conditions resolve expected state values that name another
entity, matching HA/Lovelace behavior.
- Both `entity` and `entity_id` are accepted on state and numeric
conditions and triggers (a superset of HA's two dialects), so there is
no forced rename.
- `state_not` remains supported as a card/Lovelace-friendly extension.

## Trigger Payloads

Automation action templates receive a top-level `trigger` object.

For stock `state` and `numeric_state` triggers:

```yaml
trigger.platform
trigger.entity_id
trigger.entity
trigger.from_state
trigger.to_state
```

For template triggers:

```yaml
trigger.platform
```

For card-specific triggers:

```yaml
trigger.platform # "acc"
trigger.type
trigger.from_acc
trigger.to_acc
```

The card does not currently expose HA's `id`, `idx`, `for`, `attribute`,
`above`, `below`, or `alias` trigger fields.

BREAKING CHANGE: Automations now follow Home Assistant's `triggers:` /
`conditions:` / `actions:` model. Automations require a `triggers:`
block and `conditions:` no longer wake an automation; `actions_not` is
removed in favour of an `if` / `then` / `else` action; the nested
`acc.trigger.*` template paths and the ambient `advanced_camera_card`
template namespace are removed (use the top-level `trigger.*` surface
and the `acc` alias); trigger-only condition forms (`config`, valueless
`camera` / `view` / `state`) are no longer valid conditions; and
template-condition vs template-trigger truthiness now follow HA.
Existing configs are upgraded automatically where a faithful conversion
exists; anything that cannot be converted is recorded under
`__UPGRADE_FAILURE__` for manual migration.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 17:45:13 -07:00
Dermot Duffy 37df382aa2 fix: Fix leaky Frigate subscriptions/unsubscriptions (#2513) 2026-06-30 17:45:13 -07:00
Dermot Duffy 0a36358394 feat: Trigger cameras on HA bus events (#2512) 2026-06-30 17:45:13 -07:00
Dermot Duffy 406176eebc feat: Add 'doorbell' configuration profile (#2511) 2026-06-30 17:45:13 -07:00
Dermot Duffy 773cee95a5 feat: Add automatic doorbell detection (#2507) 2026-06-30 17:45:13 -07:00
Dermot Duffy 4b72fcd629 feat: Support HA event entities as triggers (#2506) 2026-06-30 17:45:13 -07:00
Dermot Duffy 261a7e1a92 feat: Auto-resolve Frigate client_id from camera entity (#2475)
Credit goes to @Eduardo-Jaramillo for the inspiration and contribution
of https://github.com/dermotduffy/advanced-camera-card/pull/2474 .
2026-06-30 17:45:12 -07:00
Dermot Duffy 31d0c9322f fix: Always respect the returned MIME type (#2473)
- Closes #2449
2026-06-30 17:45:12 -07:00
Dermot Duffy 591f466b92 fix: Render sub-labels for Frigate reviews (#2468)
- Closes: #2438
2026-06-30 17:45:12 -07:00
Dermot Duffy 47bcce93d3 feat: Add hardened error handling and retries (#2451)
- Closes #1830
 - Closes #2099
2026-06-30 17:45:12 -07:00
Dermot Duffy 1cd5520154 feat: Add proxying support for images (#2427)
- Closes #2418
2026-06-30 17:45:12 -07:00
Dermot Duffy 62fe65556e test: Expand testing to cover 100% of camera manager files (#2420) 2026-06-30 17:45:12 -07:00
Dermot Duffy abf1d8defe test: Add full testing for the frigate engine (#2419) 2026-06-30 17:45:12 -07:00
Dermot Duffy 415a3e0886 test: Improve test coverage of various camera engine files (#2415) 2026-06-30 17:45:12 -07:00
Dermot Duffy 95faddd9a0 fix: Add support for Reolink number based zooming (#2408)
- Closes #2034
2026-03-13 19:55:27 -07:00
Dermot Duffy 497e6e88a0 perf: Variety of small type and performance fixes (#2367) 2026-02-22 15:02:14 -08:00
Dermot Duffy a15376602f feat: Allow 2-way audio detection to be skipped and timed (#2355)
- For #2313
2026-02-16 19:47:56 -08:00
Dermot Duffy 7dbf058ab2 fix: Seek to the start of review media (#2353) 2026-02-16 14:52:11 -08:00
Dermot Duffy 47880e4306 feat: Add ability to untrigger after fixed number of seconds (#2350)
- Related: #2342 


BREAKING CHANGE: Renames: `untrigger_seconds` to
`untrigger_delay_seconds`
2026-02-16 12:25:56 -08:00
dermotduffy 584521c78e Merge branch 'main' into dev 2026-02-15 19:28:38 -08:00
Dermot Duffy b1467dc4bd fix: Improve state handling to avoid stuck triggered cameras (#2348)
- Related:
https://github.com/dermotduffy/advanced-camera-card/issues/2342
2026-02-15 19:26:56 -08:00
Dermot Duffy 93f1f08e51 feat: Make cameras optional (#2343) 2026-02-13 20:21:05 -08:00
dermotduffy 0a099b95f7 Merge branch 'main' into dev 2026-02-07 21:59:50 -08:00
Dermot Duffy 49073fee48 fix: Update components and styles to work with >= HA 2026.2 (#2333)
Caution: Requires HA >= `2026.2`

- Closes #2329
- Closes #2332
2026-02-07 20:32:05 -08:00
Dermot Duffy c03ddd9a13 feat: Add severity media filter (#2323) 2026-01-24 21:25:10 -08:00
Dermot Duffy f4e905a7fb fix: Improve review media filtering (#2322) 2026-01-24 16:22:12 -08:00
Dermot Duffy fc32727860 feat: Add support for Frigate reviews / detections [initial PR] (#2315)
- Add support for Frigate reviews / detections.
 - Add support for GenAI metadata.
- Significant internal refactor to more flexible "UnifiedQuery" to allow
mixing cameras with simple metadata and review metadata (e.g. a timeline
view of a Frigate camera with reviews, and a Reolink camera with simple
metadata).
 - Add support for folder media as camera media.

There are a few more PRs to commit prior to this going live, but
commiting this for now due to the scale of the change.

BREAKING CHANGE: `media_type` and `events_type` are retired under
`live`, `viewer` and `timeline` configuration sections, instead media
type is associated (once) with the camera under `media`.
2026-01-19 13:32:50 -08:00
Dermot Duffy d17ecd6c12 fix: Small (rare) bug in Frigate PTZ identification capability (#2296)
[skip ci]
2025-12-20 14:52:07 -08:00
Dermot Duffy 1e821f09c6 fix: Improve 2-way audio detection (#2293)
- For Frigate cameras, you must be running at least [integration
v5.12.0](https://github.com/blakeblackshear/frigate-hass-integration/releases/tag/v5.12.0).
 - Closes: #2191 

Includes a significant refactor of cameras, and the introduction of a
`2-way-audio` capability that is dynamically fetched from `go2rtc`. One
gotcha is if you previously had a substream with 2-way audio, you may
need to modify

```yaml
 - camera_entity: camera.foo
    capabilities:
      disable_except:
        - substream
```

... to ...

```yaml
 - camera_entity: camera.foo
    capabilities:
      disable_except:
        - substream
        - 2-way-audio
```
2025-12-20 13:06:09 -08:00
Dermot Duffy f64690335b test: Improve timezone resilience / cleanliness of tests (#2279)
- Related: https://github.com/dermotduffy/advanced-camera-card/pull/2278
- Reported by @0x464e
2025-12-11 05:33:48 -08:00
Dermot Duffy bbdf96474e fix: Reolink Trackmix cameras should be detected correctly (#2264)
- Closes: #2254
2025-12-08 17:23:59 -08:00
Dermot Duffy f276b8c57d feat: Automatically detect Reolink NVR channel number (#2259)
- Closes: #2032, #1899 
 -  May or may not help with #2254

- Caution: I do not have an NVR-based Reolink camera to test. If
suddenly Reolink cameras don't work in the card, or in particular if
media doesn't correctly load, this is likely the culprit PR.
2025-12-07 22:48:33 -08:00
Dermot Duffy c38bf192e0 fix: PTZ actions should reflect camera rotation (#2258)
- Closes: #2210
2025-12-07 21:12:12 -08:00
Dermot Duffy 596a37903f feat: Add TPLink camera engine support (#2257)
* Related #2183

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduce `tplink` camera engine with relative PTZ via button
entities, add icons/docs support, refactor to `EntityCamera`, and extend
tests/schema/factory for auto-detection.
> 
> - **Engines**
>   - **New `tplink` engine**:
>     - Add `Engine.TPLink`, schema support (`engine: 'tplink'`).
> - Implement `TPLinkCameraManagerEngine` and `TPLinkCamera` (relative
PTZ via `button.*` entities; no presets/zoom; stop is no-op).
> - Update `engine-factory` to create/auto-detect `tplink` (platform
`tplink`).
> - **Refactor**
> - Replace `BrowseMediaCamera` with `EntityCamera`; update `motioneye`
and `reolink` cameras and `motioneye` engine type checks.
> - **UI/Icons**
> - Add `tplink.svg`; wire into `IconController`; update docs icon copy
script.
> - **Docs**
> - Add `tplink` to engine capability and live-provider matrices; new
`tplink` section incl. PTZ note; add custom icon entry.
> - **Tests**
> - Add TPLink camera/engine tests and factory tests; remove obsolete
`BrowseMediaCamera` test.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
74a688c3bd602912d63a22c3c6fa2f4b60ec14f9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-12-07 16:57:33 -08:00
Dermot Duffy 1f6a4ddce2 fix: Fix folder media in timeline when camera is not timeline supported (#2246)
- Closes: #2205
2025-11-24 14:33:06 -08:00
Dermot Duffy 79c27c43b8 fix: Don't incorrectly change camera when a dependency supports media (#2171)
- Closes: #2122
2025-09-01 13:29:04 -07:00
Dermot Duffy bc5022ef1a feat: Enable go2rtc live stream proxying (#2159)
- Closes #1984
2025-08-28 21:19:19 -07:00
Dermot Duffy c6a4c8aea2 feat: Implement basic general folder support (#2051)
- Related: #1748
2025-05-21 19:59:21 -07:00
Dermot Duffy 2eb0d9e35e fix: Reolink PTZ support should work with hub-connected cameras (#2014)
- Related: #1964
2025-04-16 10:58:18 +01:00
Dermot Duffy a24c9628c5 fix: Reolink PTZ support should detect presets (#1997)
- Related: #1964
2025-04-06 17:47:43 +01:00
Dermot Duffy b5eee5878a feat: Add out of the box support for Reolink PTZ (#1982)
- Closes: #1964
2025-03-26 21:26:02 -07:00
Dermot Duffy d6225fefda refactor: Break apart monolithic configuration schema (#1977)
[skip ci]
2025-03-22 15:06:11 -07:00
Dermot Duffy a79ffa6edc refactor: Retire the use of unmaintained custom-card-helpers (#1956)
- Closes #1295
2025-03-09 16:02:33 -07:00
Dermot Duffy bb147a45fd fix: Reolink metadata should not reject media with labels (#1900)
- Related to: #1870
2025-02-17 20:13:21 -08:00
Dermot Duffy c66c3ec919 feat: Add control/output of selected camera via an entity (#1895)
- Closes #1871
2025-02-17 13:33:10 -08:00
Dermot Duffy cc376a8be1 feat: Rename the card to advanced-camera-card (#1873)
Whilst the Frigate support in this card is the best among camera
engines, the name incorrectly suggests that Frigate is a requirement.
Instead, to broaden the appeal, change to more camera agnostic name.
This does not suggest any change in priority, role or support for
Frigate.

This change is likely to be bug prone, due to the size of the rename --
the code contains 1500+ references to "Frigate" most of which make sense
to rename, some which do not, all of which needed human assessment.

- Closes #1298


BREAKING CHANGE: References to `frigate-card` in all kinds of
configuration need to be updated to `advanced-camera-card`. An automated
config upgrade should take care of the majority of usecases (click `Edit
-> Upgrade -> Save`), though may not be perfect.
2025-02-06 19:32:32 -08:00
Dermot Duffy 805a21107d fix: Correctly detect configuration for the webrtc-card live provider (#1850)
- Closes #1843

If this breaks something for you, please let me know. As a workaround,
you should manually be able to set it back to what it was with something
like:

```yaml
cameras:
 - camera_entity: camera.office
   webrtc_card:
      url: <frigate camera name>
```
2025-01-21 19:42:47 -08:00
Dermot Duffy 0212840688 fix: Substream only cameras should not be triggerable (#1848)
- Closes #1846
2025-01-21 07:17:09 -08:00
Dermot Duffy 7657bdf22c fix: Rendering not correctly forced when tab changed in certain circumstances (#1817)
- Closes #1811
- Closes #1769
2025-01-09 20:21:26 -08:00