Autodetect motion and occupancy sensors.

This commit is contained in:
Dermot Duffy
2022-05-21 11:23:31 -07:00
parent 4a40e4a943
commit c8d9e89b78
10 changed files with 358 additions and 80 deletions
+145 -43
View File
@@ -48,23 +48,21 @@ import './patches/ha-camera-stream.js';
import './patches/ha-hls-player.js'; import './patches/ha-hls-player.js';
import './patches/ha-web-rtc-player.ts'; import './patches/ha-web-rtc-player.ts';
import cardStyle from './scss/card.scss'; import cardStyle from './scss/card.scss';
import type {
Entity,
ExtendedHomeAssistant,
FrigateCardConfig,
MediaShowInfo,
MenuButton,
Message
} from './types.js';
import { import {
Actions, Actions,
ActionType, ActionType,
CameraConfig, CameraConfig,
entitySchema, EntityList,
ExtendedEntity,
ExtendedHomeAssistant,
FrigateCardConfig,
frigateCardConfigSchema, frigateCardConfigSchema,
FrigateCardCustomAction, FrigateCardCustomAction,
FrigateCardView, FrigateCardView,
FRIGATE_CARD_VIEWS_USER_SPECIFIED, FRIGATE_CARD_VIEWS_USER_SPECIFIED,
MediaShowInfo,
MenuButton,
Message,
RawFrigateCardConfig RawFrigateCardConfig
} from './types.js'; } from './types.js';
import { import {
@@ -81,15 +79,20 @@ import {
getEntityTitle, getEntityTitle,
getHassDifferences, getHassDifferences,
homeAssistantSignPath, homeAssistantSignPath,
homeAssistantWSRequest,
isHassDifferent, isHassDifferent,
isTriggeredState, 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';
import {
ExtendedEntityCache,
getAllEntities,
getExtendedEntities,
getExtendedEntity
} from './utils/ha/entity-registry.js';
import { ResolvedMediaCache } from './utils/ha/resolved-media.js';
import { supportsFeature } from './utils/ha/update.js'; import { supportsFeature } from './utils/ha/update.js';
import { isValidMediaShowInfo } from './utils/media-info.js'; import { isValidMediaShowInfo } from './utils/media-info.js';
import { ResolvedMediaCache } from './utils/resolved-media.js';
import { View } from './view.js'; import { View } from './view.js';
/** A note on media callbacks: /** A note on media callbacks:
@@ -579,23 +582,143 @@ export class FrigateCard extends LitElement {
} }
} }
/**
* Get the motion sensor entity for a given camera.
* @param cache The ExtendedEntityCache of entity registry information.
* @param cameraConfig The camera config in question.
* @returns The entity id of the motion sensor or null.
*/
protected _getMotionSensor(
cache: ExtendedEntityCache,
cameraConfig: CameraConfig,
): string | null {
if (cameraConfig.camera_name) {
return (
cache.getMatch(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:motion_sensor:${cameraConfig.zone || cameraConfig.camera_name}`,
),
),
)?.entity_id ?? null
);
}
return null;
}
/**
* Get the occupancy sensor entity for a given camera.
* @param cache The ExtendedEntityCache of entity registry information.
* @param cameraConfig The camera config in question.
* @returns The entity id of the occupancy sensor or null.
*/
protected _getOccupancySensor(
cache: ExtendedEntityCache,
cameraConfig: CameraConfig,
): string | null {
if (cameraConfig.camera_name) {
return (
cache.getMatch(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:occupancy_sensor:${cameraConfig.zone || cameraConfig.camera_name}_${
cameraConfig.label || 'all'
}`,
),
),
)?.entity_id ?? null
);
}
return null;
}
/** /**
* Fully load the configured cameras. * Fully load the configured cameras.
*/ */
protected async _loadCameras(): Promise<void> { protected async _loadCameras(): Promise<void> {
if (!this._hass) {
return;
}
const cache = new ExtendedEntityCache();
let entityList: EntityList | undefined;
try {
entityList = await getAllEntities(this._hass);
} catch (e) {
console.error(e, (e as Error).stack);
}
const cameras: Map<string, CameraConfig> = new Map(); const cameras: Map<string, CameraConfig> = new Map();
let errorFree = true; let errorFree = true;
const addCameraConfig = async (config: CameraConfig) => { const addCameraConfig = async (config: CameraConfig) => {
if (!config.camera_name && config.camera_entity) { if (!this._hass) {
const resolvedName = await this._getFrigateCameraNameFromEntity( return;
config.camera_entity, }
);
let entity: ExtendedEntity | null = null;
if (config.camera_entity) {
try {
entity = await getExtendedEntity(this._hass, config.camera_entity, cache);
} catch (e) {
console.error(e, (e as Error).stack);
}
}
if (!config.camera_name && entity) {
const resolvedName = this._getFrigateCameraNameFromEntity(entity);
if (resolvedName) { if (resolvedName) {
config.camera_name = resolvedName; config.camera_name = resolvedName;
} }
} }
if (entity && entityList) {
// Try to find the correct entities for the motion & occupancy sensors.
// We know they are binary_sensors, and that they'll have the same
// config entry ID as the camera. Searching via unique_id ensures this
// search still works if the user renames the entity_id.
const binarySensorEntities = entityList.filter(
(ent) =>
ent.config_entry_id === entity?.config_entry_id &&
!ent.disabled_by &&
ent.entity_id.startsWith('binary_sensor.'),
);
try {
await getExtendedEntities(
this._hass,
binarySensorEntities.map((ent) => ent.entity_id),
cache,
);
} catch(e) {
console.error(e, (e as Error).stack);
}
if (config.trigger_by_motion) {
const motionEntity = this._getMotionSensor(cache, config);
if (motionEntity) {
config.trigger_by_entities.push(motionEntity);
}
}
if (config.trigger_by_occupancy) {
const occupancyEntity = this._getOccupancySensor(cache, config);
if (occupancyEntity) {
config.trigger_by_entities.push(occupancyEntity);
}
}
// TODO: Remove this auto-detection information.
console.info(
`Trigger entities sensor for ${entity.entity_id} are ${JSON.stringify(
config.trigger_by_entities,
)}`,
);
}
config.trigger_by_entities = [...new Set(config.trigger_by_entities)];
const id = getCameraID(config); const id = getCameraID(config);
if (!id) { if (!id) {
this._setMessageAndUpdate({ this._setMessageAndUpdate({
@@ -649,37 +772,16 @@ export class FrigateCard extends LitElement {
} }
/** /**
* Get the Frigate camera name from an entity name. * Get the Frigate camera name from an entity.
* @returns The Frigate camera name or null if unavailable. * @returns The Frigate camera name or null if unavailable.
*/ */
protected async _getFrigateCameraNameFromEntity( protected _getFrigateCameraNameFromEntity(entity: ExtendedEntity): string | null {
entity: string, if (entity.unique_id && entity.platform === 'frigate') {
): Promise<string | null> { const match = entity.unique_id.match(/:camera:(?<camera>[^:]+)$/);
if (!this._hass) { if (match && match.groups) {
return null; return match.groups['camera'];
}
// Find entity unique_id in registry.
const request = {
type: 'config/entity_registry/get',
entity_id: entity,
};
try {
const entityResult = await homeAssistantWSRequest<Entity>(
this._hass,
entitySchema,
request,
);
if (entityResult && entityResult.platform == 'frigate') {
const match = entityResult.unique_id.match(/:camera:(?<camera>[^:]+)$/);
if (match && match.groups) {
return match.groups['camera'];
}
} }
} catch (e: unknown) {
// Pass.
} }
return null; return null;
} }
@@ -1498,7 +1600,7 @@ export class FrigateCard extends LitElement {
container: true, container: true,
outer: true, outer: true,
triggered: !!this._triggered && this._getConfig().view.scan.trigger_show_border, triggered: !!this._triggered && this._getConfig().view.scan.trigger_show_border,
} };
const contentClasses = { const contentClasses = {
'frigate-card-contents': true, 'frigate-card-contents': true,
+2 -1
View File
@@ -1090,7 +1090,8 @@ export class FrigateCardLiveJSMPEG extends LitElement {
if (!this.cameraConfig?.camera_name) { if (!this.cameraConfig?.camera_name) {
return dispatchErrorMessageEvent( return dispatchErrorMessageEvent(
this, this,
localize('error.no_camera_name') + `: ${JSON.stringify(this.cameraConfig)}`, localize('error.no_camera_name'),
this.cameraConfig,
); );
} }
+1 -1
View File
@@ -37,7 +37,7 @@ import {
overrideMultiBrowseMediaQueryParameters overrideMultiBrowseMediaQueryParameters
} from '../utils/ha/browse-media.js'; } from '../utils/ha/browse-media.js';
import { createMediaShowInfo } from '../utils/media-info.js'; import { createMediaShowInfo } from '../utils/media-info.js';
import { ResolvedMediaCache, resolveMedia } from '../utils/resolved-media.js'; import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
import { View } from '../view.js'; import { View } from '../view.js';
import { AutoMediaPlugin } from './embla-plugins/automedia.js'; import { AutoMediaPlugin } from './embla-plugins/automedia.js';
import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js'; import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js';
+11 -3
View File
@@ -23,6 +23,12 @@ export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER =
`${CONF_CAMERAS}.#.live_provider` as const; `${CONF_CAMERAS}.#.live_provider` as const;
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS = export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS =
`${CONF_CAMERAS}.#.dependent_cameras` as const; `${CONF_CAMERAS}.#.dependent_cameras` as const;
export const CONF_CAMERAS_ARRAY_TRIGGER_BY_MOTION =
`${CONF_CAMERAS}.#.trigger_by_motion` as const;
export const CONF_CAMERAS_ARRAY_TRIGGER_BY_OCCUPANCY =
`${CONF_CAMERAS}.#.trigger_by_occupancy` as const;
export const CONF_CAMERAS_ARRAY_TRIGGER_BY_ENTITIES =
`${CONF_CAMERAS}.#.trigger_by_entities` as const;
export const CONF_VIEW = 'view' as const; export const CONF_VIEW = 'view' as const;
export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const; export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const;
@@ -35,8 +41,10 @@ export const CONF_VIEW_UPDATE_ENTITIES = `${CONF_VIEW}.update_entities` 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_TRIGGER_MIN_SECONDS = `${CONF_VIEW_SCAN}.trigger_min_seconds` as const; export const CONF_VIEW_SCAN_TRIGGER_MIN_SECONDS =
export const CONF_VIEW_SCAN_TRIGGER_SHOW_BORDER = `${CONF_VIEW_SCAN}.trigger_show_border` as const; `${CONF_VIEW_SCAN}.trigger_min_seconds` as const;
export const CONF_VIEW_SCAN_TRIGGER_SHOW_BORDER =
`${CONF_VIEW_SCAN}.trigger_show_border` as const;
export const CONF_EVENT_GALLERY = 'event_gallery' as const; export const CONF_EVENT_GALLERY = 'event_gallery' as const;
export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS = export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS =
@@ -146,4 +154,4 @@ export const CONF_DIMENSIONS_ASPECT_RATIO_MODE =
export const CONF_OVERRIDES = 'overrides' as const; export const CONF_OVERRIDES = 'overrides' as const;
// Taken from https://github.dev/home-assistant/frontend/blob/b5861869e39290fd2e15737e89571dfc543b3ad3/src/data/media-player.ts#L93 // Taken from https://github.dev/home-assistant/frontend/blob/b5861869e39290fd2e15737e89571dfc543b3ad3/src/data/media-player.ts#L93
export const MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA = 131072; export const MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA = 131072;
+24 -21
View File
@@ -21,6 +21,9 @@ import {
CONF_CAMERAS_ARRAY_LABEL, CONF_CAMERAS_ARRAY_LABEL,
CONF_CAMERAS_ARRAY_LIVE_PROVIDER, CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
CONF_CAMERAS_ARRAY_TITLE, CONF_CAMERAS_ARRAY_TITLE,
CONF_CAMERAS_ARRAY_TRIGGER_BY_ENTITIES,
CONF_CAMERAS_ARRAY_TRIGGER_BY_MOTION,
CONF_CAMERAS_ARRAY_TRIGGER_BY_OCCUPANCY,
CONF_CAMERAS_ARRAY_URL, CONF_CAMERAS_ARRAY_URL,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY, CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL,
@@ -103,7 +106,7 @@ import {
} from './types.js'; } from './types.js';
import { arrayMove } from './utils/basic.js'; import { arrayMove } from './utils/basic.js';
import { getCameraID, getCameraTitle } from './utils/camera.js'; import { getCameraID, getCameraTitle } from './utils/camera.js';
import { sideLoadHomeAssistantElements } from './utils/ha'; import { getEntitiesFromHASS, sideLoadHomeAssistantElements } from './utils/ha';
const MENU_BUTTONS = 'buttons'; const MENU_BUTTONS = 'buttons';
const MENU_CAMERAS = 'cameras'; const MENU_CAMERAS = 'cameras';
@@ -403,20 +406,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
} }
} }
protected _getEntities(domain: string): string[] {
if (!this.hass) {
return [];
}
const entities = Object.keys(this.hass.states).filter(
(eid) => eid.substr(0, eid.indexOf('.')) === domain,
);
entities.sort();
// Add a blank entry to unset a selection.
entities.unshift('');
return entities;
}
/** /**
* Render an option set header * Render an option set header
* @param optionSetName The name of the EditorOptionsSet. * @param optionSetName The name of the EditorOptionsSet.
@@ -615,9 +604,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
.key=${true} .key=${true}
> >
<ha-icon .icon=${'mdi:target-account'}></ha-icon> <ha-icon .icon=${'mdi:target-account'}></ha-icon>
<span <span>${localize(`config.${CONF_VIEW_SCAN}.scan_mode`)}</span>
>${localize(`config.${CONF_VIEW_SCAN}.scan_mode`)}</span
>
</div> </div>
${this._expandedMenus[MENU_VIEW_SCAN] ${this._expandedMenus[MENU_VIEW_SCAN]
? html` <div class="values"> ? html` <div class="values">
@@ -743,6 +730,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
protected _renderCamera( protected _renderCamera(
cameras: RawFrigateCardConfigArray, cameras: RawFrigateCardConfigArray,
cameraIndex: number, cameraIndex: number,
entities: string[],
addNewCamera?: boolean, addNewCamera?: boolean,
): TemplateResult | void { ): TemplateResult | void {
const liveProviders: EditorSelectOption[] = [ const liveProviders: EditorSelectOption[] = [
@@ -891,6 +879,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
multiple: true, multiple: true,
}, },
)} )}
${this._renderSwitch(
getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGER_BY_OCCUPANCY, cameraIndex),
frigateCardConfigDefaults.cameras.trigger_by_occupancy,
)}
${this._renderSwitch(
getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGER_BY_MOTION, cameraIndex),
frigateCardConfigDefaults.cameras.trigger_by_motion,
)}
${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGER_BY_ENTITIES, cameraIndex),
entities,
{
multiple: true,
},
)}
</div>` </div>`
: ``} : ``}
`; `;
@@ -978,7 +981,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
} }
const defaults = frigateCardConfigDefaults; const defaults = frigateCardConfigDefaults;
const entities = getEntitiesFromHASS(this.hass);
const cameras = (getConfigValue(this._config, CONF_CAMERAS) || const cameras = (getConfigValue(this._config, CONF_CAMERAS) ||
[]) as RawFrigateCardConfigArray; []) as RawFrigateCardConfigArray;
@@ -1007,8 +1010,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('cameras')} ${this._renderOptionSetHeader('cameras')}
${this._expandedMenus[MENU_OPTIONS] === 'cameras' ${this._expandedMenus[MENU_OPTIONS] === 'cameras'
? html` <div class="submenu"> ? html` <div class="submenu">
${cameras.map((_, index) => this._renderCamera(cameras, index))} ${cameras.map((_, index) => this._renderCamera(cameras, index, entities))}
${this._renderCamera(cameras, cameras.length, true)} ${this._renderCamera(cameras, cameras.length, entities, true)}
</div>` </div>`
: ''} : ''}
${this._renderOptionSetHeader('view')} ${this._renderOptionSetHeader('view')}
+3
View File
@@ -27,6 +27,9 @@
"frigate-jsmpeg": "Frigate JSMpeg", "frigate-jsmpeg": "Frigate JSMpeg",
"webrtc-card": "WebRTC Card (i.e. AlexxIT's WebRTC Card)" "webrtc-card": "WebRTC Card (i.e. AlexxIT's WebRTC Card)"
}, },
"trigger_by_entities": "Trigger from other entities",
"trigger_by_motion": "Trigger by auto-detecting the motion sensor",
"trigger_by_occupancy": "Trigger by auto-detecting the occupancy sensor",
"webrtc_card": { "webrtc_card": {
"entity": "WebRTC Card Camera Entity (Not a Frigate camera)", "entity": "WebRTC Card Camera Entity (Not a Frigate camera)",
"url": "WebRTC Card Camera URL" "url": "WebRTC Card Camera URL"
+17 -4
View File
@@ -364,6 +364,9 @@ const customSchema = z
export const cameraConfigDefault = { export const cameraConfigDefault = {
client_id: 'frigate' as const, client_id: 'frigate' as const,
live_provider: 'auto' as const, live_provider: 'auto' as const,
trigger_by_motion: true,
trigger_by_occupancy: true,
trigger_by_entities: [],
}; };
const webrtcCardCameraConfigSchema = z.object({ const webrtcCardCameraConfigSchema = z.object({
entity: z.string().optional(), entity: z.string().optional(),
@@ -395,9 +398,9 @@ 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_motion: z.boolean().default(cameraConfigDefault.trigger_by_motion),
trigger_by_occupancy: z.boolean().optional(), trigger_by_occupancy: z.boolean().default(cameraConfigDefault.trigger_by_occupancy),
trigger_by_entities: z.string().array().optional(), trigger_by_entities: z.string().array().default(cameraConfigDefault.trigger_by_entities),
}) })
.default(cameraConfigDefault); .default(cameraConfigDefault);
export type CameraConfig = z.infer<typeof cameraConfigSchema>; export type CameraConfig = z.infer<typeof cameraConfigSchema>;
@@ -1263,8 +1266,18 @@ export const signedPathSchema = z.object({
export type SignedPath = z.infer<typeof signedPathSchema>; export type SignedPath = z.infer<typeof signedPathSchema>;
export const entitySchema = z.object({ export const entitySchema = z.object({
config_entry_id: z.string().nullable(),
disabled_by: z.string().nullable(),
entity_id: z.string(), entity_id: z.string(),
unique_id: z.string(),
platform: z.string(), platform: z.string(),
}); });
export type Entity = z.infer<typeof entitySchema>; export type Entity = z.infer<typeof entitySchema>;
export const extendedEntitySchema = entitySchema.extend({
// Extended entity results.
unique_id: z.string().optional(),
})
export type ExtendedEntity = z.infer<typeof extendedEntitySchema>;
export const entityListSchema = entitySchema.array();
export type EntityList = z.infer<typeof entityListSchema>;
+109
View File
@@ -0,0 +1,109 @@
import { HomeAssistant } from 'custom-card-helpers';
import { homeAssistantWSRequest } from '.';
import {
Entity,
EntityList,
entityListSchema,
ExtendedEntity,
extendedEntitySchema
} from '../../types.js';
export class ExtendedEntityCache {
protected _cache: Map<string, ExtendedEntity> = new Map();
/**
* Determine if the cache has a given entity_id.
* @param id
* @returns `true` if the id is in the cache, `false` otherwise.
*/
public has(id: string): boolean {
return this._cache.has(id);
}
/**
* Get the first value that returns true for the given predicate.
* @param func A callback function that returns a boolean.
* @returns The first matching value.
*/
public getMatch(func: (arg: ExtendedEntity) => boolean): ExtendedEntity | null {
return [...this._cache.values()].find(func) ?? null;
}
/**
* Get entity information given an id.
* @param id The entity id.
* @returns The `ExtendedEntity` for this id.
*/
public get(id: string): ExtendedEntity | undefined {
return this._cache.get(id);
}
/**
* Add a given ExtendedEntity to the cache.
* @param extendedEntity
*/
public set(extendedEntity: ExtendedEntity): void {
this._cache.set(extendedEntity.entity_id, extendedEntity);
}
}
/**
* Get the extended entity information for an entity. May throw.
* @param hass The Home Assistant object.
* @param entity The entity id.
* @param cache An optional ExtendedEntityCache.
* @returns The ExtendedEntity information.
*/
export const getExtendedEntity = async (
hass: HomeAssistant,
entity: string,
cache?: ExtendedEntityCache,
): Promise<ExtendedEntity> => {
const cachedValue = cache ? cache.get(entity) : undefined;
if (cachedValue) {
return cachedValue;
}
const result = await homeAssistantWSRequest<ExtendedEntity>(
hass,
extendedEntitySchema,
{
type: 'config/entity_registry/get',
entity_id: entity,
},
);
if (cache) {
cache.set(result);
}
return result;
};
/**
* Get the extended entity information for an array of entities.
* @param hass The Home Assistant object.
* @param entities An array of entity ids.
* @param cache An optional ExtendedEntityCache.
* @returns A map of entity id to ExtendedEntity objects.
*/
export const getExtendedEntities = async (
hass: HomeAssistant,
entities: string[],
cache?: ExtendedEntityCache,
): Promise<Map<string, Entity>> => {
const output: Map<string, Entity> = new Map();
const _storeExtendedEntity = async (entity: string): Promise<void> => {
output.set(entity, await getExtendedEntity(hass, entity, cache));
};
await Promise.all(entities.map(_storeExtendedEntity));
return output;
};
/**
* Get a list of all entities from the entity registry.
* @param hass The Home Assistant object.
* @returns An entity list object.
*/
export const getAllEntities = async (hass: HomeAssistant): Promise<EntityList> => {
return await homeAssistantWSRequest<EntityList>(hass, entityListSchema, {
type: 'config/entity_registry/list',
});
};
+18 -2
View File
@@ -4,8 +4,7 @@ import { StyleInfo } from 'lit/directives/style-map.js';
import { ZodSchema } from 'zod'; import { ZodSchema } from 'zod';
import { localize } from '../../localize/localize.js'; import { localize } from '../../localize/localize.js';
import { import {
CardHelpers, CardHelpers, ExtendedHomeAssistant,
ExtendedHomeAssistant,
SignedPath, SignedPath,
signedPathSchema, signedPathSchema,
StateParameters StateParameters
@@ -306,3 +305,20 @@ export const sideLoadHomeAssistantElements = async (): Promise<boolean> => {
export const isTriggeredState = (state?: HassEntity): boolean => { export const isTriggeredState = (state?: HassEntity): boolean => {
return !!state && ['on', 'open'].includes(state.state); return !!state && ['on', 'open'].includes(state.state);
}; };
/**
* Get entities from the HASS object.
* @param hass
* @param domain
* @returns
*/
export const getEntitiesFromHASS = (hass: HomeAssistant, domain?: string): string[] => {
if (!hass) {
return [];
}
const entities = Object.keys(hass.states).filter(
(eid) => !domain || eid.substr(0, eid.indexOf('.')) === domain,
);
entities.sort();
return entities;
}
@@ -1,11 +1,11 @@
import { HomeAssistant } from 'custom-card-helpers'; import { HomeAssistant } from 'custom-card-helpers';
import QuickLRU from 'quick-lru'; import QuickLRU from 'quick-lru';
import { homeAssistantWSRequest } from '.';
import { import {
FrigateBrowseMediaSource, FrigateBrowseMediaSource,
ResolvedMedia, ResolvedMedia,
resolvedMediaSchema resolvedMediaSchema
} from '../types.js'; } from '../../types.js';
import { homeAssistantWSRequest } from './ha';
// It's important the cache size be at least as large as the largest likely // It's important the cache size be at least as large as the largest likely
// media query or media items will from a given query will be evicted for other // media query or media items will from a given query will be evicted for other
@@ -21,19 +21,42 @@ export class ResolvedMediaCache {
this._cache = new QuickLRU({ maxSize: RESOLVED_MEDIA_CACHE_SIZE }); this._cache = new QuickLRU({ maxSize: RESOLVED_MEDIA_CACHE_SIZE });
} }
/**
* Determine if the cache has a given id.
* @param id
* @returns `true` if the id is in the cache, `false` otherwise.
*/
public has(id: string): boolean { public has(id: string): boolean {
return this._cache.has(id); return this._cache.has(id);
} }
/**
* Get resolved media information given an id.
* @param id The id.
* @returns The `ResolvedMedia` for this id.
*/
public get(id: string): ResolvedMedia | undefined { public get(id: string): ResolvedMedia | undefined {
return this._cache.get(id); return this._cache.get(id);
} }
/**
* Add a given ResolvedMedia to the cache.
* @param id The id for the object.
* @param resolvedMedia The `ResolvedMedia` object.
*/
public set(id: string, resolvedMedia: ResolvedMedia): void { public set(id: string, resolvedMedia: ResolvedMedia): void {
this._cache.set(id, resolvedMedia); this._cache.set(id, resolvedMedia);
} }
} }
/**
* Resolve a given media source item.
* @param hass The Home Assistant object.
* @param mediaSource The media source object.
* @param cache An optional ResolvedMediaCache object.
* @returns
*/
export const resolveMedia = async ( export const resolveMedia = async (
hass: HomeAssistant, hass: HomeAssistant,
mediaSource?: FrigateBrowseMediaSource, mediaSource?: FrigateBrowseMediaSource,