Refactor camera initialization into the camera engines.

This commit is contained in:
Dermot Duffy
2023-02-12 16:10:22 -08:00
parent 587f483f40
commit 06b4cc914c
30 changed files with 1249 additions and 842 deletions
+5 -5
View File
@@ -167,11 +167,11 @@ export function getDurationString(start: Date, end: Date): string {
return duration;
}
export const allPromises = async <T>(
items: T[],
func: (arg: T) => void,
): Promise<void> => {
await Promise.all(Array.from(items).map((item) => func(item)));
export const allPromises = async <T, R>(
items: Iterable<T>,
func: (arg: T) => R,
): Promise<Awaited<R>[]> => {
return await Promise.all(Array.from(items).map((item) => func(item)));
};
/**
+1 -1
View File
@@ -26,7 +26,7 @@ export function getCameraID(
/**
* Get all cameras that depend on a given camera.
* @param cameras Cameras map.
* @param camera Name of the target camera.
* @param cameraID ID of the target camera.
* @returns A set of query parameters.
*/
export const getAllDependentCameras = (
-109
View File
@@ -1,109 +0,0 @@
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. May throw.
* @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. May throw.
* @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',
});
};
+50
View File
@@ -0,0 +1,50 @@
import { Entity, ExtendedEntity } from './types.js';
export class EntityCache<T extends Entity | ExtendedEntity> {
protected _cache: Map<string, T> = 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 getFirstMatch(func: (arg: T) => boolean): T | null {
// return [...this._cache.values()].find(func) ?? null;
// }
public getMatches(func: (arg: T) => boolean): T[] {
return [...this._cache.values()].filter(func);
}
/**
* Get entity information given an id.
* @param id The entity id.
* @returns The `ExtendedEntity` for this id.
*/
public get(id: string): T | undefined {
return this._cache.get(id);
}
/**
* Add a given entity to the cache.
* @param extendedEntity
*/
public set(input: T | T[]): void {
const _set = (entity: T) => this._cache.set(entity.entity_id, entity);
if (Array.isArray(input)) {
input.forEach(_set);
} else {
_set(input);
}
}
}
+95
View File
@@ -0,0 +1,95 @@
import { HomeAssistant } from 'custom-card-helpers';
import { homeAssistantWSRequest } from '..';
import { EntityCache } from './cache';
import {
Entity,
EntityList,
entityListSchema,
ExtendedEntity,
extendedEntitySchema,
} from './types.js';
type EntityRegistryCache = EntityCache<Entity>;
type ExtendedEntityRegistryCache = EntityCache<ExtendedEntity>;
// 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 {
protected _cache: EntityRegistryCache;
protected _extendedCache: ExtendedEntityRegistryCache;
protected _fetchedEntityList = false;
constructor(cache: EntityCache<Entity>, extendedCache: EntityCache<ExtendedEntity>) {
this._cache = cache;
this._extendedCache = extendedCache;
}
public async getEntity(hass: HomeAssistant, entityID: string): Promise<Entity | null> {
const cachedEntity = this._cache.get(entityID);
if (cachedEntity) {
return cachedEntity;
}
const cachedExtendedEntity = this._extendedCache.get(entityID);
if (cachedExtendedEntity) {
return cachedExtendedEntity;
}
return await this.getExtendedEntity(hass, entityID);
}
public async getMatchingEntities(hass: HomeAssistant, func: (arg: Entity) => boolean): Promise<Entity[]> {
await this.fetchEntityList(hass);
return this._cache.getMatches(func);
}
public async getExtendedEntity(
hass: HomeAssistant,
entityID: string,
): Promise<ExtendedEntity | null> {
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 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> {
if (this._fetchedEntityList) {
return;
}
const entityList = await homeAssistantWSRequest<EntityList>(hass, entityListSchema, {
type: 'config/entity_registry/list',
});
this._cache.set(entityList);
this._fetchedEntityList = true;
}
}
+19
View File
@@ -0,0 +1,19 @@
import { z } from 'zod';
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(),
});
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>;
+11 -7
View File
@@ -1,7 +1,7 @@
import add from 'date-fns/add';
import sub from 'date-fns/sub';
import { ViewContext } from 'view';
import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types';
import { ClipsOrSnapshotsOrAll, FrigateCardView } from '../types';
import { View } from '../view/view';
import {
EventMediaQueries,
@@ -21,7 +21,6 @@ export const changeViewToRecentEventsForCameraAndDependents = async (
element: HTMLElement,
hass: HomeAssistant,
cameraManager: CameraManager,
cameras: Map<string, CameraConfig>,
view: View,
options?: {
mediaType?: ClipsOrSnapshotsOrAll;
@@ -29,7 +28,7 @@ export const changeViewToRecentEventsForCameraAndDependents = async (
},
): Promise<void> => {
(
await createViewForEvents(element, hass, cameraManager, cameras, view, {
await createViewForEvents(element, hass, cameraManager, view, {
...options,
limit: 50, // Capture the 50 most recent events.
})
@@ -40,7 +39,6 @@ export const createViewForEvents = async (
element: HTMLElement,
hass: HomeAssistant,
cameraManager: CameraManager,
cameras: Map<string, CameraConfig>,
view: View,
options?: {
query?: EventMediaQueries;
@@ -51,6 +49,10 @@ export const createViewForEvents = async (
limit?: number;
},
): Promise<View | null> => {
const cameras = cameraManager.getCameras();
if (!cameras) {
return null;
}
let query: EventMediaQueries;
const cameraIDs: Set<string> = options?.cameraIDs
? options.cameraIDs
@@ -94,7 +96,6 @@ export const changeViewToRecentRecordingForCameraAndDependents = async (
element: HTMLElement,
hass: HomeAssistant,
cameraManager: CameraManager,
cameras: Map<string, CameraConfig>,
view: View,
options?: {
targetView?: 'recording' | 'recordings';
@@ -102,7 +103,7 @@ export const changeViewToRecentRecordingForCameraAndDependents = async (
): Promise<void> => {
const now = new Date();
(
await createViewForRecordings(element, hass, cameraManager, cameras, view, {
await createViewForRecordings(element, hass, cameraManager, view, {
...options,
// Fetch 7 days worth of recordings (including recordings that are for the
// current hour).
@@ -127,7 +128,6 @@ export const createViewForRecordings = async (
element: HTMLElement,
hass: HomeAssistant,
cameraManager: CameraManager,
cameras: Map<string, CameraConfig>,
view: View,
options?: {
query?: RecordingMediaQueries;
@@ -139,6 +139,10 @@ export const createViewForRecordings = async (
end?: Date;
},
): Promise<View | null> => {
const cameras = cameraManager.getCameras();
if (!cameras) {
return null;
}
const cameraIDs: Set<string> = options?.cameraIDs
? options.cameraIDs
: new Set(getAllDependentCameras(cameras, view.camera));