Merge pull request #986 from dermotduffy/no-longer-need-extended-for-uniqueid
Remove unnecessary entity fetches
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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
@@ -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>(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
Reference in New Issue
Block a user