From 67b33b1026282d5fb80f5b1a57f7c41071d35ed5 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 5 Jun 2022 17:55:01 -0700 Subject: [PATCH] Add untrigger timer. --- README.md | 12 ++++++-- src/card.ts | 53 ++++++++++++++++++++++++++-------- src/const.ts | 4 +++ src/editor.ts | 22 ++++++++++---- src/localize/languages/en.json | 4 ++- src/types.ts | 4 +++ 6 files changed, 79 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index cf94edd7..306e5207 100644 --- a/README.md +++ b/README.md @@ -191,14 +191,20 @@ 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 it is triggered (as defined by your camera configuration, see `trigger_by_motion`, `trigger_by_occupancy` and `trigger_by_entities` parameters). When the camera untriggers, the camera selection will return to the next most recently triggered camera (as long as it is still triggered) -- if there are no triggered cameras remaining, the camera will return to the default. Triggering is only allowed when there is no ongoing human interaction with the card -- interaction will automatically untrigger it and further triggering will not occur until after the card has been unattended for `view.timeout_seconds`. +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 `trigger_by_motion`, `trigger_by_occupancy` and `trigger_by_entities` 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 tracks Home Assistant state changes -- when the card is first started, it takes a positive change in state to trigger (i.e. an already occupied room will not trigger it, but a newly occupied room would trigger it). +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). + +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`. + +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. | +| `untrigger_seconds` | `0` | :white_check_mark: | The number of seconds to wait after all entities are inactive before untriggering. | ### Menu Options @@ -1055,6 +1061,8 @@ view: scan: enabled: false show_trigger_status: true + untrigger_reset: true + untrigger_seconds: 0 actions: entity: light.office_main_lights tap_action: diff --git a/src/card.ts b/src/card.ts index ddbf2987..fb54b880 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1,12 +1,5 @@ import { getLovelace, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; -import { - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, - unsafeCSS -} from 'lit'; +import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; @@ -197,6 +190,7 @@ export class FrigateCard extends LitElement { protected _initialized = false; protected _triggers: Map = new Map(); + protected _untriggerTimerID: number | null = null; /** * Set the Home Assistant object. @@ -1003,8 +997,7 @@ export class FrigateCard extends LitElement { if (triggerChanges) { if (!this._triggers.size) { - this._changeView(); - changedCamera = true; + this._startUntriggerTimer(); } else { const targetCamera = this._getMostRecentTrigger(); if ( @@ -1019,11 +1012,47 @@ export class FrigateCard extends LitElement { return changedCamera; } + /** + * Determine if the scan mode is currently triggered. + * @returns + */ + protected _isTriggered(): boolean { + return !!this._triggers.size || !!this._untriggerTimerID; + } + /** * Untrigger the card. */ protected _untrigger(): void { this._triggers.clear(); + this._clearUntriggerTimer(); + } + + /** + * Start the untrigger timer. + */ + protected _startUntriggerTimer(): void { + this._clearUntriggerTimer(); + + this._untriggerTimerID = window.setTimeout(() => { + this._untrigger(); + if ( + this._isAutomatedViewUpdateAllowed() && + this._getConfig().view.scan.untrigger_reset + ) { + this._changeView(); + } + }, this._getConfig().view.scan.untrigger_seconds * 1000); + } + + /** + * Clear the user interaction ('screensaver') timer. + */ + protected _clearUntriggerTimer(): void { + if (this._untriggerTimerID) { + window.clearTimeout(this._untriggerTimerID); + this._untriggerTimerID = null; + } } /** @@ -1385,7 +1414,7 @@ export class FrigateCard extends LitElement { */ protected _isAutomatedViewUpdateAllowed(ignoreTriggers?: boolean): boolean { return ( - (ignoreTriggers || !this._triggers.size) && + (ignoreTriggers || !this._isTriggered()) && (this._getConfig().view.update_force || !this._interactionTimerID) ); } @@ -1568,7 +1597,7 @@ export class FrigateCard extends LitElement { const mainClasses = { main: true, triggered: - !!this._triggers.size && this._getConfig().view.scan.show_trigger_status, + !!this._isTriggered() && this._getConfig().view.scan.show_trigger_status, }; const actions = this._getMergedActions(); diff --git a/src/const.ts b/src/const.ts index bd081db1..269c4ac7 100644 --- a/src/const.ts +++ b/src/const.ts @@ -43,6 +43,10 @@ export const CONF_VIEW_SCAN = `${CONF_VIEW}.scan` as const; export const CONF_VIEW_SCAN_ENABLED = `${CONF_VIEW_SCAN}.enabled` as const; export const CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS = `${CONF_VIEW_SCAN}.show_trigger_status` as const; +export const CONF_VIEW_SCAN_UNTRIGGER_RESET = + `${CONF_VIEW_SCAN}.untrigger_reset` as const; +export const CONF_VIEW_SCAN_UNTRIGGER_SECONDS = + `${CONF_VIEW_SCAN}.untrigger_seconds` as const; export const CONF_EVENT_GALLERY = 'event_gallery' as const; export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS = diff --git a/src/editor.ts b/src/editor.ts index 6d818e1c..ebd52bd7 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -32,7 +32,8 @@ import { CONF_DIMENSIONS_ASPECT_RATIO_MODE, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_CONTROLS, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS, - CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE, CONF_IMAGE_MODE, + CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE, + CONF_IMAGE_MODE, CONF_IMAGE_REFRESH_SECONDS, CONF_IMAGE_URL, CONF_LIVE_AUTO_MUTE, @@ -52,7 +53,8 @@ import { CONF_LIVE_LAZY_LOAD, CONF_LIVE_LAZY_UNLOAD, CONF_LIVE_PRELOAD, - CONF_LIVE_TRANSITION_EFFECT, CONF_MEDIA_VIEWER_AUTO_MUTE, + CONF_LIVE_TRANSITION_EFFECT, + CONF_MEDIA_VIEWER_AUTO_MUTE, CONF_MEDIA_VIEWER_AUTO_PAUSE, CONF_MEDIA_VIEWER_AUTO_PLAY, CONF_MEDIA_VIEWER_AUTO_UNMUTE, @@ -66,7 +68,8 @@ import { CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE, CONF_MEDIA_VIEWER_DRAGGABLE, CONF_MEDIA_VIEWER_LAZY_LOAD, - CONF_MEDIA_VIEWER_TRANSITION_EFFECT, CONF_MENU_ALIGNMENT, + CONF_MEDIA_VIEWER_TRANSITION_EFFECT, + CONF_MENU_ALIGNMENT, CONF_MENU_BUTTONS, CONF_MENU_BUTTON_SIZE, CONF_MENU_POSITION, @@ -85,6 +88,8 @@ import { CONF_VIEW_SCAN, CONF_VIEW_SCAN_ENABLED, CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS, + CONF_VIEW_SCAN_UNTRIGGER_RESET, + CONF_VIEW_SCAN_UNTRIGGER_SECONDS, CONF_VIEW_TIMEOUT_SECONDS, CONF_VIEW_UPDATE_CYCLE_CAMERA, CONF_VIEW_UPDATE_FORCE, @@ -607,18 +612,25 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ? html`
${this._renderSwitch( CONF_VIEW_SCAN_ENABLED, - frigateCardConfigDefaults.view.scan.enabled ?? true, + frigateCardConfigDefaults.view.scan.enabled, { label: localize(`config.${CONF_VIEW_SCAN_ENABLED}`), }, )} ${this._renderSwitch( CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS, - frigateCardConfigDefaults.view.scan.show_trigger_status ?? true, + frigateCardConfigDefaults.view.scan.show_trigger_status, { label: localize(`config.${CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS}`), }, )} + ${this._renderSwitch( + CONF_VIEW_SCAN_UNTRIGGER_RESET, + frigateCardConfigDefaults.view.scan.untrigger_reset, + )} + ${this._renderNumberInput(CONF_VIEW_SCAN_UNTRIGGER_SECONDS, { + default: frigateCardConfigDefaults.view.scan.untrigger_seconds, + })}
` : ''} `; diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 75659d49..95396a46 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -73,7 +73,9 @@ "scan": { "scan_mode": "Scan mode", "enabled": "Scan mode enabled", - "show_trigger_status": "Show pulsing border when triggered" + "show_trigger_status": "Show pulsing border when triggered", + "untrigger_reset": "Reset the view to default after untrigger", + "untrigger_seconds": "Seconds after inactive state change to untrigger" } }, "event_gallery": { diff --git a/src/types.ts b/src/types.ts index 0b429c85..e08b99e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -518,6 +518,8 @@ const viewConfigDefault = { scan: { enabled: false, show_trigger_status: true, + untrigger_seconds: 0, + untrigger_reset: true, }, }; const viewConfigSchema = z @@ -541,6 +543,8 @@ const viewConfigSchema = z show_trigger_status: z .boolean() .default(viewConfigDefault.scan.show_trigger_status), + untrigger_seconds: z.number().default(viewConfigDefault.scan.untrigger_seconds), + untrigger_reset: z.boolean().default(viewConfigDefault.scan.untrigger_reset), }) .default(viewConfigDefault.scan), })