diff --git a/README.md b/README.md index 53ecfb13..3b58fa62 100644 --- a/README.md +++ b/README.md @@ -135,12 +135,12 @@ view: | Option | Default | Overridable | Description | | - | - | - | - | | `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. See [views](#views) below.| -| `timeout_seconds` | | :heavy_multiplication_x: | A numbers of seconds of inactivity after which the card will reset to the default configured view. Inactivity is defined as lack of interaction with the Frigate menu.| -| `actions` | | :heavy_multiplication_x: | Actions to use for all views, individual actions may be overriden by view-specific actions. See [actions](#actions) below.| -| `update_force` | `false` | :heavy_multiplication_x: | Whether card updates/refreshes should ignore playing media and human interaction. See [card updates](#card-updates) below for behavior and usecases.| +| `timeout_seconds` | `300` | :heavy_multiplication_x: | A numbers of seconds of inactivity after human 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. | +| `update_seconds` | `0` | :heavy_multiplication_x: | 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` | :heavy_multiplication_x: | Whether automated card updates/refreshes should ignore human interaction. See [card updates](#card-updates) below for behavior and usecases.| | `update_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the view to reset to the default. See [card updates](#card-updates) below for behavior and usecases.| | `update_cycle_camera` | `false` | :heavy_multiplication_x: | When set to `true` the selected camera is cycled on each default view change. | - +| `actions` | | :heavy_multiplication_x: | Actions to use for all views, individual actions may be overriden by view-specific actions. See [actions](#actions) below.| ### Menu Options All configuration is under: @@ -412,8 +412,6 @@ item, that has both of the following parameters set: | `conditions` | | :heavy_multiplication_x: | A set of conditions that must evaluate to `true` in order for the overrides to be applied. See [Frigate Card Conditions](#frigate-card-conditions). | | `overrides` | | :heavy_multiplication_x: |Configuration overrides to be applied. Any configuration parameter described in this documentation as 'Overridable' is supported. | - - ### Using WebRTC WebRTC support blends the use of the ultra-realtime [WebRTC live @@ -1099,31 +1097,34 @@ image: ## Card Refreshes -Three sets of flags govern when the card will automatically re-render in the +Four sets of flags govern when the card will automatically refresh in the absence of human interaction. -The following table describes the behavior these 3 flags have. +The following table describes the behavior these flags have. ### Card Update Truth Table -| `view.timeout_seconds` | `view.update_force` | `view.update_entities` | Behavior | -| :-: | :-: | :-: | - | -| Unset or `0` | *(Any value)* | Unset | Card will not automatically refresh. | -| Unset or `0` | `false` | *(Any entity)* | Card will reload default view when entity state changes, unless media is playing. | -| Unset or `0` | `true` | *(Any entity)* | Card will reload default view when entity state changes. | -| `X` seconds | `false` | Unset | Card will reload default view `X` seconds after human interaction stops, unless media is playing. | -| `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops or when entity state changes -- in both cases unless media is playing. | -| `X` seconds | `true` | Unset | Card will reload default view every `X` seconds. | -| `X` seconds | `true` | *(Any entity)* | Card will reload default view every `X` seconds or when entity state changes. | +| `view.update_seconds` | `view.timeout_seconds` | `view.update_force` | `view.update_entities` | Behavior | +| :-: | :-: | :-: | :-: | - | +| `0` | `0` | *(Any value)* | Unset | Card will not automatically refresh. | +| `0` | `0` | *(Any value)* | *(Any entity)* | Card will reload default view when entity state changes. | +| `0` | `X` seconds | *(Any value)* | Unset | Card will reload default view `X` seconds after human interaction stops. | +| `0` | `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, or when entity state changes (as long as human interaction has not occurred in the last `X` seconds). | +| `0` | `X` seconds | `true` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops or when entity state changes. | +| `Y` seconds | `0` | *(Any value)* | Unset | Card will reload default view every `Y` seconds. | +| `Y` seconds | `0` | *(Any value)* | *(Any entity)* | Card will reload default view every `Y` seconds, or whenever entity state changes. | +| `Y` seconds | `X` seconds | `false` | Unset | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds (as long as there hasn't been human interaction in the last `X` seconds). | +| `Y` seconds | `X` seconds | `false` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds or whenever entity state changes (in both cases -- as long as there hasn't been human interaction in the last `X` seconds). | +| `Y` seconds | `X` seconds | `true` | Unset | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds. | +| `Y` seconds | `X` seconds | `true` | *(Any entity)* | Card will reload default view `X` seconds after human interaction stops, and every `Y` seconds or whenever entity state changes. | ### Usecases For Automated Refreshes - * Refreshing the `live` thumbnails periodically. + * Refreshing the `live` thumbnails every 30 seconds. ```yaml view: default: live - timeout_seconds: 30 - force: true + update_seconds: 30 ``` * Using `clip` or `snapshot` as the default view (for the most recent clip or snapshot respectively) and having the card automatically refresh (to fetch a @@ -1135,6 +1136,19 @@ view: update_entities: - binary_sensor.office_person_motion ``` + * Cycle the live view of the camera every 60 seconds +```yaml +view: + update_cycle_camera: true + update_seconds: 60 +``` + * Return to the most recent clip of the default camera 30 seconds after human + interaction with the card stops. +```yaml +view: + default: clip + timeout_seconds: 30 +``` ## Troubleshooting diff --git a/src/card.ts b/src/card.ts index 05e5e336..b230f392 100644 --- a/src/card.ts +++ b/src/card.ts @@ -151,11 +151,12 @@ export class FrigateCard extends LitElement { @query('frigate-card-elements') _elements?: FrigateCardElements; - // Human interaction timer ID. + // Human interaction timer ("screensaver" functionality, return to default + // view after human interaction). protected _interactionTimerID: number | null = null; - // Whether or not media is actively playing (live or clip). - protected _mediaPlaying = false; + // Automated refreshes of the default view. + protected _updateTimerID: number | null = null; // Information about the most recently loaded media item. protected _mediaShowInfo: MediaShowInfo | null = null; @@ -619,10 +620,6 @@ export class FrigateCard extends LitElement { this._cameras = undefined; this._view = undefined; - if (this._getConfig().view.update_force) { - // If update force is enabled, start a timer right away. - this._resetInteractionTimer(); - } this._changeView(); } @@ -640,6 +637,7 @@ export class FrigateCard extends LitElement { } if (args?.view === undefined) { + // Load the default view. let camera = this._view?.camera; if (this._cameras?.size) { if (!camera) { @@ -658,6 +656,14 @@ export class FrigateCard extends LitElement { camera: camera, }); this._generateConditionState(); + + // The default view has been loaded, so can abandon any running + // 'screensaver' timer. + this._clearInteractionTimer(); + + // Restart the update timer, so the default view is refreshed at a fixed + // interval from now (if so configured). + this._startUpdateTimer(); } } else { this._view = args.view; @@ -692,8 +698,7 @@ export class FrigateCard extends LitElement { // Assistant update if there's been recent interaction (e.g. clicks on the // card) or if there is media active playing. if ( - (this._getConfig().view.update_force || - !(this._interactionTimerID && this._mediaPlaying)) && + this._isAutomatedViewUpdateAllowed() && shouldUpdateBasedOnHass( this._hass, oldHass, @@ -701,9 +706,8 @@ export class FrigateCard extends LitElement { ) ) { // If entities being monitored have changed then reset the view to the - // default and allow a re-render. Note that as per the Lit lifecycle, - // the setting of the view itself will not trigger an *additional* - // re-render here. + // default. Note that as per the Lit lifecycle, the setting of the view + // itself will not trigger an *additional* re-render here. this._changeView(); return true; } @@ -891,25 +895,66 @@ export class FrigateCard extends LitElement { ) { handleAction(node, this._hass as HomeAssistant, config, ev.detail.action); } - this._resetInteractionTimer(); + + // Set the 'screensaver' timer. + this._startInteractionTimer(); } - protected _resetInteractionTimer(): void { + /** + * Clear the human interaction ('screensaver') timer. + */ + protected _clearInteractionTimer(): void { + if (this._interactionTimerID) { + window.clearTimeout(this._interactionTimerID); + this._interactionTimerID = null; + } + } + + /** + * Start the human interaction ('screensaver') timer to reset the view to + * default `view.timeout_seconds` after human interaction. + */ + protected _startInteractionTimer(): void { + this._clearInteractionTimer(); if (this._getConfig().view.timeout_seconds) { - if (this._interactionTimerID) { - window.clearTimeout(this._interactionTimerID); - } this._interactionTimerID = window.setTimeout(() => { - this._interactionTimerID = null; this._changeView(); - if (this._getConfig().view.update_force) { - // If force is enabled, the timer just resets and starts over. - this._resetInteractionTimer(); - } }, this._getConfig().view.timeout_seconds * 1000); } } + /** + * Set the update timer to trigger an update refresh every + * `view.update_seconds`. + */ + protected _startUpdateTimer(): void { + if (this._updateTimerID) { + window.clearTimeout(this._updateTimerID); + this._updateTimerID = null; + } + if (this._getConfig().view.update_seconds) { + this._updateTimerID = window.setTimeout(() => { + if (this._isAutomatedViewUpdateAllowed()) { + this._changeView(); + } else { + // Not allowed to update this time around, but try again at the next + // interval. + this._startUpdateTimer(); + } + }, this._getConfig().view.update_seconds * 1000); + } + } + + /** + * Determine if an automated view update is allowed. + * @returns `true` if it's allowed, `false` otherwise. + */ + protected _isAutomatedViewUpdateAllowed(): boolean { + return ( + this._getConfig().view.update_force || !this._interactionTimerID + ); + } + /** * Render the card menu. * @returns A rendered template. @@ -929,20 +974,6 @@ export class FrigateCard extends LitElement { `; } - /** - * Handler for media play event. - */ - protected _playHandler(): void { - this._mediaPlaying = true; - } - - /** - * Handler for media pause event. - */ - protected _pauseHandler(): void { - this._mediaPlaying = false; - } - /** * Set the message to display and trigger an update. * @param message The message to display. @@ -1115,8 +1146,6 @@ export class FrigateCard extends LitElement { @frigate-card:message=${this._messageHandler} @frigate-card:change-view=${this._changeViewHandler} @frigate-card:media-show=${this._mediaShowHandler} - @frigate-card:pause=${this._pauseHandler} - @frigate-card:play=${this._playHandler} > ${this._getConfig().menu.mode == 'above' ? this._renderMenu() : ''}