Skip to content

Commit

Permalink
Merge pull request #1351 from dermotduffy/advanced-scan
Browse files Browse the repository at this point in the history
Allow scan mode to filter triggered camera and to be used in automations
  • Loading branch information
dermotduffy authored Jan 28, 2024
2 parents ad1c323 + 53fa1e0 commit 055d184
Show file tree
Hide file tree
Showing 43 changed files with 1,234 additions and 449 deletions.
113 changes: 98 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,9 @@ See the [fully expanded view configuration example](#config-expanded-view) for h
| - | - | - | - |
| `default` | `live` | :white_check_mark: | The view to show in the card by default. The default camera is the first one listed. See [views](#views) below.|
| `camera_select` | `current` | :white_check_mark: | The view to show when a new camera is selected (e.g. in the camera menu). If `current` the view is unchanged when a new camera is selected. Other acceptable values may be seen at [views](#views) below.|
| `dark_mode` | `off` | :white_check_mark: | Whether or not to turn dark mode `on`, `off` or `auto` to automatically turn on if the card `timeout_seconds` has expired (i.e. card has been left unattended for that period of time) or if dark mode is enabled in the HA profile theme setting. Dark mode dims the brightness by `25%`.|
| `timeout_seconds` | `300` | :white_check_mark: | A numbers of seconds of inactivity after user interaction, after which the card will reset to the default configured view (i.e. 'screensaver' functionality). Inactivity is defined as lack of mouse/touch interaction with the Frigate card. If the default view occurs sooner (e.g. via `update_seconds` or manually) the timer will be stopped. `0` means disable this functionality. |
| `dark_mode` | `off` | :white_check_mark: | Whether or not to turn dark mode `on`, `off` or `auto` to automatically turn on if the card `interaction_seconds` has expired (i.e. card has been left unattended for that period of time) or if dark mode is enabled in the HA profile theme setting. Dark mode dims the brightness by `25%`.|
| `interaction_seconds` | `300` | :white_check_mark: | After a mouse/touch interaction with the Frigate card, it will be considered "interacted with" until this number of seconds elapses without further interaction. May be used in conditions with the `interaction` parameter of a [Frigate card condition](#frigate-card-condition) or with `reset_after_interaction` (below). `0` means no interactions are reported / acted upon. |
| `reset_after_interaction` | `true` | :white_check_mark: | If `true` the card will reset to the default configured view (i.e. 'screensaver' functionality) after `interaction_seconds` has elapsed after user interaction. |
| `update_seconds` | `0` | :white_check_mark: | A number of seconds after which to automatically update/refresh the default view. See [card updates](#card-updates) below for behavior and usecases. If the default view occurs sooner (e.g. manually) the timer will start over. `0` disables this functionality.|
| `update_force` | `false` | :white_check_mark: | Whether automated card updates/refreshes should ignore user interaction. See [card updates](#card-updates) below for behavior and usecases.|
| `update_entities` | | :white_check_mark: | **YAML only**: A card-wide list of entities that should cause the view to reset to the default (if the entity only pertains to a particular camera use `triggers` for the selected camera instead, see [Trigger Configuration](#camera-triggers-configuration)). See [card updates](#card-updates) below for behavior and usecases.|
Expand All @@ -421,20 +422,27 @@ view:
scan:
```

Scan mode allows the card to automatically "follow the action". In this mode the card will automatically select a camera in the `live` view when an entity changes to an active state (specifically `on` or `open`). The entities considered are defined by your camera configuration (see `triggers` parameters). An untrigger is defined as the state for all the configured entities returning to inactive (i.e. not `on` or `open`), with an optional number of seconds to wait prior to the untriggering (see `untrigger_seconds`).
Scan mode allows the card to automatically "follow the action". In this mode the card will automatically execute an action (defined by `trigger_action`) when a camera is 'triggered', by default selecting that camera in the `live` view . The trigger entities considered are defined by your camera configuration (see `triggers` parameters). An untrigger is defined as the state for all the configured entities returning to inactive (i.e. not `on` or `open`), with an optional number of seconds to wait prior to the untriggering (see `untrigger_seconds`).

When the camera untriggers, the view will either remain as-is (if `untrigger_reset` is `false`) and the card return to normal operation, or reset to the default view (if `untrigger_reset` is `true` -- the default).
When the camera untriggers, a different action (defined by `untrigger_action`) is automatically executed, by default returning the card to the default view and camera.

Triggering is only allowed when there is no ongoing human interaction with the card -- interaction will automatically untrigger and further triggering will not occur until after the card has been unattended for `view.timeout_seconds`.
By default, triggering is only allowed when there is no ongoing human interaction with the card. This behavior can be controlled by the `interaction_mode` parameter.

Scan mode tracks Home Assistant state *changes* -- when the card is first started, it takes an active change in state to trigger (i.e. an already occupied room will not trigger it, but a newly occupied room will).

| Option | Default | Overridable | Description |
| - | - | - | - |
| `enabled` | `false` | :white_check_mark: | Whether to enable scan mode. |
| `show_trigger_status` | `true` | :white_check_mark: | Whether or not the card should show a visual indication that it is triggered (a pulsing border around the card edge). |
| `untrigger_reset` | `true` | :white_check_mark: | Whether or not to reset the view to the default after untriggering. |
| `filter_selected_camera` | `false` | :white_check_mark: | If set to `true` will only trigger on the currently selected camera.|
| `show_trigger_status` | `true` | :white_check_mark: | Whether or not the `live` view should show a visual indication that it is triggered (a pulsing border around the camera edge). |
| `untrigger_seconds` | `0` | :white_check_mark: | The number of seconds to wait after all entities are inactive before untriggering. |
| `actions` | | :white_check_mark: | The actions to take when scan mode triggers (see below). |

#### View: Scan Mode Actions configuration

| `trigger` | `live` | :white_check_mark | When `live` the trigger will select the triggered camera in `live` view, when `none` will take no action. |
| `untrigger` | `default` | :white_check_mark | When `default` the untrigger will return to the default view and camera, when `none` will take no action. |
| `interaction_mode` | `inactive` | :white_check_mark: | Whether actions should be taken when the card is being interacted with. If `all`, actions will always be taken regardless. If `inactive` actions will only be taken if the card has *not* had human interaction recently (as defined by `view.interaction_seconds`). If `active` actions will only be taken if the card *has* had human interaction recently. This does not stop triggering itself (i.e. border will still pulse if `show_trigger_status` is true) but rather just prevents the actions being performed.|

### Menu Options

Expand Down Expand Up @@ -1276,12 +1284,14 @@ All variables listed are under a `conditions:` section.
| Condition | Description |
| ------------- | --------------------------------------------- |
| `view` | A list of [views](#views) in which this condition is satified (e.g. `clips`) |
| `camera` | A list of camera ids in which this condition is satisfied. See [camera IDs](#camera-ids).|
| `camera` | A list of camera IDs in which this condition is satisfied. See [camera IDs](#camera-ids).|
| `fullscreen` | If `true` the condition is satisfied if the card is in fullscreen mode. If `false` the condition is satisfied if the card is **NOT** in fullscreen mode.|
| `expand` | If `true` the condition is satisfied if the card is in expanded mode (in a dialog/popup). If `false` the condition is satisfied if the card is **NOT** in expanded mode (in a dialog/popup).|
| `state` | A list of state conditions to compare with Home Assistant state. See below. |
| `media_loaded` | If `true` the condition is satisfied if there is media load**ED** (not load**ING**) in the card (e.g. a clip, snapshot or live view). This may be used to hide controls during media loading or when a message (not media) is being displayed. Note that if `true` this condition will never be satisfied for views that do not themselves load media directly (e.g. gallery).|
| `media_query` | Any valid [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries) string. Media queries must start and end with parentheses. This may be used to alter card configuration based on device/media properties (e.g. viewport width, orientation). Please note that `width` and `height` refer to the entire viewport not just the card. See the [media query example](#media-query-example).|
| `interacted` | If `true` the condition is satisfied if the card has had human interaction within `view.interaction_seconds` elapsed seconds. If `false` the condition is satisfied if the card has **NOT** had human interaction in that time. |
| `triggered` | A list of camera IDs which, if triggered in [scan mode](#scan-mode), satisfy the condition.|

See the [example below](#frigate-card-conditional-example) for a real-world example of how these conditions can be used.

Expand Down Expand Up @@ -1422,7 +1432,7 @@ Parameters for the `custom:frigate-card-conditional` element:
|`download`|Download the displayed media.|
|`camera_ui`|Open the Frigate UI at the configured URL.|
|`fullscreen`|Toggle fullscreen.|
|`camera_select`|Select a given camera. Takes a single additional `camera` parameter with the [camera ID](#camera-ids) of the camera to select. Respects the value of `view.camera_select` to choose the appropriate view on the new camera.|
|`camera_select`|Select a given camera. Takes an additional `camera` parameter with the [camera ID](#camera-ids) of the camera to select. Respects the value of `view.camera_select` to choose the appropriate view on the new camera. If a `triggered` parameter is set to `true` instead of `camera` being specified then a triggered camera (if any) is selected instead. |
|`menu_toggle` | Show/hide the menu (for the `hidden` mode style). |
|`media_player`| Perform a media player action. Takes a `media_player` parameter with the entity ID of the media_player on which to perform the action, and a `media_player_action` parameter which should be either `play` or `stop` to play or stop the media in question. |
|`live_substream_select`| Perform a media player action. Takes a `camera` parameter with the [camera ID](#camera-ids) of the substream camera. |
Expand All @@ -1449,7 +1459,7 @@ This card supports several different views:
|`clip`|Shows a viewer for the most recent clip for this camera. Can also be accessed by holding down the `clips` menu icon.|
|`recordings`|Shows a gallery of recent (last day) recordings for this camera and its dependents.|
|`recording`|Shows a viewer for the most recent recording for this camera. Can also be accessed by holding down the `recordings` menu icon.|
|`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.timeout_seconds`).|
|`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.interaction_seconds`).|
|`timeline`|Shows an event timeline.|

### Navigating From A Snapshot To A Clip
Expand Down Expand Up @@ -1901,7 +1911,7 @@ Reference: [View Options](#view-options).
view:
default: live
camera_select: current
timeout_seconds: 300
interaction_seconds: 300
update_seconds: 0
update_force: false
update_cycle_camera: false
Expand All @@ -1913,8 +1923,12 @@ view:
scan:
enabled: false
show_trigger_status: true
untrigger_reset: true
filter_selected_camera: false
untrigger_seconds: 0
actions:
interaction_mode: 'inactive' as const,
trigger: 'live' as const,
untrigger: 'default' as const,
actions:
entity: light.office_main_lights
tap_action:
Expand Down Expand Up @@ -2849,7 +2863,7 @@ overrides:
view:
default: live
camera_select: current
timeout_seconds: 300
interaction_seconds: 300
update_seconds: 0
update_force: false
update_cycle_camera: false
Expand Down Expand Up @@ -3652,6 +3666,37 @@ view:
```
</details>

<details>
<summary>Expand: Showing an alarm menu button when a camera is triggered</summary>

This example adds a menu button to optionally activate a siren when the camera is triggered.

```yaml
type: custom:frigate-card
cameras:
- camera_entity: camera.office
elements:
- type: custom:frigate-card-conditional
elements:
- type: custom:frigate-card-menu-icon
icon: mdi:alarm-bell
title: Activate alarm
style:
color: red
tap_action:
action: call-service
service: homeassistant.toggle
data:
entity_id: siren.siren
conditions:
triggered:
- camera.office
view:
scan:
enabled: true
```
</details>

<a name="media-layout-examples"></a>

### Changing the Media Layout
Expand Down Expand Up @@ -4040,6 +4085,44 @@ views:
</details>
### Interaction conditions
The card can automatically execute actions when the card is interacted with (mouse or touch).
<details>
<summary>Expand: Automatically show a high-bandwidth stream on interaction</summary>
This example will automatically use a HD live substream when the mouse cursor interacts with the card.
```yaml
type: custom:frigate-card
cameras:
- live_provider: go2rtc
camera_entity: camera.office
triggers:
entities:
- switch.office_detect
dependencies:
cameras:
- camera.office_hd
- camera_entity: camera.office_hd
live_provider: go2rtc
hide: true
automations:
- actions:
- action: custom:frigate-card-action
frigate_card_action: live_substream_on
actions_not:
- action: custom:frigate-card-action
frigate_card_action: live_substream_off
conditions:
interaction: true
view:
scan:
enabled: true
```
</details>
<a name="media-layout-examples"></a>
## Card Refreshes
Expand All @@ -4055,7 +4138,7 @@ Note that no (other) automated updates are permitted when [scan mode](#scan-mode
In the below "Trigger Entities" refers to the combination of `view.update_entities` and the `triggers.entities` for the currently selected camera (which in turn will also include the occupancy and motion sensor entities for Frigate cameras if `triggers.occupancy` and `triggers.motion` options are enabled, see [Trigger Configuration](#camera-triggers-configuration)).

| `view . update_seconds` | `view . timeout_seconds` | `view . update_force` | Trigger Entities | Behavior |
| `view . update_seconds` | `view . interaction_seconds` | `view . update_force` | Trigger Entities | Behavior |
| :-: | :-: | :-: | :-: | - |
| `0` | `0` | *(Any value)* | Unset | Card will not automatically refresh. |
| `0` | `0` | *(Any value)* | *(Any entity)* | Card will reload default view & camera when entity state changes. |
Expand Down Expand Up @@ -4097,7 +4180,7 @@ view:
```yaml
view:
default: clip
timeout_seconds: 30
interaction_seconds: 30
```

<a name="query-string-actions"></a>
Expand Down
34 changes: 30 additions & 4 deletions src/card-controller/actions-manager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
Actions,
ActionsConfig,
ActionType,
FrigateCardCustomAction,
FRIGATE_CARD_VIEW_DEFAULT,
} from '../config/types.js';
import {
convertActionToFrigateCardCustomAction,
frigateCardHandleAction,
frigateCardHandleActionConfig,
getActionConfigGivenAction,
} from '../utils/action.js';
Expand Down Expand Up @@ -81,16 +83,36 @@ export class ActionsManager {

const frigateCardAction = convertActionToFrigateCardCustomAction(ev.detail);
if (frigateCardAction) {
this.executeAction(frigateCardAction);
this.executeFrigateAction(frigateCardAction);
}
};

/**
* Small convenience method to call frigateCardHandleAction without the caller
* needing hass or the element.
*/
public executeActions(actions: ActionType | ActionType[]): void {
const hass = this._api.getHASSManager().getHASS();
if (!hass) {
return;
}

frigateCardHandleAction(
this._api.getCardElementManager().getElement(),
hass,
{},
actions,
);
}

/**
* Execute a card action.
* @param frigateCardAction
* @returns `true` if an action is executed.
*/
public async executeAction(frigateCardAction: FrigateCardCustomAction): Promise<void> {
public async executeFrigateAction(
frigateCardAction: FrigateCardCustomAction,
): Promise<void> {
const config = this._api.getConfigManager().getConfig();
const mediaLoadedInfoManager = this._api.getMediaLoadedInfoManager();

Expand Down Expand Up @@ -146,8 +168,12 @@ export class ActionsManager {
this._api.getCardElementManager().toggleMenu();
break;
case 'camera_select':
const selectCameraID = frigateCardAction.camera;
if (view) {
const selectCameraID =
frigateCardAction.camera ??
(frigateCardAction.triggered
? this._api.getTriggersManager().getMostRecentlyTriggeredCameraID()
: null);
if (selectCameraID && view) {
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
const targetViewName =
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
Expand Down
7 changes: 7 additions & 0 deletions src/card-controller/card-element-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export class CardElementManager {
}

public elementConnected(): void {
// Set initial condition state. Must be done after the element is connected to
// allow callbacks to interact with the card.
this._api.getInteractionManager().initialize();
this._api.getFullscreenManager().initialize();
this._api.getExpandManager().initialize();
this._api.getMediaLoadedInfoManager().initialize();

// Whether or not the card is in panel mode on the dashboard.
setOrRemoveAttribute(this._element, isCardInPanel(this._element), 'panel');

Expand Down
15 changes: 13 additions & 2 deletions src/card-controller/conditions-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
frigateConditionalSchema,
OverrideConfigurationKey,
RawFrigateCardConfig,
ViewDisplayMode
ViewDisplayMode,
} from '../config/types';
import { CardConditionAPI } from './types';

Expand All @@ -18,6 +18,8 @@ interface ConditionState {
state?: HassEntities;
media_loaded?: boolean;
displayMode?: ViewDisplayMode;
triggered?: Set<string>;
interaction?: boolean;
}

export class ConditionEvaluateRequestEvent extends Event {
Expand Down Expand Up @@ -133,7 +135,7 @@ export class ConditionsManager {
this._listeners = [
() => this._api.getConfigManager().computeOverrideConfig(),
() => this._api.getAutomationsManager().execute(),
...(listener ? [listener] : [])
...(listener ? [listener] : []),
];
}

Expand Down Expand Up @@ -243,6 +245,15 @@ export class ConditionsManager {
if (condition.display_mode) {
result &&= !!state.displayMode && condition.display_mode === state.displayMode;
}
if (condition.triggered?.length) {
result &&= condition.triggered.some((triggeredCameraID) =>
state.triggered?.has(triggeredCameraID),
);
}
if (condition.interaction !== undefined) {
result &&=
state.interaction !== undefined && condition.interaction === state.interaction;
}
return result;
}

Expand Down
12 changes: 10 additions & 2 deletions src/card-controller/expand-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export class ExpandManager {
this._api = api;
}

public initialize(): void {
this._setConditionState();
}

public isExpanded(): boolean {
return this._expanded;
}
Expand All @@ -23,9 +27,13 @@ export class ExpandManager {
}

this._expanded = expanded;
this._setConditionState();
this._api.getCardElementManager().update();
}

protected _setConditionState(): void {
this._api.getConditionsManager()?.setState({
expand: expanded,
expand: this._expanded,
});
this._api.getCardElementManager().update();
}
}
Loading

0 comments on commit 055d184

Please sign in to comment.