Initial version of more advanced scan mode control.
This commit is contained in:
@@ -421,20 +421,43 @@ view:
|
|||||||
scan:
|
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).
|
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 |
|
| Option | Default | Overridable | Description |
|
||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `enabled` | `false` | :white_check_mark: | Whether to enable scan mode. |
|
| `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). |
|
| `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.timeout_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 `trigger_show_status` is true) but rather just prevents the actions being performed.|
|
||||||
|
| `trigger_filter_camera` | `all` | :white_check_mark: | If set to `all` the camera will be triggered regardless of which camera is currently selected, if set to `selected` the camera will only trigger if that camera is already selected.|
|
||||||
|
| `trigger_show_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). |
|
||||||
|
| `trigger_action` | Selects triggered camera in `live` view (see below) | :white_check_mark: | An action or list of actions that are executed when a camera is triggered. May be set to `null` for no action.|
|
||||||
| `untrigger_reset` | `true` | :white_check_mark: | Whether or not to reset the view to the default after untriggering. |
|
| `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. |
|
| `untrigger_seconds` | `0` | :white_check_mark: | The number of seconds to wait after all entities are inactive before untriggering. |
|
||||||
|
| `untrigger_action` | Selects default view and camera (see below) | :white_check_mark: | An action or list of actions that are executed when a camera is triggered. May bet to `null` for no action.|
|
||||||
|
|
||||||
|
##### View: Scan Mode default actions
|
||||||
|
|
||||||
|
The default `trigger_action` is:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
frigate_card_action: camera_select
|
||||||
|
triggered: true
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
frigate_card_action: live
|
||||||
|
```
|
||||||
|
|
||||||
|
The default `untrigger_action` is:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
frigate_card_action: default
|
||||||
|
```
|
||||||
|
|
||||||
### Menu Options
|
### Menu Options
|
||||||
|
|
||||||
@@ -1422,7 +1445,7 @@ Parameters for the `custom:frigate-card-conditional` element:
|
|||||||
|`download`|Download the displayed media.|
|
|`download`|Download the displayed media.|
|
||||||
|`camera_ui`|Open the Frigate UI at the configured URL.|
|
|`camera_ui`|Open the Frigate UI at the configured URL.|
|
||||||
|`fullscreen`|Toggle fullscreen.|
|
|`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). |
|
|`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. |
|
|`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. |
|
|`live_substream_select`| Perform a media player action. Takes a `camera` parameter with the [camera ID](#camera-ids) of the substream camera. |
|
||||||
@@ -1912,7 +1935,7 @@ view:
|
|||||||
dark_mode: 'off'
|
dark_mode: 'off'
|
||||||
scan:
|
scan:
|
||||||
enabled: false
|
enabled: false
|
||||||
show_trigger_status: true
|
trigger_show_status: true
|
||||||
untrigger_reset: true
|
untrigger_reset: true
|
||||||
untrigger_seconds: 0
|
untrigger_seconds: 0
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
Actions,
|
Actions,
|
||||||
ActionsConfig,
|
ActionsConfig,
|
||||||
|
ActionType,
|
||||||
FrigateCardCustomAction,
|
FrigateCardCustomAction,
|
||||||
FRIGATE_CARD_VIEW_DEFAULT,
|
FRIGATE_CARD_VIEW_DEFAULT,
|
||||||
} from '../config/types.js';
|
} from '../config/types.js';
|
||||||
import {
|
import {
|
||||||
convertActionToFrigateCardCustomAction,
|
convertActionToFrigateCardCustomAction,
|
||||||
|
frigateCardHandleAction,
|
||||||
frigateCardHandleActionConfig,
|
frigateCardHandleActionConfig,
|
||||||
getActionConfigGivenAction,
|
getActionConfigGivenAction,
|
||||||
} from '../utils/action.js';
|
} from '../utils/action.js';
|
||||||
@@ -81,16 +83,36 @@ export class ActionsManager {
|
|||||||
|
|
||||||
const frigateCardAction = convertActionToFrigateCardCustomAction(ev.detail);
|
const frigateCardAction = convertActionToFrigateCardCustomAction(ev.detail);
|
||||||
if (frigateCardAction) {
|
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.
|
* Execute a card action.
|
||||||
* @param frigateCardAction
|
* @param frigateCardAction
|
||||||
* @returns `true` if an action is executed.
|
* @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 config = this._api.getConfigManager().getConfig();
|
||||||
const mediaLoadedInfoManager = this._api.getMediaLoadedInfoManager();
|
const mediaLoadedInfoManager = this._api.getMediaLoadedInfoManager();
|
||||||
|
|
||||||
@@ -146,8 +168,12 @@ export class ActionsManager {
|
|||||||
this._api.getCardElementManager().toggleMenu();
|
this._api.getCardElementManager().toggleMenu();
|
||||||
break;
|
break;
|
||||||
case 'camera_select':
|
case 'camera_select':
|
||||||
const selectCameraID = frigateCardAction.camera;
|
const selectCameraID =
|
||||||
if (view) {
|
frigateCardAction.camera ??
|
||||||
|
(frigateCardAction.triggered
|
||||||
|
? this._api.getTriggersManager().getMostRecentlyTriggeredCameraID()
|
||||||
|
: null);
|
||||||
|
if (selectCameraID && view) {
|
||||||
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
||||||
const targetViewName =
|
const targetViewName =
|
||||||
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class HASSManager {
|
|||||||
this._api.getCardElementManager().update();
|
this._api.getCardElementManager().update();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._api.getTriggersManager().updateTriggeredCameras(oldHass);
|
this._api.getTriggersManager().updateTriggerHAState(oldHass);
|
||||||
|
|
||||||
if (this._api.getConditionsManager().hasHAStateConditions()) {
|
if (this._api.getConditionsManager().hasHAStateConditions()) {
|
||||||
this._api.getConditionsManager().setState({ state: this._hass.states });
|
this._api.getConditionsManager().setState({ state: this._hass.states });
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ export class InteractionManager {
|
|||||||
protected _reportInteraction(): void {
|
protected _reportInteraction(): void {
|
||||||
this._timer.stop();
|
this._timer.stop();
|
||||||
|
|
||||||
// Interactions reset the trigger state.
|
|
||||||
this._api.getTriggersManager().untrigger();
|
|
||||||
|
|
||||||
const timeoutSeconds = this._api.getConfigManager().getConfig()
|
const timeoutSeconds = this._api.getConfigManager().getConfig()
|
||||||
?.view.timeout_seconds;
|
?.view.timeout_seconds;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { FrigateCardCustomAction, FrigateCardViewAction } from '../config/types';
|
import { FrigateCardCustomAction, FrigateCardViewAction } from '../config/types';
|
||||||
import { createFrigateCardCameraAction, createFrigateCardSimpleAction } from '../utils/action.js';
|
import {
|
||||||
|
createFrigateCardCameraAction,
|
||||||
|
createFrigateCardSimpleAction
|
||||||
|
} from '../utils/action.js';
|
||||||
import { CardQueryStringAPI } from './types';
|
import { CardQueryStringAPI } from './types';
|
||||||
import { ViewManagerSetViewParameters } from './view-manager';
|
import { ViewManagerSetViewParameters } from './view-manager';
|
||||||
|
|
||||||
@@ -35,43 +38,6 @@ export class QueryStringManager {
|
|||||||
this._executeNonViewRelated(intent);
|
this._executeNonViewRelated(intent);
|
||||||
};
|
};
|
||||||
|
|
||||||
public generateQueryString(action: FrigateCardCustomAction): string | null {
|
|
||||||
const baseKey =
|
|
||||||
'frigate-card-action.' + (action.card_id ? `${action.card_id}.` : '');
|
|
||||||
|
|
||||||
switch (action.frigate_card_action) {
|
|
||||||
case 'camera_select':
|
|
||||||
case 'live_substream_select':
|
|
||||||
return new URLSearchParams([
|
|
||||||
[baseKey + action.frigate_card_action, action.camera],
|
|
||||||
]).toString();
|
|
||||||
case 'camera_ui':
|
|
||||||
case 'clip':
|
|
||||||
case 'clips':
|
|
||||||
case 'default':
|
|
||||||
case 'diagnostics':
|
|
||||||
case 'download':
|
|
||||||
case 'expand':
|
|
||||||
case 'image':
|
|
||||||
case 'live':
|
|
||||||
case 'menu_toggle':
|
|
||||||
case 'recording':
|
|
||||||
case 'recordings':
|
|
||||||
case 'snapshot':
|
|
||||||
case 'snapshots':
|
|
||||||
case 'timeline':
|
|
||||||
return new URLSearchParams([
|
|
||||||
[baseKey + action.frigate_card_action, ''],
|
|
||||||
]).toString();
|
|
||||||
default:
|
|
||||||
console.warn(
|
|
||||||
`Frigate card cannot convert unsupported action to query string:`,
|
|
||||||
action,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _executeViewRelated(intent: QueryStringViewIntent): void {
|
protected _executeViewRelated(intent: QueryStringViewIntent): void {
|
||||||
if (intent.view) {
|
if (intent.view) {
|
||||||
if (intent.view.default) {
|
if (intent.view.default) {
|
||||||
@@ -96,7 +62,7 @@ export class QueryStringManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intent.other?.forEach((action) =>
|
intent.other?.forEach((action) =>
|
||||||
this._api.getActionsManager().executeAction(action),
|
this._api.getActionsManager().executeFrigateAction(action),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,30 +2,44 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
|||||||
import orderBy from 'lodash-es/orderBy';
|
import orderBy from 'lodash-es/orderBy';
|
||||||
import { getHassDifferences, isTriggeredState } from '../utils/ha';
|
import { getHassDifferences, isTriggeredState } from '../utils/ha';
|
||||||
import { Timer } from '../utils/timer';
|
import { Timer } from '../utils/timer';
|
||||||
|
import { View } from '../view/view';
|
||||||
import { CardTriggersAPI } from './types';
|
import { CardTriggersAPI } from './types';
|
||||||
|
|
||||||
export class TriggersManager {
|
export class TriggersManager {
|
||||||
protected _api: CardTriggersAPI;
|
protected _api: CardTriggersAPI;
|
||||||
|
|
||||||
protected _triggers: Map<string, Date> = new Map();
|
protected _triggeredCameras: Map<string, Date> = new Map();
|
||||||
protected _untriggerTimer = new Timer();
|
protected _triggeredCameraTimers: Map<string, Timer> = new Map();
|
||||||
|
protected _triggeredState: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(api: CardTriggersAPI) {
|
constructor(api: CardTriggersAPI) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isTriggered(): boolean {
|
public getTriggeredCameraIDs(): Set<string> {
|
||||||
return !!this._triggers.size || this._untriggerTimer.isRunning();
|
return new Set(this._triggeredCameras.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateTriggeredCameras(oldHass?: HomeAssistant | null): boolean {
|
public isTriggered(): boolean {
|
||||||
if (!this._shouldTrackTriggers()) {
|
return !!this._triggeredCameras.size;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
public getMostRecentlyTriggeredCameraID(): string | null {
|
||||||
|
const sorted = orderBy(
|
||||||
|
[...this._triggeredCameras.entries()],
|
||||||
|
(entry) => entry[1].getTime(),
|
||||||
|
'desc',
|
||||||
|
);
|
||||||
|
return sorted.length ? sorted[0][0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateTriggerHAState(oldHass?: HomeAssistant | null): void {
|
||||||
|
const scanConfig = this._api.getConfigManager().getConfig()?.view.scan;
|
||||||
|
if (!scanConfig || !scanConfig.enabled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hass = this._api.getHASSManager().getHASS();
|
const hass = this._api.getHASSManager().getHASS();
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
let triggerChanges = false;
|
let triggerChanges = false;
|
||||||
|
|
||||||
const visibleCameraIDs = this._api
|
const visibleCameraIDs = this._api
|
||||||
@@ -45,88 +59,108 @@ export class TriggersManager {
|
|||||||
(entity) => !isTriggeredState(hass?.states[entity]),
|
(entity) => !isTriggeredState(hass?.states[entity]),
|
||||||
);
|
);
|
||||||
if (shouldTrigger) {
|
if (shouldTrigger) {
|
||||||
this._triggers.set(cameraID, now);
|
this._triggeredState.add(cameraID);
|
||||||
triggerChanges = true;
|
triggerChanges = true;
|
||||||
} else if (shouldUntrigger && this._triggers.has(cameraID)) {
|
} else if (shouldUntrigger && this._triggeredState.has(cameraID)) {
|
||||||
this._triggers.delete(cameraID);
|
this._triggeredState.delete(cameraID);
|
||||||
triggerChanges = true;
|
triggerChanges = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerChanges) {
|
if (triggerChanges) {
|
||||||
const targetCameraID = this._getMostRecentTrigger();
|
this._evaluateTriggers();
|
||||||
if (targetCameraID) {
|
|
||||||
this._triggerAction(targetCameraID);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
this._startUntriggerTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public untrigger(): void {
|
|
||||||
const wasTriggered = this.isTriggered();
|
|
||||||
this._triggers.clear();
|
|
||||||
this._untriggerTimer.stop();
|
|
||||||
|
|
||||||
if (wasTriggered) {
|
|
||||||
this._untriggerAction();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _triggerAction(cameraID: string): void {
|
public updateView(oldView?: View | null): void {
|
||||||
const view = this._api.getViewManager().getView();
|
if (oldView?.camera !== this._api.getViewManager().getView()?.camera) {
|
||||||
|
// If the view changes, a new camera may have been selected, which may
|
||||||
|
// mean a trigger is required (in the case that `trigger_filter_camera`
|
||||||
|
// has been set to `selected`).
|
||||||
|
this._evaluateTriggers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _evaluateTriggers(): void {
|
||||||
|
const scanConfig = this._api.getConfigManager().getConfig()?.view.scan;
|
||||||
|
if (!scanConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
for (const cameraID of this._triggeredState.keys()) {
|
||||||
if (
|
if (
|
||||||
this._isAutomatedViewUpdateAllowed() &&
|
!this._triggeredCameras.has(cameraID) &&
|
||||||
(view?.camera !== cameraID || !view?.is('live'))
|
(scanConfig.trigger_filter_camera === 'all' ||
|
||||||
|
(scanConfig.trigger_filter_camera === 'selected' &&
|
||||||
|
cameraID === this._api.getViewManager().getView()?.camera))
|
||||||
) {
|
) {
|
||||||
this._api.getViewManager().setViewByParameters({
|
this._triggeredCameras.set(cameraID, now);
|
||||||
viewName: 'live',
|
this._triggerAction();
|
||||||
cameraID: cameraID,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _untriggerAction(): void {
|
for (const cameraID of this._triggeredCameras.keys()) {
|
||||||
if (
|
if (!this._triggeredState.has(cameraID)) {
|
||||||
!this.isTriggered() &&
|
this._startUntriggerTimer(cameraID);
|
||||||
this._isAutomatedViewUpdateAllowed() &&
|
}
|
||||||
this._api.getConfigManager().getConfig()?.view.scan.untrigger_reset
|
|
||||||
) {
|
|
||||||
this._api.getViewManager().setViewDefault();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _isAutomatedViewUpdateAllowed(): boolean {
|
protected _hasAllowableInteractionState(): boolean {
|
||||||
|
const scanConfig = this._api.getConfigManager().getConfig()?.view.scan;
|
||||||
|
const hasInteraction = this._api.getInteractionManager().hasInteraction();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this._api.getConfigManager().getConfig()?.view.update_force ||
|
!!scanConfig &&
|
||||||
!this._api.getInteractionManager().hasInteraction()
|
(scanConfig.interaction_mode === 'all' ||
|
||||||
|
(scanConfig.interaction_mode === 'active' && hasInteraction) ||
|
||||||
|
(scanConfig.interaction_mode === 'inactive' && !hasInteraction))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _shouldTrackTriggers(): boolean {
|
protected _triggerAction(): void {
|
||||||
return !!this._api.getConfigManager().getConfig()?.view.scan.enabled;
|
const action = this._api.getConfigManager().getConfig()?.view.scan.trigger_action;
|
||||||
|
|
||||||
|
if (action && this._hasAllowableInteractionState()) {
|
||||||
|
this._api.getActionsManager().executeActions(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _startUntriggerTimer(): void {
|
// Must update master element to add border pulsing.
|
||||||
this._untriggerTimer.start(
|
this._api.getCardElementManager().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _untriggerAction(cameraID: string): void {
|
||||||
|
const action = this._api.getConfigManager().getConfig()?.view.scan.untrigger_action;
|
||||||
|
|
||||||
|
if (action && this._hasAllowableInteractionState()) {
|
||||||
|
this._api.getActionsManager().executeActions(action);
|
||||||
|
}
|
||||||
|
this._triggeredCameras.delete(cameraID);
|
||||||
|
this._deleteTimer(cameraID);
|
||||||
|
|
||||||
|
// Must update master element to remove border pulsing.
|
||||||
|
this._api.getCardElementManager().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _startUntriggerTimer(cameraID: string): void {
|
||||||
|
this._deleteTimer(cameraID);
|
||||||
|
|
||||||
|
const timer = new Timer();
|
||||||
|
this._triggeredCameraTimers.set(cameraID, timer);
|
||||||
|
timer.start(
|
||||||
/* istanbul ignore next: the case of config being null here cannot be
|
/* istanbul ignore next: the case of config being null here cannot be
|
||||||
reached, as there's no way to have the untrigger call happen without
|
reached, as there's no way to have the untrigger call happen without
|
||||||
a config. -- @preserve */
|
a config. -- @preserve */
|
||||||
this._api.getConfigManager().getConfig()?.view.scan.untrigger_seconds ?? 0,
|
this._api.getConfigManager().getConfig()?.view.scan.untrigger_seconds ?? 0,
|
||||||
() => {
|
() => {
|
||||||
this._untriggerAction();
|
this._untriggerAction(cameraID);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _getMostRecentTrigger(): string | null {
|
protected _deleteTimer(cameraID: string): void {
|
||||||
const sorted = orderBy(
|
this._triggeredCameraTimers.get(cameraID)?.stop();
|
||||||
[...this._triggers.entries()],
|
this._triggeredCameraTimers.delete(cameraID);
|
||||||
(entry) => entry[1].getTime(),
|
|
||||||
'desc',
|
|
||||||
);
|
|
||||||
return sorted.length ? sorted[0][0] : null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,9 @@ export interface CardActionsManagerAPI {
|
|||||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||||
getMediaPlayerManager(): MediaPlayerManager;
|
getMediaPlayerManager(): MediaPlayerManager;
|
||||||
getMessageManager(): MessageManager;
|
getMessageManager(): MessageManager;
|
||||||
getViewManager(): ViewManager;
|
|
||||||
getMicrophoneManager(): MicrophoneManager;
|
getMicrophoneManager(): MicrophoneManager;
|
||||||
|
getTriggersManager(): TriggersManager;
|
||||||
|
getViewManager(): ViewManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardAutomationsAPI {
|
export interface CardAutomationsAPI {
|
||||||
@@ -55,17 +56,17 @@ export interface CardAutomationsAPI {
|
|||||||
|
|
||||||
export interface CardAutoRefreshAPI {
|
export interface CardAutoRefreshAPI {
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getViewManager(): ViewManager;
|
|
||||||
getTriggersManager(): TriggersManager;
|
|
||||||
getInteractionManager(): InteractionManager;
|
getInteractionManager(): InteractionManager;
|
||||||
|
getTriggersManager(): TriggersManager;
|
||||||
|
getViewManager(): ViewManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardCameraAPI {
|
export interface CardCameraAPI {
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getEntityRegistryManager(): EntityRegistryManager;
|
getEntityRegistryManager(): EntityRegistryManager;
|
||||||
getResolvedMediaCache(): ResolvedMediaCache;
|
|
||||||
getHASSManager(): HASSManager;
|
getHASSManager(): HASSManager;
|
||||||
getMessageManager(): MessageManager;
|
getMessageManager(): MessageManager;
|
||||||
|
getResolvedMediaCache(): ResolvedMediaCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardCameraURLAPI {
|
export interface CardCameraURLAPI {
|
||||||
@@ -106,15 +107,15 @@ export interface CardElementAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CardExpandAPI {
|
export interface CardExpandAPI {
|
||||||
getFullscreenManager(): FullscreenManager;
|
|
||||||
getConditionsManager(): ConditionsManager;
|
|
||||||
getCardElementManager(): CardElementManager;
|
getCardElementManager(): CardElementManager;
|
||||||
|
getConditionsManager(): ConditionsManager;
|
||||||
|
getFullscreenManager(): FullscreenManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardFullscreenAPI {
|
export interface CardFullscreenAPI {
|
||||||
getCardElementManager(): CardElementManager;
|
getCardElementManager(): CardElementManager;
|
||||||
getExpandManager(): ExpandManager;
|
|
||||||
getConditionsManager(): ConditionsManager;
|
getConditionsManager(): ConditionsManager;
|
||||||
|
getExpandManager(): ExpandManager;
|
||||||
getMediaPlayerManager(): MediaPlayerManager;
|
getMediaPlayerManager(): MediaPlayerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,23 +154,23 @@ export interface CardInteractionAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CardMediaLoadedAPI {
|
export interface CardMediaLoadedAPI {
|
||||||
|
getCardElementManager(): CardElementManager;
|
||||||
getConditionsManager(): ConditionsManager;
|
getConditionsManager(): ConditionsManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getCardElementManager(): CardElementManager;
|
|
||||||
getStyleManager(): StyleManager;
|
getStyleManager(): StyleManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardMediaPlayerAPI {
|
export interface CardMediaPlayerAPI {
|
||||||
getHASSManager(): HASSManager;
|
|
||||||
getCameraManager(): CameraManager;
|
getCameraManager(): CameraManager;
|
||||||
getEntityRegistryManager(): EntityRegistryManager;
|
getEntityRegistryManager(): EntityRegistryManager;
|
||||||
|
getHASSManager(): HASSManager;
|
||||||
getMessageManager(): MessageManager;
|
getMessageManager(): MessageManager;
|
||||||
getQueryStringManager(): QueryStringManager;
|
getQueryStringManager(): QueryStringManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardMessageAPI {
|
export interface CardMessageAPI {
|
||||||
getConditionsManager(): ConditionsManager;
|
|
||||||
getCardElementManager(): CardElementManager;
|
getCardElementManager(): CardElementManager;
|
||||||
|
getConditionsManager(): ConditionsManager;
|
||||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,9 +180,9 @@ export interface CardMicrophoneAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CardQueryStringAPI {
|
export interface CardQueryStringAPI {
|
||||||
|
getActionsManager(): ActionsManager;
|
||||||
getCardElementManager(): CardElementManager;
|
getCardElementManager(): CardElementManager;
|
||||||
getViewManager(): ViewManager;
|
getViewManager(): ViewManager;
|
||||||
getActionsManager(): ActionsManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardStyleAPI {
|
export interface CardStyleAPI {
|
||||||
@@ -196,7 +197,9 @@ export interface CardStyleAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CardTriggersAPI {
|
export interface CardTriggersAPI {
|
||||||
|
getActionsManager(): ActionsManager;
|
||||||
getCameraManager(): CameraManager;
|
getCameraManager(): CameraManager;
|
||||||
|
getCardElementManager(): CardElementManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getHASSManager(): HASSManager;
|
getHASSManager(): HASSManager;
|
||||||
getInteractionManager(): InteractionManager;
|
getInteractionManager(): InteractionManager;
|
||||||
@@ -206,11 +209,12 @@ export interface CardTriggersAPI {
|
|||||||
export interface CardViewAPI {
|
export interface CardViewAPI {
|
||||||
getAutoUpdateManager(): AutoUpdateManager;
|
getAutoUpdateManager(): AutoUpdateManager;
|
||||||
getCameraManager(): CameraManager;
|
getCameraManager(): CameraManager;
|
||||||
|
getCardElementManager(): CardElementManager;
|
||||||
|
getConditionsManager(): ConditionsManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getHASSManager(): HASSManager;
|
getHASSManager(): HASSManager;
|
||||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||||
getMessageManager(): MessageManager;
|
getMessageManager(): MessageManager;
|
||||||
getStyleManager(): StyleManager;
|
getStyleManager(): StyleManager;
|
||||||
getConditionsManager(): ConditionsManager;
|
getTriggersManager(): TriggersManager;
|
||||||
getCardElementManager(): CardElementManager;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,6 +244,8 @@ export class ViewManager {
|
|||||||
camera: view.camera,
|
camera: view.camera,
|
||||||
displayMode: view.displayMode ?? undefined,
|
displayMode: view.displayMode ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._api.getTriggersManager().updateView(oldView);
|
||||||
this._api.getCardElementManager().update();
|
this._api.getCardElementManager().update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -241,7 +241,7 @@ class FrigateCard extends LitElement {
|
|||||||
const cardClasses = {
|
const cardClasses = {
|
||||||
triggered:
|
triggered:
|
||||||
!!this._controller.getTriggersManager().isTriggered() &&
|
!!this._controller.getTriggersManager().isTriggered() &&
|
||||||
!!this._config?.view.scan.show_trigger_status,
|
!!this._config?.view.scan.trigger_show_status,
|
||||||
};
|
};
|
||||||
const mainClasses = {
|
const mainClasses = {
|
||||||
main: true,
|
main: true,
|
||||||
|
|||||||
+77
-5
@@ -1,3 +1,43 @@
|
|||||||
|
// TODO: Config migration
|
||||||
|
// TODO: Editor support for scan mode changes
|
||||||
|
// TODO: Consider this:
|
||||||
|
|
||||||
|
/*
|
||||||
|
// NOW ===>
|
||||||
|
view:
|
||||||
|
scan:
|
||||||
|
enabled: true
|
||||||
|
trigger_show_status: true,
|
||||||
|
trigger_action:
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
frigate_card_action: camera_select
|
||||||
|
triggered: true
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
frigate_card_action: live
|
||||||
|
untrigger_action:
|
||||||
|
- action: custom:frigate-card-action
|
||||||
|
interaction_mode: inactive
|
||||||
|
trigger_filter_camera: all
|
||||||
|
untrigger_seconds: 0
|
||||||
|
|
||||||
|
view:
|
||||||
|
scan:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
// TO
|
||||||
|
|
||||||
|
view:
|
||||||
|
scan:
|
||||||
|
enabled: true
|
||||||
|
trigger_show_status: true
|
||||||
|
actions:
|
||||||
|
live | live-reset | none
|
||||||
|
interaction: inactive | active | all
|
||||||
|
trigger_filter_camera: all
|
||||||
|
untrigger_seconds: 0
|
||||||
|
automations:
|
||||||
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CallServiceActionConfig,
|
CallServiceActionConfig,
|
||||||
ConfirmationRestrictionConfig,
|
ConfirmationRestrictionConfig,
|
||||||
@@ -241,7 +281,8 @@ const frigateCardGeneralActionSchema = frigateCardCustomActionsBaseSchema.extend
|
|||||||
|
|
||||||
const frigateCardCameraSelectActionSchema = frigateCardCustomActionsBaseSchema.extend({
|
const frigateCardCameraSelectActionSchema = frigateCardCustomActionsBaseSchema.extend({
|
||||||
frigate_card_action: z.literal('camera_select'),
|
frigate_card_action: z.literal('camera_select'),
|
||||||
camera: z.string(),
|
camera: z.string().optional(),
|
||||||
|
triggered: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const frigateCardLiveDependencySelectActionSchema =
|
const frigateCardLiveDependencySelectActionSchema =
|
||||||
@@ -1119,17 +1160,48 @@ const viewConfigDefault = {
|
|||||||
dark_mode: 'off' as const,
|
dark_mode: 'off' as const,
|
||||||
scan: {
|
scan: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
show_trigger_status: true,
|
trigger_show_status: true,
|
||||||
|
trigger_action: [
|
||||||
|
{
|
||||||
|
action: 'custom:frigate-card-action' as const,
|
||||||
|
frigate_card_action: 'camera_select' as const,
|
||||||
|
triggered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'custom:frigate-card-action' as const,
|
||||||
|
frigate_card_action: 'live' as const,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
untrigger_action: {
|
||||||
|
action: 'custom:frigate-card-action' as const,
|
||||||
|
frigate_card_action: 'default' as const,
|
||||||
|
},
|
||||||
|
interaction_mode: 'inactive' as const,
|
||||||
|
trigger_filter_camera: 'all' as const,
|
||||||
untrigger_seconds: 0,
|
untrigger_seconds: 0,
|
||||||
untrigger_reset: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const scanSchema = z.object({
|
const scanSchema = z.object({
|
||||||
enabled: z.boolean().default(viewConfigDefault.scan.enabled),
|
enabled: z.boolean().default(viewConfigDefault.scan.enabled),
|
||||||
show_trigger_status: z.boolean().default(viewConfigDefault.scan.show_trigger_status),
|
|
||||||
|
interaction_mode: z
|
||||||
|
.enum(['all', 'inactive', 'active'])
|
||||||
|
.default(viewConfigDefault.scan.interaction_mode),
|
||||||
|
trigger_filter_camera: z
|
||||||
|
.enum(['all', 'selected'])
|
||||||
|
.default(viewConfigDefault.scan.trigger_filter_camera),
|
||||||
|
trigger_show_status: z.boolean().default(viewConfigDefault.scan.trigger_show_status),
|
||||||
|
trigger_action: actionSchema
|
||||||
|
.or(actionSchema.array())
|
||||||
|
.nullable()
|
||||||
|
.default(viewConfigDefault.scan.trigger_action),
|
||||||
|
|
||||||
|
untrigger_action: actionSchema
|
||||||
|
.or(actionSchema.array())
|
||||||
|
.nullable()
|
||||||
|
.default(viewConfigDefault.scan.untrigger_action),
|
||||||
untrigger_seconds: z.number().default(viewConfigDefault.scan.untrigger_seconds),
|
untrigger_seconds: z.number().default(viewConfigDefault.scan.untrigger_seconds),
|
||||||
untrigger_reset: z.boolean().default(viewConfigDefault.scan.untrigger_reset),
|
|
||||||
});
|
});
|
||||||
export type ScanOptions = z.infer<typeof scanSchema>;
|
export type ScanOptions = z.infer<typeof scanSchema>;
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -77,10 +77,8 @@ export const CONF_VIEW_UPDATE_FORCE = `${CONF_VIEW}.update_force` as const;
|
|||||||
export const CONF_VIEW_UPDATE_SECONDS = `${CONF_VIEW}.update_seconds` as const;
|
export const CONF_VIEW_UPDATE_SECONDS = `${CONF_VIEW}.update_seconds` as const;
|
||||||
export const CONF_VIEW_SCAN = `${CONF_VIEW}.scan` as const;
|
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_ENABLED = `${CONF_VIEW_SCAN}.enabled` as const;
|
||||||
export const CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS =
|
export const CONF_VIEW_SCAN_TRIGGER_SHOW_STATUS =
|
||||||
`${CONF_VIEW_SCAN}.show_trigger_status` as const;
|
`${CONF_VIEW_SCAN}.trigger_show_status` as const;
|
||||||
export const CONF_VIEW_SCAN_UNTRIGGER_RESET =
|
|
||||||
`${CONF_VIEW_SCAN}.untrigger_reset` as const;
|
|
||||||
export const CONF_VIEW_SCAN_UNTRIGGER_SECONDS =
|
export const CONF_VIEW_SCAN_UNTRIGGER_SECONDS =
|
||||||
`${CONF_VIEW_SCAN}.untrigger_seconds` as const;
|
`${CONF_VIEW_SCAN}.untrigger_seconds` as const;
|
||||||
|
|
||||||
|
|||||||
+4
-9
@@ -182,8 +182,7 @@ import {
|
|||||||
CONF_VIEW_DEFAULT,
|
CONF_VIEW_DEFAULT,
|
||||||
CONF_VIEW_SCAN,
|
CONF_VIEW_SCAN,
|
||||||
CONF_VIEW_SCAN_ENABLED,
|
CONF_VIEW_SCAN_ENABLED,
|
||||||
CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS,
|
CONF_VIEW_SCAN_TRIGGER_SHOW_STATUS,
|
||||||
CONF_VIEW_SCAN_UNTRIGGER_RESET,
|
|
||||||
CONF_VIEW_SCAN_UNTRIGGER_SECONDS,
|
CONF_VIEW_SCAN_UNTRIGGER_SECONDS,
|
||||||
CONF_VIEW_TIMEOUT_SECONDS,
|
CONF_VIEW_TIMEOUT_SECONDS,
|
||||||
CONF_VIEW_UPDATE_CYCLE_CAMERA,
|
CONF_VIEW_UPDATE_CYCLE_CAMERA,
|
||||||
@@ -902,16 +901,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS,
|
CONF_VIEW_SCAN_TRIGGER_SHOW_STATUS,
|
||||||
this._defaults.view.scan.show_trigger_status,
|
this._defaults.view.scan.trigger_show_status,
|
||||||
{
|
{
|
||||||
label: localize(`config.${CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS}`),
|
label: localize(`config.${CONF_VIEW_SCAN_TRIGGER_SHOW_STATUS}`),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
${this._renderSwitch(
|
|
||||||
CONF_VIEW_SCAN_UNTRIGGER_RESET,
|
|
||||||
this._defaults.view.scan.untrigger_reset,
|
|
||||||
)}
|
|
||||||
${this._renderNumberInput(CONF_VIEW_SCAN_UNTRIGGER_SECONDS, {
|
${this._renderNumberInput(CONF_VIEW_SCAN_UNTRIGGER_SECONDS, {
|
||||||
default: this._defaults.view.scan.untrigger_seconds,
|
default: this._defaults.view.scan.untrigger_seconds,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -387,7 +387,7 @@
|
|||||||
"scan": {
|
"scan": {
|
||||||
"enabled": "Scan mode enabled",
|
"enabled": "Scan mode enabled",
|
||||||
"scan_mode": "Scan mode",
|
"scan_mode": "Scan mode",
|
||||||
"show_trigger_status": "Show pulsing border when triggered",
|
"trigger_show_status": "Show pulsing border when triggered",
|
||||||
"untrigger_reset": "Reset the view to default after untrigger",
|
"untrigger_reset": "Reset the view to default after untrigger",
|
||||||
"untrigger_seconds": "Seconds after inactive state change to untrigger"
|
"untrigger_seconds": "Seconds after inactive state change to untrigger"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -383,7 +383,7 @@
|
|||||||
"scan": {
|
"scan": {
|
||||||
"enabled": "Modalità di scansione abilitata",
|
"enabled": "Modalità di scansione abilitata",
|
||||||
"scan_mode": "Modalità di scansione",
|
"scan_mode": "Modalità di scansione",
|
||||||
"show_trigger_status": "Mostra bordo pulsante quando attivato",
|
"trigger_show_status": "Mostra bordo pulsante quando attivato",
|
||||||
"untrigger_reset": "Reset the view to default after untrigger",
|
"untrigger_reset": "Reset the view to default after untrigger",
|
||||||
"untrigger_seconds": "Reimposta la vista ai valori predefiniti dopo aver annullato l'attivazione"
|
"untrigger_seconds": "Reimposta la vista ai valori predefiniti dopo aver annullato l'attivazione"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -386,7 +386,7 @@
|
|||||||
"scan": {
|
"scan": {
|
||||||
"enabled": "Modo scan ativado",
|
"enabled": "Modo scan ativado",
|
||||||
"scan_mode": "Modo scan",
|
"scan_mode": "Modo scan",
|
||||||
"show_trigger_status": "Pulsar borda quando acionado",
|
"trigger_show_status": "Pulsar borda quando acionado",
|
||||||
"untrigger_reset": "Redefinir a visualização para o padrão após desacionar",
|
"untrigger_reset": "Redefinir a visualização para o padrão após desacionar",
|
||||||
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
|
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -376,7 +376,7 @@
|
|||||||
"scan": {
|
"scan": {
|
||||||
"enabled": "Modo scan ativado",
|
"enabled": "Modo scan ativado",
|
||||||
"scan_mode": "Modo scan",
|
"scan_mode": "Modo scan",
|
||||||
"show_trigger_status": "Exibir estado do gatilho",
|
"trigger_show_status": "Exibir estado do gatilho",
|
||||||
"untrigger_reset": "Redefinir a visualização para o padrão após desacionar",
|
"untrigger_reset": "Redefinir a visualização para o padrão após desacionar",
|
||||||
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
|
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { FrigateCardMediaPlayer } from '../../src/types';
|
import { FrigateCardMediaPlayer } from '../../src/types';
|
||||||
import {
|
import {
|
||||||
convertActionToFrigateCardCustomAction,
|
convertActionToFrigateCardCustomAction,
|
||||||
|
frigateCardHandleAction,
|
||||||
frigateCardHandleActionConfig,
|
frigateCardHandleActionConfig,
|
||||||
getActionConfigGivenAction,
|
getActionConfigGivenAction,
|
||||||
} from '../../src/utils/action.js';
|
} from '../../src/utils/action.js';
|
||||||
@@ -245,7 +246,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
|
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
card_id: 'NOT_foo',
|
card_id: 'NOT_foo',
|
||||||
frigate_card_action: 'default',
|
frigate_card_action: 'default',
|
||||||
@@ -259,7 +260,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'default',
|
frigate_card_action: 'default',
|
||||||
})!,
|
})!,
|
||||||
@@ -283,7 +284,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: viewName,
|
frigate_card_action: viewName,
|
||||||
})!,
|
})!,
|
||||||
@@ -301,7 +302,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'download',
|
frigate_card_action: 'download',
|
||||||
})!,
|
})!,
|
||||||
@@ -314,7 +315,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_ui',
|
frigate_card_action: 'camera_ui',
|
||||||
})!,
|
})!,
|
||||||
@@ -327,7 +328,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'expand',
|
frigate_card_action: 'expand',
|
||||||
})!,
|
})!,
|
||||||
@@ -340,7 +341,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'fullscreen',
|
frigate_card_action: 'fullscreen',
|
||||||
})!,
|
})!,
|
||||||
@@ -353,7 +354,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'menu_toggle',
|
frigate_card_action: 'menu_toggle',
|
||||||
})!,
|
})!,
|
||||||
@@ -370,7 +371,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_select',
|
frigate_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera',
|
||||||
@@ -397,7 +398,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_select',
|
frigate_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera',
|
||||||
@@ -430,7 +431,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_select',
|
frigate_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera',
|
||||||
@@ -445,11 +446,55 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with triggered camera', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||||
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
|
vi.mocked(
|
||||||
|
api.getTriggersManager().getMostRecentlyTriggeredCameraID,
|
||||||
|
).mockReturnValue('camera');
|
||||||
|
|
||||||
|
await manager.executeFrigateAction(
|
||||||
|
createAction({
|
||||||
|
frigate_card_action: 'camera_select',
|
||||||
|
triggered: true,
|
||||||
|
})!,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
viewName: 'live',
|
||||||
|
cameraID: 'camera',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('without camera or triggered camera', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||||
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
|
vi.mocked(
|
||||||
|
api.getTriggersManager().getMostRecentlyTriggeredCameraID,
|
||||||
|
).mockReturnValue('camera');
|
||||||
|
|
||||||
|
await manager.executeFrigateAction(
|
||||||
|
createAction({
|
||||||
|
frigate_card_action: 'camera_select',
|
||||||
|
})!,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('without a current view', async () => {
|
it('without a current view', async () => {
|
||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_select',
|
frigate_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera',
|
||||||
@@ -469,7 +514,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'camera_select',
|
frigate_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera',
|
||||||
@@ -490,7 +535,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'live_substream_select',
|
frigate_card_action: 'live_substream_select',
|
||||||
camera: 'substream',
|
camera: 'substream',
|
||||||
@@ -504,7 +549,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'live_substream_off',
|
frigate_card_action: 'live_substream_off',
|
||||||
})!,
|
})!,
|
||||||
@@ -517,7 +562,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'live_substream_on',
|
frigate_card_action: 'live_substream_on',
|
||||||
})!,
|
})!,
|
||||||
@@ -531,7 +576,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'media_player',
|
frigate_card_action: 'media_player',
|
||||||
media_player_action: 'stop',
|
media_player_action: 'stop',
|
||||||
@@ -552,7 +597,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'media_player',
|
frigate_card_action: 'media_player',
|
||||||
media_player_action: 'play',
|
media_player_action: 'play',
|
||||||
@@ -576,7 +621,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'media_player',
|
frigate_card_action: 'media_player',
|
||||||
media_player_action: 'play',
|
media_player_action: 'play',
|
||||||
@@ -599,7 +644,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'media_player',
|
frigate_card_action: 'media_player',
|
||||||
media_player_action: 'play',
|
media_player_action: 'play',
|
||||||
@@ -615,7 +660,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'diagnostics',
|
frigate_card_action: 'diagnostics',
|
||||||
})!,
|
})!,
|
||||||
@@ -632,7 +677,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'microphone_mute',
|
frigate_card_action: 'microphone_mute',
|
||||||
})!,
|
})!,
|
||||||
@@ -645,7 +690,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'microphone_unmute',
|
frigate_card_action: 'microphone_unmute',
|
||||||
})!,
|
})!,
|
||||||
@@ -670,7 +715,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
})!,
|
})!,
|
||||||
@@ -684,7 +729,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'screenshot',
|
frigate_card_action: 'screenshot',
|
||||||
})!,
|
})!,
|
||||||
@@ -697,7 +742,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'display_mode_select',
|
frigate_card_action: 'display_mode_select',
|
||||||
display_mode: 'grid',
|
display_mode: 'grid',
|
||||||
@@ -715,7 +760,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
createView({ camera: 'camera.office' }),
|
createView({ camera: 'camera.office' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'ptz',
|
frigate_card_action: 'ptz',
|
||||||
ptz_action: 'left',
|
ptz_action: 'left',
|
||||||
@@ -736,7 +781,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'ptz',
|
frigate_card_action: 'ptz',
|
||||||
ptz_action: 'left',
|
ptz_action: 'left',
|
||||||
@@ -751,7 +796,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const manager = new ActionsManager(api);
|
const manager = new ActionsManager(api);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
createAction({
|
createAction({
|
||||||
frigate_card_action: 'show_ptz',
|
frigate_card_action: 'show_ptz',
|
||||||
show_ptz: true,
|
show_ptz: true,
|
||||||
@@ -768,7 +813,7 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
|
|
||||||
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||||
|
|
||||||
await manager.executeAction(
|
await manager.executeFrigateAction(
|
||||||
// Have to manually create the action (vs using `createAction()`) since
|
// Have to manually create the action (vs using `createAction()`) since
|
||||||
// it's malformed.
|
// it's malformed.
|
||||||
{
|
{
|
||||||
@@ -781,3 +826,36 @@ describe('ActionsManager.executeAction', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ActionsManager.executeActions', () => {
|
||||||
|
it('should execute actions', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const hass = createHASS();
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||||
|
|
||||||
|
const manager = new ActionsManager(api);
|
||||||
|
const action = createAction({
|
||||||
|
frigate_card_action: 'default',
|
||||||
|
})!;
|
||||||
|
manager.executeActions(action);
|
||||||
|
|
||||||
|
expect(frigateCardHandleAction).toBeCalledWith(element, hass, {}, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not execute actions without hass', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||||
|
|
||||||
|
const manager = new ActionsManager(api);
|
||||||
|
manager.executeActions(
|
||||||
|
createAction({
|
||||||
|
frigate_card_action: 'default',
|
||||||
|
})!,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ describe('HASSManager', () => {
|
|||||||
|
|
||||||
const originalHASS = createHASS();
|
const originalHASS = createHASS();
|
||||||
manager.setHASS(originalHASS);
|
manager.setHASS(originalHASS);
|
||||||
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(null);
|
expect(api.getTriggersManager().updateTriggerHAState).toBeCalledWith(null);
|
||||||
|
|
||||||
manager.setHASS(createHASS());
|
manager.setHASS(createHASS());
|
||||||
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(originalHASS);
|
expect(api.getTriggersManager().updateTriggerHAState).toBeCalledWith(originalHASS);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should handle connection state change when', () => {
|
describe('should handle connection state change when', () => {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ describe('InteractionManager', () => {
|
|||||||
|
|
||||||
manager.reportInteraction();
|
manager.reportInteraction();
|
||||||
|
|
||||||
expect(api.getTriggersManager().untrigger).toBeCalled();
|
|
||||||
expect(manager.hasInteraction()).toBeTruthy();
|
expect(manager.hasInteraction()).toBeTruthy();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ describe('MediaPlayerManager', () => {
|
|||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
vi.mocked(api.getQueryStringManager().generateQueryString).mockReturnValue('');
|
|
||||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||||
const manager = new MediaPlayerManager(api);
|
const manager = new MediaPlayerManager(api);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('QueryStringManager', () => {
|
|||||||
manager.executeAll();
|
manager.executeAll();
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ describe('QueryStringManager', () => {
|
|||||||
manager.executeAll();
|
manager.executeAll();
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||||
expect(api.getActionsManager().executeAction).toBeCalledWith({
|
expect(api.getActionsManager().executeFrigateAction).toBeCalledWith({
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
card_id: 'id',
|
card_id: 'id',
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
@@ -97,7 +97,7 @@ describe('QueryStringManager', () => {
|
|||||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ describe('QueryStringManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ describe('QueryStringManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ describe('QueryStringManager', () => {
|
|||||||
manager.executeAll();
|
manager.executeAll();
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
},
|
},
|
||||||
@@ -165,7 +165,7 @@ describe('QueryStringManager', () => {
|
|||||||
manager.executeAll();
|
manager.executeAll();
|
||||||
|
|
||||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
expect(consoleSpy).toBeCalled();
|
expect(consoleSpy).toBeCalled();
|
||||||
@@ -212,7 +212,7 @@ describe('QueryStringManager', () => {
|
|||||||
|
|
||||||
manager.executeAll();
|
manager.executeAll();
|
||||||
|
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
});
|
});
|
||||||
@@ -296,77 +296,7 @@ describe('QueryStringManager', () => {
|
|||||||
|
|
||||||
manager.executeViewRelated();
|
manager.executeViewRelated();
|
||||||
|
|
||||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should generate query string', () => {
|
|
||||||
describe('that require no arguments', () => {
|
|
||||||
it.each([
|
|
||||||
['camera_ui' as const],
|
|
||||||
['clip' as const],
|
|
||||||
['clips' as const],
|
|
||||||
['default' as const],
|
|
||||||
['diagnostics' as const],
|
|
||||||
['download' as const],
|
|
||||||
['expand' as const],
|
|
||||||
['image' as const],
|
|
||||||
['live' as const],
|
|
||||||
['menu_toggle' as const],
|
|
||||||
['recording' as const],
|
|
||||||
['recordings' as const],
|
|
||||||
['snapshot' as const],
|
|
||||||
['snapshots' as const],
|
|
||||||
['timeline' as const],
|
|
||||||
])('%s', (actionName: FrigateCardGeneralAction | FrigateCardUserSpecifiedView) => {
|
|
||||||
const manager = new QueryStringManager(createCardAPI());
|
|
||||||
expect(
|
|
||||||
manager.generateQueryString({
|
|
||||||
action: 'fire-dom-event',
|
|
||||||
frigate_card_action: actionName,
|
|
||||||
}),
|
|
||||||
).toBe(`frigate-card-action.${actionName}=`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('that require camera argument', () => {
|
|
||||||
it.each([['camera_select' as const], ['live_substream_select' as const]])(
|
|
||||||
'%s',
|
|
||||||
(actionName: 'camera_select' | 'live_substream_select') => {
|
|
||||||
const manager = new QueryStringManager(createCardAPI());
|
|
||||||
expect(
|
|
||||||
manager.generateQueryString({
|
|
||||||
action: 'fire-dom-event',
|
|
||||||
frigate_card_action: actionName,
|
|
||||||
camera: 'camera',
|
|
||||||
}),
|
|
||||||
).toBe(`frigate-card-action.${actionName}=camera`);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('that include a card_id', () => {
|
|
||||||
const manager = new QueryStringManager(createCardAPI());
|
|
||||||
expect(
|
|
||||||
manager.generateQueryString({
|
|
||||||
action: 'fire-dom-event',
|
|
||||||
frigate_card_action: 'clips',
|
|
||||||
card_id: 'card-id',
|
|
||||||
}),
|
|
||||||
).toBe(`frigate-card-action.card-id.clips=`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('that include an unsupported action', () => {
|
|
||||||
const spy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
|
||||||
const manager = new QueryStringManager(createCardAPI());
|
|
||||||
|
|
||||||
expect(
|
|
||||||
manager.generateQueryString({
|
|
||||||
action: 'fire-dom-event',
|
|
||||||
frigate_card_action: 'microphone_unmute',
|
|
||||||
}),
|
|
||||||
).toBeNull();
|
|
||||||
expect(spy).toBeCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,28 @@ import {
|
|||||||
createView,
|
createView,
|
||||||
} from '../test-utils';
|
} from '../test-utils';
|
||||||
|
|
||||||
|
const baseScanConfig: Partial<ScanOptions> = {
|
||||||
|
enabled: true,
|
||||||
|
untrigger_seconds: 10,
|
||||||
|
interaction_mode: 'inactive' as const,
|
||||||
|
trigger_filter_camera: 'all' as const,
|
||||||
|
trigger_action: [
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event' as const,
|
||||||
|
frigate_card_action: 'camera_select' as const,
|
||||||
|
triggered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event' as const,
|
||||||
|
frigate_card_action: 'live' as const,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
untrigger_action: {
|
||||||
|
action: 'fire-dom-event' as const,
|
||||||
|
frigate_card_action: 'default' as const,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Creating and mocking a trigger API is a lot of boilerplate, this convenience
|
// Creating and mocking a trigger API is a lot of boilerplate, this convenience
|
||||||
// function reduces it.
|
// function reduces it.
|
||||||
const createTriggerAPI = (options?: {
|
const createTriggerAPI = (options?: {
|
||||||
@@ -26,11 +48,7 @@ const createTriggerAPI = (options?: {
|
|||||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||||
createConfig({
|
createConfig({
|
||||||
view: {
|
view: {
|
||||||
scan: options?.config ?? {
|
scan: options?.config ?? baseScanConfig,
|
||||||
enabled: true,
|
|
||||||
untrigger_reset: true,
|
|
||||||
untrigger_seconds: 10,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -83,11 +101,28 @@ describe('TriggersManager', () => {
|
|||||||
});
|
});
|
||||||
const manager = new TriggersManager(api);
|
const manager = new TriggersManager(api);
|
||||||
|
|
||||||
manager.updateTriggeredCameras(null);
|
manager.updateTriggerHAState(null);
|
||||||
|
|
||||||
expect(manager.isTriggered()).toBeFalsy();
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not trigger without a scan config', () => {
|
||||||
|
const api = createTriggerAPI({
|
||||||
|
hassStates: hassActiveState,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||||
|
createView({
|
||||||
|
camera: 'camera_1' as const,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
||||||
|
|
||||||
|
const manager = new TriggersManager(api);
|
||||||
|
manager.updateView(null);
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should trigger and untrigger based on entity state', () => {
|
it('should trigger and untrigger based on entity state', () => {
|
||||||
const start = new Date('2023-10-01T17:14');
|
const start = new Date('2023-10-01T17:14');
|
||||||
vi.setSystemTime(start);
|
vi.setSystemTime(start);
|
||||||
@@ -96,22 +131,29 @@ describe('TriggersManager', () => {
|
|||||||
});
|
});
|
||||||
const manager = new TriggersManager(api);
|
const manager = new TriggersManager(api);
|
||||||
|
|
||||||
manager.updateTriggeredCameras(createHASS(hassInactiveState));
|
manager.updateTriggerHAState(createHASS(hassInactiveState));
|
||||||
|
|
||||||
expect(manager.isTriggered()).toBeTruthy();
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
expect(api.getActionsManager().executeActions).toBeCalledWith([
|
||||||
viewName: 'live',
|
{
|
||||||
cameraID: 'camera_1',
|
action: 'fire-dom-event' as const,
|
||||||
});
|
frigate_card_action: 'camera_select' as const,
|
||||||
|
triggered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event' as const,
|
||||||
|
frigate_card_action: 'live' as const,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||||
createHASS(hassInactiveState),
|
createHASS(hassInactiveState),
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.updateTriggeredCameras(createHASS(hassActiveState));
|
manager.updateTriggerHAState(createHASS(hassActiveState));
|
||||||
|
|
||||||
// Intentional state update with no change.
|
// Intentional state update with no change.
|
||||||
manager.updateTriggeredCameras(createHASS(hassActiveState));
|
manager.updateTriggerHAState(createHASS(hassActiveState));
|
||||||
|
|
||||||
// Will still be triggered, but untrigger timer will be running.
|
// Will still be triggered, but untrigger timer will be running.
|
||||||
expect(manager.isTriggered()).toBeTruthy();
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
@@ -120,26 +162,10 @@ describe('TriggersManager', () => {
|
|||||||
vi.runOnlyPendingTimers();
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
expect(manager.isTriggered()).toBeFalsy();
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trigger and set view if current view is wrong', () => {
|
expect(api.getActionsManager().executeActions).toBeCalledWith({
|
||||||
const api = createTriggerAPI({
|
action: 'fire-dom-event' as const,
|
||||||
hassStates: hassActiveState,
|
frigate_card_action: 'default' as const,
|
||||||
});
|
|
||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
|
||||||
createView({
|
|
||||||
// Correct camera, but wrong view.
|
|
||||||
view: 'clips',
|
|
||||||
camera: 'camera_1',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const manager = new TriggersManager(api);
|
|
||||||
manager.updateTriggeredCameras(null);
|
|
||||||
|
|
||||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
|
||||||
viewName: 'live',
|
|
||||||
cameraID: 'camera_1',
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,40 +176,196 @@ describe('TriggersManager', () => {
|
|||||||
const manager = new TriggersManager(api);
|
const manager = new TriggersManager(api);
|
||||||
expect(manager.isTriggered()).toBeFalsy();
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
manager.updateTriggeredCameras(null);
|
manager.updateTriggerHAState(null);
|
||||||
expect(manager.isTriggered()).toBeTruthy();
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should untrigger manually', () => {
|
it('should take no actions with human interactions', () => {
|
||||||
const api = createTriggerAPI({
|
const start = new Date('2023-10-01T17:14');
|
||||||
hassStates: hassActiveState,
|
|
||||||
});
|
|
||||||
const manager = new TriggersManager(api);
|
|
||||||
|
|
||||||
// Untriggering when not triggered.
|
|
||||||
manager.untrigger();
|
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
|
||||||
|
|
||||||
manager.updateTriggeredCameras(null);
|
|
||||||
expect(manager.isTriggered()).toBeTruthy();
|
|
||||||
|
|
||||||
manager.untrigger();
|
|
||||||
expect(manager.isTriggered()).toBeFalsy();
|
|
||||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take no actions when automated actions are not allowed', () => {
|
|
||||||
const api = createTriggerAPI({
|
const api = createTriggerAPI({
|
||||||
hassStates: hassActiveState,
|
hassStates: hassActiveState,
|
||||||
// Interaction present.
|
// Interaction present.
|
||||||
interaction: true,
|
interaction: true,
|
||||||
});
|
});
|
||||||
const manager = new TriggersManager(api);
|
const manager = new TriggersManager(api);
|
||||||
manager.updateTriggeredCameras(null);
|
manager.updateTriggerHAState(createHASS(hassInactiveState));
|
||||||
expect(manager.isTriggered()).toBeTruthy();
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
|
||||||
|
|
||||||
manager.untrigger();
|
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||||
|
createHASS(hassInactiveState),
|
||||||
|
);
|
||||||
|
manager.updateTriggerHAState(createHASS(hassActiveState));
|
||||||
|
|
||||||
|
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should take actions with human interactions when interaction mode is active', () => {
|
||||||
|
const start = new Date('2023-10-01T17:14');
|
||||||
|
const api = createTriggerAPI({
|
||||||
|
hassStates: hassActiveState,
|
||||||
|
// Interaction present.
|
||||||
|
interaction: true,
|
||||||
|
config: {
|
||||||
|
...baseScanConfig,
|
||||||
|
interaction_mode: 'active',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const manager = new TriggersManager(api);
|
||||||
|
manager.updateTriggerHAState(createHASS(hassInactiveState));
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
|
|
||||||
|
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||||
|
createHASS(hassInactiveState),
|
||||||
|
);
|
||||||
|
manager.updateTriggerHAState(createHASS(hassActiveState));
|
||||||
|
|
||||||
|
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should report multiple triggered cameras', () => {
|
||||||
|
const start = new Date('2023-10-01T17:14');
|
||||||
|
const bothOnState = {
|
||||||
|
'binary_sensor.one': createStateEntity({ state: 'on' }),
|
||||||
|
'binary_sensor.two': createStateEntity({ state: 'on' }),
|
||||||
|
};
|
||||||
|
const api = createTriggerAPI({
|
||||||
|
hassStates: bothOnState,
|
||||||
|
});
|
||||||
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||||
|
createStore([
|
||||||
|
{
|
||||||
|
cameraID: 'camera_1',
|
||||||
|
config: createCameraConfig({
|
||||||
|
triggers: {
|
||||||
|
entities: ['binary_sensor.one'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cameraID: 'camera_2',
|
||||||
|
config: createCameraConfig({
|
||||||
|
triggers: {
|
||||||
|
entities: ['binary_sensor.two'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const manager = new TriggersManager(api);
|
||||||
|
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
expect(manager.getMostRecentlyTriggeredCameraID()).toBeNull();
|
||||||
|
expect(manager.getTriggeredCameraIDs()).toEqual(new Set());
|
||||||
|
|
||||||
|
manager.updateTriggerHAState(null);
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
|
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_1', 'camera_2']));
|
||||||
|
|
||||||
|
// Either is the most recently triggered.
|
||||||
|
expect(['camera_1', 'camera_2']).toContain(
|
||||||
|
manager.getMostRecentlyTriggeredCameraID(),
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||||
|
createHASS({
|
||||||
|
'binary_sensor.one': createStateEntity({ state: 'off' }),
|
||||||
|
'binary_sensor.two': createStateEntity({ state: 'on' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.updateTriggerHAState(createHASS(bothOnState));
|
||||||
|
|
||||||
|
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_2']));
|
||||||
|
expect(manager.getMostRecentlyTriggeredCameraID()).toBe('camera_2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter triggers by camera', () => {
|
||||||
|
const start = new Date('2023-10-01T17:14');
|
||||||
|
const api = createTriggerAPI({
|
||||||
|
config: {
|
||||||
|
...baseScanConfig,
|
||||||
|
// Filter triggers to selected camera only.
|
||||||
|
trigger_filter_camera: 'selected' as const,
|
||||||
|
},
|
||||||
|
hassStates: hassActiveState,
|
||||||
|
});
|
||||||
|
const manager = new TriggersManager(api);
|
||||||
|
manager.updateTriggerHAState(createHASS(hassInactiveState));
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
const otherCameraSelected = createView({
|
||||||
|
camera: 'camera_SOME_OTHER_CAMERA' as const,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(otherCameraSelected);
|
||||||
|
manager.updateView(null);
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
const thisCameraSelected = createView({
|
||||||
|
camera: 'camera_1' as const,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
|
||||||
|
manager.updateView(otherCameraSelected);
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
|
|
||||||
|
// Ensure a view change to the same camera stays triggered.
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
|
||||||
|
manager.updateView(thisCameraSelected);
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not untrigger triggers by camera', () => {
|
||||||
|
const start = new Date('2023-10-01T17:14');
|
||||||
|
const api = createTriggerAPI({
|
||||||
|
config: {
|
||||||
|
...baseScanConfig,
|
||||||
|
// Filter triggers to selected camera only.
|
||||||
|
trigger_filter_camera: 'selected' as const,
|
||||||
|
},
|
||||||
|
hassStates: hassActiveState,
|
||||||
|
});
|
||||||
|
const manager = new TriggersManager(api);
|
||||||
|
manager.updateTriggerHAState(createHASS(hassInactiveState));
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
const otherCameraSelected = createView({
|
||||||
|
camera: 'camera_SOME_OTHER_CAMERA' as const,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(otherCameraSelected);
|
||||||
|
manager.updateView(null);
|
||||||
|
expect(manager.isTriggered()).toBeFalsy();
|
||||||
|
|
||||||
|
const thisCameraSelected = createView({
|
||||||
|
camera: 'camera_1' as const,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
|
||||||
|
manager.updateView(otherCameraSelected);
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
|
|
||||||
|
// Ensure a view change to the same camera stays triggered.
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
|
||||||
|
manager.updateView(thisCameraSelected);
|
||||||
|
expect(manager.isTriggered()).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -279,9 +279,25 @@ describe('config defaults', () => {
|
|||||||
default: 'live',
|
default: 'live',
|
||||||
scan: {
|
scan: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
show_trigger_status: true,
|
interaction_mode: 'inactive',
|
||||||
untrigger_reset: true,
|
trigger_show_status: true,
|
||||||
untrigger_seconds: 0,
|
untrigger_seconds: 0,
|
||||||
|
trigger_action: [
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
frigate_card_action: 'camera_select',
|
||||||
|
triggered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
frigate_card_action: 'live',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trigger_filter_camera: 'all',
|
||||||
|
untrigger_action: {
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
frigate_card_action: 'default',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
timeout_seconds: 300,
|
timeout_seconds: 300,
|
||||||
update_cycle_camera: false,
|
update_cycle_camera: false,
|
||||||
|
|||||||
+3
-3
@@ -10,10 +10,10 @@ export default defineConfig({
|
|||||||
// Thresholds will automatically be updated as coverage improves to avoid
|
// Thresholds will automatically be updated as coverage improves to avoid
|
||||||
// back-sliding.
|
// back-sliding.
|
||||||
thresholdAutoUpdate: true,
|
thresholdAutoUpdate: true,
|
||||||
statements: 71.65,
|
statements: 71.74,
|
||||||
branches: 60.62,
|
branches: 60.62,
|
||||||
functions: 72.78,
|
functions: 72.84,
|
||||||
lines: 71.54,
|
lines: 71.63,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user