Merge pull request #986 from dermotduffy/no-longer-need-extended-for-uniqueid

Remove unnecessary entity fetches
This commit is contained in:
Dermot Duffy
2023-03-04 13:18:57 -08:00
committed by GitHub
6 changed files with 41 additions and 105 deletions
+14 -23
View File
@@ -71,7 +71,7 @@ import { FrigateViewMediaFactory } from './media';
import { log } from '../../utils/debug'; import { log } from '../../utils/debug';
import { getEntityTitle } from '../../utils/ha'; import { getEntityTitle } from '../../utils/ha';
import { EntityRegistryManager } from '../../utils/ha/entity-registry'; 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 { CameraInitializationError } from '../error';
import { localize } from '../../localize/localize'; import { localize } from '../../localize/localize';
import uniq from 'lodash-es/uniq'; import uniq from 'lodash-es/uniq';
@@ -148,17 +148,13 @@ export class FrigateCameraManagerEngine
const hasAutoTriggers = const hasAutoTriggers =
cameraConfig.triggers.motion || cameraConfig.triggers.occupancy; 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 // Entity information is required if the Frigate camera name is missing, or
// missing, or if the entity requires automatic resolution of // if the entity requires automatic resolution of motion/occupancy sensors.
// motion/occupancy sensors.
if (cameraConfig.camera_entity && (!hasCameraName || hasAutoTriggers)) { if (cameraConfig.camera_entity && (!hasCameraName || hasAutoTriggers)) {
try { try {
entity = await entityRegistryManager.getExtendedEntity( entity = await entityRegistryManager.getEntity(hass, cameraConfig.camera_entity);
hass,
cameraConfig.camera_entity,
);
} catch (e) { } catch (e) {
throw new CameraInitializationError( throw new CameraInitializationError(
localize('error.no_camera_entity'), localize('error.no_camera_entity'),
@@ -187,14 +183,9 @@ export class FrigateCameraManagerEngine
ent.entity_id.startsWith('binary_sensor.'), ent.entity_id.startsWith('binary_sensor.'),
); );
const extendedEntities = await entityRegistryManager.getExtendedEntities(
hass,
binarySensorEntities.map((entity) => entity.entity_id),
);
if (cameraConfig.triggers.motion) { if (cameraConfig.triggers.motion) {
const motionEntity = this._getMotionSensor(cameraConfig, [ const motionEntity = this._getMotionSensor(cameraConfig, [
...extendedEntities.values(), ...binarySensorEntities.values(),
]); ]);
if (motionEntity) { if (motionEntity) {
cameraConfig.triggers.entities.push(motionEntity); cameraConfig.triggers.entities.push(motionEntity);
@@ -203,7 +194,7 @@ export class FrigateCameraManagerEngine
if (cameraConfig.triggers.occupancy) { if (cameraConfig.triggers.occupancy) {
const occupancyEntity = this._getOccupancySensor(cameraConfig, [ const occupancyEntity = this._getOccupancySensor(cameraConfig, [
...extendedEntities.values(), ...binarySensorEntities.values(),
]); ]);
if (occupancyEntity) { if (occupancyEntity) {
cameraConfig.triggers.entities.push(occupancyEntity); cameraConfig.triggers.entities.push(occupancyEntity);
@@ -221,7 +212,7 @@ export class FrigateCameraManagerEngine
* Get the Frigate camera name from an entity. * 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 _getFrigateCameraNameFromEntity(entity: ExtendedEntity): string | null { protected _getFrigateCameraNameFromEntity(entity: Entity): string | null {
if (entity.unique_id && entity.platform === 'frigate') { if (entity.unique_id && entity.platform === 'frigate') {
const match = entity.unique_id.match(/:camera:(?<camera>[^:]+)$/); const match = entity.unique_id.match(/:camera:(?<camera>[^:]+)$/);
if (match && match.groups) { if (match && match.groups) {
@@ -233,17 +224,17 @@ export class FrigateCameraManagerEngine
/** /**
* Get the motion sensor entity for a given camera. * 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. * @param cameraConfig The camera config in question.
* @returns The entity id of the motion sensor or null. * @returns The entity id of the motion sensor or null.
*/ */
protected _getMotionSensor( protected _getMotionSensor(
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
extendedEntities: ExtendedEntity[], entities: Entity[],
): string | null { ): string | null {
if (cameraConfig.frigate.camera_name) { if (cameraConfig.frigate.camera_name) {
return ( return (
extendedEntities.find( entities.find(
(ent) => (ent) =>
!!ent.unique_id?.match( !!ent.unique_id?.match(
new RegExp( new RegExp(
@@ -260,17 +251,17 @@ export class FrigateCameraManagerEngine
/** /**
* Get the occupancy sensor entity for a given camera. * 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. * @param cameraConfig The camera config in question.
* @returns The entity id of the occupancy sensor or null. * @returns The entity id of the occupancy sensor or null.
*/ */
protected _getOccupancySensor( protected _getOccupancySensor(
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
extendedEntities: ExtendedEntity[], entities: Entity[],
): string | null { ): string | null {
if (cameraConfig.frigate.camera_name) { if (cameraConfig.frigate.camera_name) {
return ( return (
extendedEntities.find( entities.find(
(ent) => (ent) =>
!!ent.unique_id?.match( !!ent.unique_id?.match(
new RegExp( new RegExp(
+2 -1
View File
@@ -187,7 +187,8 @@ export class CameraManager {
camerasConfig.some((config) => hasAutoTriggers(config)) camerasConfig.some((config) => hasAutoTriggers(config))
) { ) {
// ... then we need to populate the entity cache by fetching all entities // ... 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); await entityRegistryManager.fetchEntityList(hass);
} }
+2 -5
View File
@@ -85,7 +85,7 @@ import { CameraManagerEngineFactory } from './camera-manager/engine-factory.js';
import { log } from './utils/debug.js'; import { log } from './utils/debug.js';
import { EntityRegistryManager } from './utils/ha/entity-registry/index.js'; import { EntityRegistryManager } from './utils/ha/entity-registry/index.js';
import { EntityCache } from './utils/ha/entity-registry/cache.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'; import { getAllDependentCameras } from './utils/camera.js';
import cloneDeep from 'lodash-es/cloneDeep'; import cloneDeep from 'lodash-es/cloneDeep';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
@@ -222,10 +222,7 @@ class FrigateCard extends LitElement {
constructor() { constructor() {
super(); super();
this._entityRegistryManager = new EntityRegistryManager( this._entityRegistryManager = new EntityRegistryManager(new EntityCache());
new EntityCache<Entity>(),
new EntityCache<ExtendedEntity>(),
);
} }
/** /**
+9 -9
View File
@@ -1,7 +1,7 @@
import { Entity, ExtendedEntity } from './types.js'; import { Entity } from './types.js';
export class EntityCache<T extends Entity | ExtendedEntity> { export class EntityCache {
protected _cache: Map<string, T> = new Map(); protected _cache: Map<string, Entity> = new Map();
/** /**
* Determine if the cache has a given entity_id. * Determine if the cache has a given entity_id.
@@ -21,25 +21,25 @@ export class EntityCache<T extends Entity | ExtendedEntity> {
// return [...this._cache.values()].find(func) ?? null; // 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); return [...this._cache.values()].filter(func);
} }
/** /**
* Get entity information given an id. * Get entity information given an id.
* @param id The entity 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); return this._cache.get(id);
} }
/** /**
* Add a given entity to the cache. * Add a given entity to the cache.
* @param extendedEntity * @param input The entity.
*/ */
public set(input: T | T[]): void { public set(input: Entity | Entity[]): void {
const _set = (entity: T) => this._cache.set(entity.entity_id, entity); const _set = (entity: Entity) => this._cache.set(entity.entity_id, entity);
if (Array.isArray(input)) { if (Array.isArray(input)) {
input.forEach(_set); input.forEach(_set);
+12 -60
View File
@@ -1,32 +1,18 @@
import { HomeAssistant } from 'custom-card-helpers'; import { HomeAssistant } from 'custom-card-helpers';
import { homeAssistantWSRequest } from '..'; import { homeAssistantWSRequest } from '..';
import { EntityCache } from './cache'; import { EntityCache } from './cache';
import { import { Entity, EntityList, entitySchema, entityListSchema } from './types.js';
Entity,
EntityList,
entityListSchema,
ExtendedEntity,
extendedEntitySchema,
} from './types.js';
type EntityRegistryCache = EntityCache<Entity>; // This class manages interactions with entities, caching results and fetching
type ExtendedEntityRegistryCache = EntityCache<ExtendedEntity>; // as necessary. Some calls require every entity to be fetched, which may be
// non-trivial in size (after which it is cached forever).
// 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).
export class EntityRegistryManager { export class EntityRegistryManager {
protected _cache: EntityRegistryCache; protected _cache: EntityCache;
protected _extendedCache: ExtendedEntityRegistryCache;
protected _fetchedEntityList = false; protected _fetchedEntityList = false;
constructor(cache: EntityCache<Entity>, extendedCache: EntityCache<ExtendedEntity>) { constructor(cache: EntityCache) {
this._cache = cache; this._cache = cache;
this._extendedCache = extendedCache;
} }
public async getEntity(hass: HomeAssistant, entityID: string): Promise<Entity | null> { public async getEntity(hass: HomeAssistant, entityID: string): Promise<Entity | null> {
@@ -35,11 +21,12 @@ export class EntityRegistryManager {
return cachedEntity; return cachedEntity;
} }
const cachedExtendedEntity = this._extendedCache.get(entityID); const entity = await homeAssistantWSRequest<Entity>(hass, entitySchema, {
if (cachedExtendedEntity) { type: 'config/entity_registry/get',
return cachedExtendedEntity; entity_id: entityID,
} });
return await this.getExtendedEntity(hass, entityID); this._cache.set(entity);
return entity;
} }
public async getMatchingEntities( public async getMatchingEntities(
@@ -50,26 +37,6 @@ export class EntityRegistryManager {
return this._cache.getMatches(func); return this._cache.getMatches(func);
} }
public async getExtendedEntity(
hass: HomeAssistant,
entityID: string,
): Promise<ExtendedEntity> {
const cachedValue = this._extendedCache.get(entityID);
if (cachedValue) {
return cachedValue;
}
const extendedEntity = await homeAssistantWSRequest<ExtendedEntity>(
hass,
extendedEntitySchema,
{
type: 'config/entity_registry/get',
entity_id: entityID,
},
);
this._extendedCache.set(extendedEntity);
return extendedEntity;
}
public async getEntities( public async getEntities(
hass: HomeAssistant, hass: HomeAssistant,
entityIDs: string[], entityIDs: string[],
@@ -85,21 +52,6 @@ export class EntityRegistryManager {
return output; return output;
} }
public async getExtendedEntities(
hass: HomeAssistant,
entityIDs: string[],
): Promise<Map<string, ExtendedEntity>> {
const output: Map<string, ExtendedEntity> = new Map();
const _storeExtendedEntity = async (entityID: string): Promise<void> => {
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<void> { public async fetchEntityList(hass: HomeAssistant): Promise<void> {
if (this._fetchedEntityList) { if (this._fetchedEntityList) {
return; return;
+2 -7
View File
@@ -1,19 +1,14 @@
import { z } from 'zod'; import { z } from 'zod';
const entitySchema = z.object({ export const entitySchema = z.object({
config_entry_id: z.string().nullable(), config_entry_id: z.string().nullable(),
disabled_by: z.string().nullable(), disabled_by: z.string().nullable(),
entity_id: z.string(), entity_id: z.string(),
hidden_by: z.string().nullable(), hidden_by: z.string().nullable(),
platform: z.string(), platform: z.string(),
unique_id: z.string().optional(),
}); });
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 const entityListSchema = entitySchema.array();
export type EntityList = z.infer<typeof entityListSchema>; export type EntityList = z.infer<typeof entityListSchema>;