First skeleton of scan mode.
This commit is contained in:
+113
-6
@@ -79,9 +79,11 @@ import { getCameraIcon, getCameraID, getCameraTitle } from './utils/camera.js';
|
|||||||
import {
|
import {
|
||||||
getEntityIcon,
|
getEntityIcon,
|
||||||
getEntityTitle,
|
getEntityTitle,
|
||||||
|
getHassDifferences,
|
||||||
homeAssistantSignPath,
|
homeAssistantSignPath,
|
||||||
homeAssistantWSRequest,
|
homeAssistantWSRequest,
|
||||||
shouldUpdateBasedOnHass,
|
isHassDifferent,
|
||||||
|
isTriggeredState,
|
||||||
sideLoadHomeAssistantElements
|
sideLoadHomeAssistantElements
|
||||||
} from './utils/ha';
|
} from './utils/ha';
|
||||||
import { getEventID } from './utils/ha/browse-media.js';
|
import { getEventID } from './utils/ha/browse-media.js';
|
||||||
@@ -169,6 +171,9 @@ export class FrigateCard extends LitElement {
|
|||||||
// Automated refreshes of the default view.
|
// Automated refreshes of the default view.
|
||||||
protected _updateTimerID: number | null = null;
|
protected _updateTimerID: number | null = null;
|
||||||
|
|
||||||
|
// Untrigger timer.
|
||||||
|
protected _untriggerTimerID: number | null = null;
|
||||||
|
|
||||||
// Information about the most recently loaded media item.
|
// Information about the most recently loaded media item.
|
||||||
protected _mediaShowInfo: MediaShowInfo | null = null;
|
protected _mediaShowInfo: MediaShowInfo | null = null;
|
||||||
|
|
||||||
@@ -191,6 +196,10 @@ export class FrigateCard extends LitElement {
|
|||||||
// Whether the card has been successfully initialized.
|
// Whether the card has been successfully initialized.
|
||||||
protected _initialized = false;
|
protected _initialized = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
protected _triggered: Date | null = null;
|
||||||
|
protected _triggers: Map<string, Date> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Home Assistant object.
|
* Set the Home Assistant object.
|
||||||
*/
|
*/
|
||||||
@@ -488,7 +497,6 @@ export class FrigateCard extends LitElement {
|
|||||||
const isValidMediaPlayer = (entity: string): boolean => {
|
const isValidMediaPlayer = (entity: string): boolean => {
|
||||||
if (entity.startsWith('media_player.')) {
|
if (entity.startsWith('media_player.')) {
|
||||||
const stateObj = this._hass?.states[entity];
|
const stateObj = this._hass?.states[entity];
|
||||||
|
|
||||||
if (stateObj && supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA)) {
|
if (stateObj && supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -775,6 +783,7 @@ export class FrigateCard extends LitElement {
|
|||||||
this._message = null;
|
this._message = null;
|
||||||
this._generateConditionState();
|
this._generateConditionState();
|
||||||
this._setLightOrDarkMode();
|
this._setLightOrDarkMode();
|
||||||
|
this._triggered = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -850,6 +859,7 @@ export class FrigateCard extends LitElement {
|
|||||||
* Called before each update.
|
* Called before each update.
|
||||||
*/
|
*/
|
||||||
protected willUpdate(): void {
|
protected willUpdate(): void {
|
||||||
|
// Side load the necessary elements if not already initialized.
|
||||||
if (!this._initialized) {
|
if (!this._initialized) {
|
||||||
sideLoadHomeAssistantElements().then((success) => {
|
sideLoadHomeAssistantElements().then((success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -859,6 +869,96 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a camera has been triggered.
|
||||||
|
* @param oldHass The old HA object.
|
||||||
|
* @returns A boolean indicating whether the camera was changed.
|
||||||
|
*/
|
||||||
|
protected _updateTriggeredCameras(oldHass: HomeAssistant): boolean {
|
||||||
|
if (!this._view) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
let changedCamera = false;
|
||||||
|
let untriggerCard = true;
|
||||||
|
|
||||||
|
for (const [camera, config] of this._cameras?.entries() ?? []) {
|
||||||
|
const triggerEntities = config?.trigger_by_entities ?? [];
|
||||||
|
const diffs = getHassDifferences(this._hass, oldHass, triggerEntities, {
|
||||||
|
stateOnly: true,
|
||||||
|
});
|
||||||
|
const shouldTrigger = diffs.some((diff) => isTriggeredState(diff.newState));
|
||||||
|
const shouldUntrigger = triggerEntities.every(
|
||||||
|
(entity) => !isTriggeredState(this._hass?.states[entity]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const priorTrigger = this._triggers.get(camera);
|
||||||
|
if (shouldTrigger) {
|
||||||
|
if (
|
||||||
|
!priorTrigger ||
|
||||||
|
(now.getTime() - priorTrigger.getTime()) / 1000 >
|
||||||
|
this._getConfig().view.scan.trigger_min_seconds
|
||||||
|
) {
|
||||||
|
this._clearUntriggerTimer();
|
||||||
|
this._triggers.set(camera, new Date());
|
||||||
|
if (this._isAutomatedViewUpdateAllowed()) {
|
||||||
|
if (!changedCamera) {
|
||||||
|
this._changeView({ view: this._view.evolve({ camera: camera }) });
|
||||||
|
changedCamera = true;
|
||||||
|
if (!this._triggered) {
|
||||||
|
this._triggered = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
untriggerCard &&= shouldUntrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._triggered && untriggerCard && !this._untriggerTimerID) {
|
||||||
|
this._untriggerTimerID = window.setInterval(
|
||||||
|
this._untriggerTimerHandler.bind(this),
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
this._getConfig().view.scan.trigger_min_seconds * 1000 -
|
||||||
|
(now.getTime() - this._triggered.getTime()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changedCamera;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the untrigger timer.
|
||||||
|
*/
|
||||||
|
protected _clearUntriggerTimer() {
|
||||||
|
if (this._untriggerTimerID) {
|
||||||
|
window.clearTimeout(this._untriggerTimerID);
|
||||||
|
this._untriggerTimerID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Untrigger the card.
|
||||||
|
*/
|
||||||
|
protected _untrigger(): void {
|
||||||
|
this._clearUntriggerTimer();
|
||||||
|
this._triggered = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for the untrigger timer.
|
||||||
|
*/
|
||||||
|
protected _untriggerTimerHandler(): void {
|
||||||
|
this._untrigger();
|
||||||
|
|
||||||
|
// Change back to the default view if the untrigger is
|
||||||
|
// timer-based/automated.
|
||||||
|
this._changeView();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the element should be updated.
|
* Determine whether the element should be updated.
|
||||||
* @param changedProps The changed properties if any.
|
* @param changedProps The changed properties if any.
|
||||||
@@ -875,9 +975,11 @@ export class FrigateCard extends LitElement {
|
|||||||
// are browsing the mini-gallery). Do not allow re-rendering from a Home
|
// are browsing the mini-gallery). Do not allow re-rendering from a Home
|
||||||
// Assistant update if there's been recent interaction (e.g. clicks on the
|
// Assistant update if there's been recent interaction (e.g. clicks on the
|
||||||
// card) or if there is media active playing.
|
// card) or if there is media active playing.
|
||||||
if (
|
if (this._updateTriggeredCameras(oldHass)) {
|
||||||
|
shouldUpdate ||= true;
|
||||||
|
} else if (
|
||||||
this._isAutomatedViewUpdateAllowed() &&
|
this._isAutomatedViewUpdateAllowed() &&
|
||||||
shouldUpdateBasedOnHass(
|
isHassDifferent(
|
||||||
this._hass,
|
this._hass,
|
||||||
oldHass,
|
oldHass,
|
||||||
this._getConfig().view.update_entities || [],
|
this._getConfig().view.update_entities || [],
|
||||||
@@ -889,7 +991,7 @@ export class FrigateCard extends LitElement {
|
|||||||
this._changeView();
|
this._changeView();
|
||||||
shouldUpdate ||= true;
|
shouldUpdate ||= true;
|
||||||
} else {
|
} else {
|
||||||
shouldUpdate ||= shouldUpdateBasedOnHass(
|
shouldUpdate ||= isHassDifferent(
|
||||||
this._hass,
|
this._hass,
|
||||||
oldHass,
|
oldHass,
|
||||||
this._getConfig().view.render_entities || [],
|
this._getConfig().view.render_entities || [],
|
||||||
@@ -1168,6 +1270,10 @@ export class FrigateCard extends LitElement {
|
|||||||
*/
|
*/
|
||||||
protected _startInteractionTimer(): void {
|
protected _startInteractionTimer(): void {
|
||||||
this._clearInteractionTimer();
|
this._clearInteractionTimer();
|
||||||
|
|
||||||
|
// Interactions reset the trigger state.
|
||||||
|
this._untrigger();
|
||||||
|
|
||||||
if (this._getConfig().view.timeout_seconds) {
|
if (this._getConfig().view.timeout_seconds) {
|
||||||
this._interactionTimerID = window.setTimeout(() => {
|
this._interactionTimerID = window.setTimeout(() => {
|
||||||
this._changeView();
|
this._changeView();
|
||||||
@@ -1189,7 +1295,7 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
if (this._getConfig().view.update_seconds) {
|
if (this._getConfig().view.update_seconds) {
|
||||||
this._updateTimerID = window.setTimeout(() => {
|
this._updateTimerID = window.setTimeout(() => {
|
||||||
if (this._isAutomatedViewUpdateAllowed()) {
|
if (!this._triggered && this._isAutomatedViewUpdateAllowed()) {
|
||||||
this._changeView();
|
this._changeView();
|
||||||
} else {
|
} else {
|
||||||
// Not allowed to update this time around, but try again at the next
|
// Not allowed to update this time around, but try again at the next
|
||||||
@@ -1391,6 +1497,7 @@ export class FrigateCard extends LitElement {
|
|||||||
const contentClasses = {
|
const contentClasses = {
|
||||||
'frigate-card-contents': true,
|
'frigate-card-contents': true,
|
||||||
absolute: padding != null,
|
absolute: padding != null,
|
||||||
|
triggered: !!this._triggered && this._getConfig().view.scan.trigger_show_border,
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = this._getMergedActions();
|
const actions = this._getMergedActions();
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
unsafeCSS
|
unsafeCSS
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||||
@@ -14,7 +14,7 @@ import defaultImage from '../images/frigate-bird-in-sky.jpg';
|
|||||||
import { localize } from '../localize/localize.js';
|
import { localize } from '../localize/localize.js';
|
||||||
import imageStyle from '../scss/image.scss';
|
import imageStyle from '../scss/image.scss';
|
||||||
import { CameraConfig, ImageViewConfig } from '../types.js';
|
import { CameraConfig, ImageViewConfig } from '../types.js';
|
||||||
import { shouldUpdateBasedOnHass } from '../utils/ha';
|
import { isHassDifferent } from '../utils/ha';
|
||||||
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
import { dispatchMediaShowEvent } from '../utils/media-info.js';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { dispatchErrorMessageEvent } from './message.js';
|
import { dispatchErrorMessageEvent } from './message.js';
|
||||||
@@ -96,7 +96,7 @@ export class FrigateCardImage extends LitElement {
|
|||||||
this._imageConfig?.mode === 'camera' &&
|
this._imageConfig?.mode === 'camera' &&
|
||||||
cameraEntity
|
cameraEntity
|
||||||
) {
|
) {
|
||||||
if (shouldUpdateBasedOnHass(this.hass, changedProps.get('hass'), [cameraEntity])) {
|
if (isHassDifferent(this.hass, changedProps.get('hass'), [cameraEntity])) {
|
||||||
// If the state of the camera entity has changed, remove the cached
|
// If the state of the camera entity has changed, remove the cached
|
||||||
// value (will be re-calculated in willUpdate). This is important to
|
// value (will be re-calculated in willUpdate). This is important to
|
||||||
// ensure a changed access token is immediately used.
|
// ensure a changed access token is immediately used.
|
||||||
|
|||||||
+10
-10
@@ -1,12 +1,12 @@
|
|||||||
import type { Corner } from '@material/mwc-menu';
|
import type { Corner } from '@material/mwc-menu';
|
||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
unsafeCSS
|
unsafeCSS
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||||
@@ -15,10 +15,10 @@ import { actionHandler } from '../action-handler-directive.js';
|
|||||||
import submenuStyle from '../scss/submenu.scss';
|
import submenuStyle from '../scss/submenu.scss';
|
||||||
import { MenuSubmenu, MenuSubmenuItem, MenuSubmenuSelect } from '../types.js';
|
import { MenuSubmenu, MenuSubmenuItem, MenuSubmenuSelect } from '../types.js';
|
||||||
import {
|
import {
|
||||||
frigateCardHasAction,
|
frigateCardHasAction,
|
||||||
stopEventFromActivatingCardWideActions
|
stopEventFromActivatingCardWideActions
|
||||||
} from '../utils/action.js';
|
} from '../utils/action.js';
|
||||||
import { refreshDynamicStateParameters, shouldUpdateBasedOnHass } from '../utils/ha';
|
import { isHassDifferent, refreshDynamicStateParameters } from '../utils/ha';
|
||||||
import { domainIcon } from '../utils/icons/domain-icon.js';
|
import { domainIcon } from '../utils/icons/domain-icon.js';
|
||||||
|
|
||||||
@customElement('frigate-card-submenu')
|
@customElement('frigate-card-submenu')
|
||||||
@@ -139,7 +139,7 @@ export class FrigateCardSubmenuSelect extends LitElement {
|
|||||||
changedProps.size != 1 ||
|
changedProps.size != 1 ||
|
||||||
!this.submenuSelect ||
|
!this.submenuSelect ||
|
||||||
(!!oldHass &&
|
(!!oldHass &&
|
||||||
shouldUpdateBasedOnHass(this.hass, oldHass, [this.submenuSelect.entity]))
|
isHassDifferent(this.hass, oldHass, [this.submenuSelect.entity]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,29 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
// Include the borders in the sizing (to keep the triggered warning pulse
|
||||||
|
// visible in fullscreen).
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
// Hide scrollbar: Firefox
|
// Hide scrollbar: Firefox
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
// Hide scrollbar: IE and Edge
|
// Hide scrollbar: IE and Edge
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
}
|
}
|
||||||
|
.frigate-card-contents.triggered {
|
||||||
|
@keyframes warning-pulse {
|
||||||
|
0% {
|
||||||
|
border: solid 1px rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border: solid 1px var(--warning-color);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border: solid 1px rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animation: warning-pulse 5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
.frigate-card-contents::-webkit-scrollbar {
|
.frigate-card-contents::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -394,6 +394,10 @@ const cameraConfigSchema = z
|
|||||||
|
|
||||||
// Set of cameras IDs upon which this camera depends.
|
// Set of cameras IDs upon which this camera depends.
|
||||||
dependent_cameras: z.string().array().optional(),
|
dependent_cameras: z.string().array().optional(),
|
||||||
|
|
||||||
|
trigger_by_motion: z.boolean().optional(),
|
||||||
|
trigger_by_occupancy: z.boolean().optional(),
|
||||||
|
trigger_by_entities: z.string().array().optional(),
|
||||||
})
|
})
|
||||||
.default(cameraConfigDefault);
|
.default(cameraConfigDefault);
|
||||||
export type CameraConfig = z.infer<typeof cameraConfigSchema>;
|
export type CameraConfig = z.infer<typeof cameraConfigSchema>;
|
||||||
@@ -498,6 +502,11 @@ const viewConfigDefault = {
|
|||||||
update_force: false,
|
update_force: false,
|
||||||
update_cycle_camera: false,
|
update_cycle_camera: false,
|
||||||
dark_mode: 'off' as const,
|
dark_mode: 'off' as const,
|
||||||
|
scan: {
|
||||||
|
enabled: false,
|
||||||
|
trigger_min_seconds: 20,
|
||||||
|
trigger_show_border: true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const viewConfigSchema = z
|
const viewConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -514,6 +523,11 @@ const viewConfigSchema = z
|
|||||||
update_entities: z.string().array().optional(),
|
update_entities: z.string().array().optional(),
|
||||||
render_entities: z.string().array().optional(),
|
render_entities: z.string().array().optional(),
|
||||||
dark_mode: z.enum(['on', 'off', 'auto']).optional(),
|
dark_mode: z.enum(['on', 'off', 'auto']).optional(),
|
||||||
|
scan: z.object({
|
||||||
|
enabled: z.boolean().default(viewConfigDefault.scan.enabled),
|
||||||
|
trigger_min_seconds: z.number().default(viewConfigDefault.scan.trigger_min_seconds),
|
||||||
|
trigger_show_border: z.boolean().default(viewConfigDefault.scan.trigger_show_border),
|
||||||
|
}).default(viewConfigDefault.scan)
|
||||||
})
|
})
|
||||||
.merge(actionsSchema)
|
.merge(actionsSchema)
|
||||||
.default(viewConfigDefault);
|
.default(viewConfigDefault);
|
||||||
|
|||||||
+65
-13
@@ -75,32 +75,75 @@ export async function homeAssistantSignPath(
|
|||||||
return hass.hassUrl(response.path);
|
return hass.hassUrl(response.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HassStateDifference {
|
||||||
|
entity: string;
|
||||||
|
oldState?: HassEntity;
|
||||||
|
newState: HassEntity;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the card should be updated based on Home Assistant changes.
|
* Get the difference between two hass objects.
|
||||||
* @param newHass The new HA object.
|
* @param newHass The new HA object.
|
||||||
* @param oldHass The old HA object.
|
* @param oldHass The old HA object.
|
||||||
* @param entities The entities to examine for changes.
|
* @param entities The entities to examine for changes.
|
||||||
* @returns A boolean indicating whether or not to allow an update.
|
* @param options An options object. stateOnly: whether or not to compare state
|
||||||
|
* strings only, firstOnly: whether or not to get the first difference only.
|
||||||
|
* @returns An array of HassStateDifference objects.
|
||||||
*/
|
*/
|
||||||
export function shouldUpdateBasedOnHass(
|
export function getHassDifferences(
|
||||||
newHass: HomeAssistant | undefined | null,
|
newHass: HomeAssistant | undefined | null,
|
||||||
oldHass: HomeAssistant | undefined | null,
|
oldHass: HomeAssistant | undefined | null,
|
||||||
entities: string[] | null,
|
entities: string[] | null,
|
||||||
): boolean {
|
options?: {
|
||||||
|
firstOnly?: boolean;
|
||||||
|
stateOnly?: boolean;
|
||||||
|
},
|
||||||
|
): HassStateDifference[] {
|
||||||
if (!newHass || !entities || !entities.length) {
|
if (!newHass || !entities || !entities.length) {
|
||||||
return false;
|
return [];
|
||||||
}
|
|
||||||
if (!oldHass) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < entities.length; i++) {
|
const differences: HassStateDifference[] = [];
|
||||||
const entity = entities[i];
|
for (const entity of entities) {
|
||||||
if (entity && oldHass.states[entity] !== newHass.states[entity]) {
|
const oldState = oldHass?.states[entity];
|
||||||
return true;
|
const newState = newHass.states[entity];
|
||||||
|
if (
|
||||||
|
(options?.stateOnly && oldState?.state !== newState.state) ||
|
||||||
|
(!options?.stateOnly && oldState !== newState)
|
||||||
|
) {
|
||||||
|
differences.push({
|
||||||
|
entity: entity,
|
||||||
|
oldState: oldState,
|
||||||
|
newState: newState,
|
||||||
|
});
|
||||||
|
if (options?.firstOnly) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return differences;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if two hass objects are different for a list of entities.
|
||||||
|
* @param newHass The new HA object.
|
||||||
|
* @param oldHass The old HA object.
|
||||||
|
* @param entities The entities to examine for changes.
|
||||||
|
* @param options An options object. stateOnly: whether or not to compare state strings only.
|
||||||
|
* @returns An array of HassStateDifference objects.
|
||||||
|
*/
|
||||||
|
export function isHassDifferent(
|
||||||
|
newHass: HomeAssistant | undefined | null,
|
||||||
|
oldHass: HomeAssistant | undefined | null,
|
||||||
|
entities: string[] | null,
|
||||||
|
options?: {
|
||||||
|
stateOnly?: boolean;
|
||||||
|
},
|
||||||
|
): boolean {
|
||||||
|
return !!getHassDifferences(newHass, oldHass, entities, {
|
||||||
|
...options,
|
||||||
|
firstOnly: true,
|
||||||
|
}).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,3 +297,12 @@ export const sideLoadHomeAssistantElements = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given state qualifies as 'triggered'.
|
||||||
|
* @param state The HASSEntity.
|
||||||
|
* @returns `true` if triggered, `false` otherwise.
|
||||||
|
*/
|
||||||
|
export const isTriggeredState = (state?: HassEntity): boolean => {
|
||||||
|
return !!state && ['on', 'open'].includes(state.state);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user