Improve card loading speed.

This commit is contained in:
Dermot Duffy
2022-10-05 18:44:36 -07:00
parent c8c9098501
commit 3566011d63
4 changed files with 138 additions and 79 deletions
+57 -26
View File
@@ -709,24 +709,38 @@ export class FrigateCard extends LitElement {
return;
}
const cache = new ExtendedEntityCache();
const hasAutoTriggers = (config: CameraConfig): boolean => {
return config.triggers.motion || config.triggers.occupancy;
};
const hasAnyTriggers = (config: CameraConfig): boolean => {
return hasAutoTriggers(config) || !!config.triggers.entities.length;
};
const hasCameraName = (config: CameraConfig): boolean => {
return !!config.frigate?.camera_name;
};
// Loading cameras may require a number of calls to Home Assistant.
//
// - getAllEntities: Required if any camera has auto-triggers (motion or
// occupancy sensors).
// - getExtendedEntity: Per camera entity to autodetect the Frigate camera
// name or to compute auto-triggers (motion or occupancy sensors).
// - getExtendedEntities: Per binary sensor associated with a Frigate config
// entry, to compute auto-triggers (motion or occupancy sensors).
//
// For loading performance these are only called when absolutely needed.
let entityList: EntityList | undefined;
try {
entityList = await getAllEntities(this._hass);
} catch (e) {
errorToConsole(e as Error);
}
const cache = new ExtendedEntityCache();
const loadedCameras: CameraConfig[] = [];
const cameras: Map<string, CameraConfig> = new Map();
let errorFree = true;
const addCameraConfig = async (config: CameraConfig) => {
const addCameraConfig = async (config: CameraConfig, index: number) => {
if (!this._hass) {
return;
}
let entity: ExtendedEntity | null = null;
if (config.camera_entity) {
if (config.camera_entity && (hasAnyTriggers(config) || !hasCameraName(config))) {
try {
entity = await getExtendedEntity(this._hass, config.camera_entity, cache);
} catch (e) {
@@ -736,14 +750,15 @@ export class FrigateCard extends LitElement {
}
}
if (!config.frigate.camera_name && entity) {
if (entity && !hasCameraName(config)) {
const resolvedName = this._getFrigateCameraNameFromEntity(entity);
if (resolvedName) {
config.frigate.camera_name = resolvedName;
}
}
if (entity && entityList) {
if (entity && entityList && hasAnyTriggers(config)) {
if (hasAutoTriggers(config)) {
// 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
@@ -779,34 +794,50 @@ export class FrigateCard extends LitElement {
}
}
}
config.triggers.entities = [...new Set(config.triggers.entities)];
const id = getCameraID(config);
config.triggers.entities = [...new Set(config.triggers.entities)];
}
loadedCameras[index] = config;
};
let errorFree = true;
const cameras: Map<string, CameraConfig> = new Map();
const configCameras = this._getConfig().cameras;
if (configCameras && Array.isArray(configCameras)) {
if (configCameras.some((config) => hasAutoTriggers(config))) {
try {
entityList = await getAllEntities(this._hass);
} catch (e) {
errorToConsole(e as Error);
}
}
// Load all cameras in parallel, but remember the order they were provided
// (they must be added to the cameraMap in this same order).
await Promise.all(configCameras.map((configCamera, index) => addCameraConfig(configCamera, index)))
loadedCameras.forEach((loadedCamera: CameraConfig) => {
const id = getCameraID(loadedCamera);
if (!id) {
this._setMessageAndUpdate({
message: localize('error.no_camera_id'),
type: 'error',
context: config,
context: loadedCamera,
});
errorFree = false;
} else if (cameras.has(id)) {
this._setMessageAndUpdate({
message: localize('error.duplicate_camera_id'),
type: 'error',
context: config,
context: loadedCamera,
});
errorFree = false;
} else {
cameras.set(id, config);
}
};
if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
// Cameras are loaded sequentially rather than in parallel to preserve the
// order of the input camera array.
for (const camera of this._getConfig().cameras) {
await addCameraConfig(camera);
cameras.set(id, loadedCamera);
}
});
}
if (!cameras.size) {
+3 -2
View File
@@ -40,7 +40,7 @@ import {
/**
* Set a configuration value.
* @param obj The configuration.
* @param key The key to the property to set.
* @param keys The key to the property to set.
* @param value The value to set.
*/
@@ -55,7 +55,8 @@ export const setConfigValue = (
/**
* Get a configuration value.
* @param obj The configuration.
* @param key The key to the property to retrieve.
* @param keys The key to the property to retrieve.
* @param def Default if key not found.
* @returns The property or undefined if not found.
*/
export const getConfigValue = (
+28 -6
View File
@@ -9,8 +9,9 @@ import {
FrigateCardConfig,
RawFrigateCardConfig,
} from './types';
import { getConfigValue, setConfigValue } from './config-mgmt.js';
import { getArrayConfigPath, getConfigValue, setConfigValue } from './config-mgmt.js';
import {
CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
@@ -64,7 +65,6 @@ const LOW_PROFILE_DEFAULTS = {
[CONF_TIMELINE_SHOW_RECORDINGS]: false,
// Take no automatic media actions.
[CONF_LIVE_AUTO_PLAY]: 'never' as const,
[CONF_LIVE_AUTO_MUTE]: 'never' as const,
[CONF_MEDIA_VIEWER_AUTO_PLAY]: 'never' as const,
[CONF_MEDIA_VIEWER_AUTO_PAUSE]: 'never' as const,
@@ -114,6 +114,10 @@ const LOW_PROFILE_DEFAULTS = {
[CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false,
};
const LOW_PROFILE_CAMERA_DEFAULTS = {
[CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY]: false,
};
/**
* Set low performance profile mode. Sets flags as defined in
* LOW_PROFILE_DEFAULTS unless they are explicitly overriden in the
@@ -126,16 +130,34 @@ export const setLowPerformanceProfile = (
inputConfig: RawFrigateCardConfig,
parsedConfig: FrigateCardConfig,
): FrigateCardConfig => {
const setIfNotSpecified = (
defaultLessConfig: RawFrigateCardConfig,
parsedConfig: FrigateCardConfig,
key: string,
value: unknown,
) => {
if (getConfigValue(defaultLessConfig, key) === undefined) {
setConfigValue(parsedConfig, key, value);
}
};
const defaultLessParseResult = deepRemoveDefaults(frigateCardConfigSchema).safeParse(
inputConfig,
);
if (defaultLessParseResult.success) {
const defaultLessConfig = defaultLessParseResult.data;
Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) => {
if (getConfigValue(defaultLessConfig, k) === undefined) {
setConfigValue(parsedConfig, k, v);
}
Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) =>
setIfNotSpecified(defaultLessConfig, parsedConfig, k, v),
);
Object.entries(LOW_PROFILE_CAMERA_DEFAULTS).forEach(
([rawKey, v]: [string, unknown]) => {
defaultLessConfig.cameras.forEach((_, index: number) => {
const indexedKey = getArrayConfigPath(rawKey, index);
setIfNotSpecified(defaultLessConfig, parsedConfig, indexedKey, v);
});
},
);
}
return parsedConfig;
};
+5
View File
@@ -1213,6 +1213,11 @@ const performanceConfigDefault = {
const performanceConfigSchema = z.object({
profile: z.enum(['low', 'high'])
}).default(performanceConfigDefault);
export type PerformanceConfig = z.infer<typeof performanceConfigSchema>;
export interface CardWideConfig {
performance?: PerformanceConfig;
}
/**
* Main card config.