diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts index c5675902..2ecf063b 100644 --- a/src/camera-manager/frigate/engine-frigate.ts +++ b/src/camera-manager/frigate/engine-frigate.ts @@ -71,7 +71,7 @@ import { FrigateViewMediaFactory } from './media'; import { log } from '../../utils/debug'; import { getEntityTitle } from '../../utils/ha'; import { EntityRegistryManager } from '../../utils/ha/entity-registry'; -import { ExtendedEntity } from '../../utils/ha/entity-registry/types'; +import { Entity } from '../../utils/ha/entity-registry/types'; import { CameraInitializationError } from '../error'; import { localize } from '../../localize/localize'; import uniq from 'lodash-es/uniq'; @@ -148,17 +148,13 @@ export class FrigateCameraManagerEngine const hasAutoTriggers = cameraConfig.triggers.motion || cameraConfig.triggers.occupancy; - let entity: ExtendedEntity | null = null; + let entity: Entity | null = null; - // Extended entity information is required if the Frigate camera name is - // missing, or if the entity requires automatic resolution of - // motion/occupancy sensors. + // Entity information is required if the Frigate camera name is missing, or + // if the entity requires automatic resolution of motion/occupancy sensors. if (cameraConfig.camera_entity && (!hasCameraName || hasAutoTriggers)) { try { - entity = await entityRegistryManager.getExtendedEntity( - hass, - cameraConfig.camera_entity, - ); + entity = await entityRegistryManager.getEntity(hass, cameraConfig.camera_entity); } catch (e) { throw new CameraInitializationError( localize('error.no_camera_entity'), @@ -187,14 +183,9 @@ export class FrigateCameraManagerEngine ent.entity_id.startsWith('binary_sensor.'), ); - const extendedEntities = await entityRegistryManager.getExtendedEntities( - hass, - binarySensorEntities.map((entity) => entity.entity_id), - ); - if (cameraConfig.triggers.motion) { const motionEntity = this._getMotionSensor(cameraConfig, [ - ...extendedEntities.values(), + ...binarySensorEntities.values(), ]); if (motionEntity) { cameraConfig.triggers.entities.push(motionEntity); @@ -203,7 +194,7 @@ export class FrigateCameraManagerEngine if (cameraConfig.triggers.occupancy) { const occupancyEntity = this._getOccupancySensor(cameraConfig, [ - ...extendedEntities.values(), + ...binarySensorEntities.values(), ]); if (occupancyEntity) { cameraConfig.triggers.entities.push(occupancyEntity); @@ -221,7 +212,7 @@ export class FrigateCameraManagerEngine * Get the Frigate camera name from an entity. * @returns The Frigate camera name or null if unavailable. */ - protected _getFrigateCameraNameFromEntity(entity: ExtendedEntity): string | null { + protected _getFrigateCameraNameFromEntity(entity: Entity): string | null { if (entity.unique_id && entity.platform === 'frigate') { const match = entity.unique_id.match(/:camera:(?[^:]+)$/); if (match && match.groups) { @@ -233,17 +224,17 @@ export class FrigateCameraManagerEngine /** * Get the motion sensor entity for a given camera. - * @param cache The ExtendedEntityCache of entity registry information. + * @param cache The EntityCache of entity registry information. * @param cameraConfig The camera config in question. * @returns The entity id of the motion sensor or null. */ protected _getMotionSensor( cameraConfig: CameraConfig, - extendedEntities: ExtendedEntity[], + entities: Entity[], ): string | null { if (cameraConfig.frigate.camera_name) { return ( - extendedEntities.find( + entities.find( (ent) => !!ent.unique_id?.match( new RegExp( @@ -260,17 +251,17 @@ export class FrigateCameraManagerEngine /** * Get the occupancy sensor entity for a given camera. - * @param cache The ExtendedEntityCache of entity registry information. + * @param cache The EntityCache of entity registry information. * @param cameraConfig The camera config in question. * @returns The entity id of the occupancy sensor or null. */ protected _getOccupancySensor( cameraConfig: CameraConfig, - extendedEntities: ExtendedEntity[], + entities: Entity[], ): string | null { if (cameraConfig.frigate.camera_name) { return ( - extendedEntities.find( + entities.find( (ent) => !!ent.unique_id?.match( new RegExp( diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index f923ab1d..4a50842d 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -189,7 +189,8 @@ export class CameraManager { camerasConfig.some((config) => hasAutoTriggers(config)) ) { // ... then we need to populate the entity cache by fetching all entities - // from Home Assistant. + // from Home Assistant. Do this once upfront, to avoid each camera doing + // it. await entityRegistryManager.fetchEntityList(hass); } diff --git a/src/card.ts b/src/card.ts index e13b3df5..63d426e8 100644 --- a/src/card.ts +++ b/src/card.ts @@ -85,7 +85,7 @@ import { CameraManagerEngineFactory } from './camera-manager/engine-factory.js'; import { log } from './utils/debug.js'; import { EntityRegistryManager } from './utils/ha/entity-registry/index.js'; import { EntityCache } from './utils/ha/entity-registry/cache.js'; -import { Entity, ExtendedEntity } from './utils/ha/entity-registry/types.js'; +import { Entity } from './utils/ha/entity-registry/types.js'; import { getAllDependentCameras } from './utils/camera.js'; /** A note on media callbacks: @@ -214,10 +214,7 @@ class FrigateCard extends LitElement { constructor() { super(); - this._entityRegistryManager = new EntityRegistryManager( - new EntityCache(), - new EntityCache(), - ); + this._entityRegistryManager = new EntityRegistryManager(new EntityCache()); } /** diff --git a/src/utils/ha/entity-registry/cache.ts b/src/utils/ha/entity-registry/cache.ts index 13938296..36056805 100644 --- a/src/utils/ha/entity-registry/cache.ts +++ b/src/utils/ha/entity-registry/cache.ts @@ -1,7 +1,7 @@ -import { Entity, ExtendedEntity } from './types.js'; +import { Entity } from './types.js'; -export class EntityCache { - protected _cache: Map = new Map(); +export class EntityCache { + protected _cache: Map = new Map(); /** * Determine if the cache has a given entity_id. @@ -21,25 +21,25 @@ export class EntityCache { // return [...this._cache.values()].find(func) ?? null; // } - public getMatches(func: (arg: T) => boolean): T[] { + public getMatches(func: (arg: Entity) => boolean): Entity[] { return [...this._cache.values()].filter(func); } /** * Get entity information given an id. * @param id The entity id. - * @returns The `ExtendedEntity` for this id. + * @returns The entity for this id. */ - public get(id: string): T | undefined { + public get(id: string): Entity | undefined { return this._cache.get(id); } /** * Add a given entity to the cache. - * @param extendedEntity + * @param input The entity. */ - public set(input: T | T[]): void { - const _set = (entity: T) => this._cache.set(entity.entity_id, entity); + public set(input: Entity | Entity[]): void { + const _set = (entity: Entity) => this._cache.set(entity.entity_id, entity); if (Array.isArray(input)) { input.forEach(_set); diff --git a/src/utils/ha/entity-registry/index.ts b/src/utils/ha/entity-registry/index.ts index c7d6582d..71d9cbb6 100644 --- a/src/utils/ha/entity-registry/index.ts +++ b/src/utils/ha/entity-registry/index.ts @@ -1,32 +1,18 @@ import { HomeAssistant } from 'custom-card-helpers'; import { homeAssistantWSRequest } from '..'; import { EntityCache } from './cache'; -import { - Entity, - EntityList, - entityListSchema, - ExtendedEntity, - extendedEntitySchema, -} from './types.js'; +import { Entity, EntityList, entitySchema, entityListSchema } from './types.js'; -type EntityRegistryCache = EntityCache; -type ExtendedEntityRegistryCache = EntityCache; - -// Tne `entity_registry/list` call returns a smaller set of information for -// every entity, than the full `entity_registry/get` call returns for a single -// entity. This class manages interactions with entities, caching results -// (either the partial or extended versions) and fetching as necessary. Some -// calls require every entity to be fetched, which may be non-trivial in size -// (after which it is cached forever). +// This class manages interactions with entities, caching results and fetching +// as necessary. Some calls require every entity to be fetched, which may be +// non-trivial in size (after which it is cached forever). export class EntityRegistryManager { - protected _cache: EntityRegistryCache; - protected _extendedCache: ExtendedEntityRegistryCache; + protected _cache: EntityCache; protected _fetchedEntityList = false; - constructor(cache: EntityCache, extendedCache: EntityCache) { + constructor(cache: EntityCache) { this._cache = cache; - this._extendedCache = extendedCache; } public async getEntity(hass: HomeAssistant, entityID: string): Promise { @@ -35,11 +21,12 @@ export class EntityRegistryManager { return cachedEntity; } - const cachedExtendedEntity = this._extendedCache.get(entityID); - if (cachedExtendedEntity) { - return cachedExtendedEntity; - } - return await this.getExtendedEntity(hass, entityID); + const entity = await homeAssistantWSRequest(hass, entitySchema, { + type: 'config/entity_registry/get', + entity_id: entityID, + }); + this._cache.set(entity); + return entity; } public async getMatchingEntities( @@ -50,26 +37,6 @@ export class EntityRegistryManager { return this._cache.getMatches(func); } - public async getExtendedEntity( - hass: HomeAssistant, - entityID: string, - ): Promise { - const cachedValue = this._extendedCache.get(entityID); - if (cachedValue) { - return cachedValue; - } - const extendedEntity = await homeAssistantWSRequest( - hass, - extendedEntitySchema, - { - type: 'config/entity_registry/get', - entity_id: entityID, - }, - ); - this._extendedCache.set(extendedEntity); - return extendedEntity; - } - public async getEntities( hass: HomeAssistant, entityIDs: string[], @@ -85,21 +52,6 @@ export class EntityRegistryManager { return output; } - public async getExtendedEntities( - hass: HomeAssistant, - entityIDs: string[], - ): Promise> { - const output: Map = new Map(); - const _storeExtendedEntity = async (entityID: string): Promise => { - const extendedEntity = await this.getExtendedEntity(hass, entityID); - if (extendedEntity) { - output.set(entityID, extendedEntity); - } - }; - await Promise.all(entityIDs.map(_storeExtendedEntity)); - return output; - } - public async fetchEntityList(hass: HomeAssistant): Promise { if (this._fetchedEntityList) { return; diff --git a/src/utils/ha/entity-registry/types.ts b/src/utils/ha/entity-registry/types.ts index ac076259..ddf0b9dc 100644 --- a/src/utils/ha/entity-registry/types.ts +++ b/src/utils/ha/entity-registry/types.ts @@ -1,19 +1,14 @@ import { z } from 'zod'; -const entitySchema = z.object({ +export const entitySchema = z.object({ config_entry_id: z.string().nullable(), disabled_by: z.string().nullable(), entity_id: z.string(), hidden_by: z.string().nullable(), platform: z.string(), + unique_id: z.string().optional(), }); export type Entity = z.infer; -export const extendedEntitySchema = entitySchema.extend({ - // Extended entity results. - unique_id: z.string().optional(), -}); -export type ExtendedEntity = z.infer; - export const entityListSchema = entitySchema.array(); export type EntityList = z.infer;