Improve card loading speed.
This commit is contained in:
+101
-70
@@ -709,24 +709,38 @@ export class FrigateCard extends LitElement {
|
|||||||
return;
|
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;
|
let entityList: EntityList | undefined;
|
||||||
try {
|
const cache = new ExtendedEntityCache();
|
||||||
entityList = await getAllEntities(this._hass);
|
const loadedCameras: CameraConfig[] = [];
|
||||||
} catch (e) {
|
|
||||||
errorToConsole(e as Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cameras: Map<string, CameraConfig> = new Map();
|
const addCameraConfig = async (config: CameraConfig, index: number) => {
|
||||||
let errorFree = true;
|
|
||||||
|
|
||||||
const addCameraConfig = async (config: CameraConfig) => {
|
|
||||||
if (!this._hass) {
|
if (!this._hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity: ExtendedEntity | null = null;
|
let entity: ExtendedEntity | null = null;
|
||||||
if (config.camera_entity) {
|
if (config.camera_entity && (hasAnyTriggers(config) || !hasCameraName(config))) {
|
||||||
try {
|
try {
|
||||||
entity = await getExtendedEntity(this._hass, config.camera_entity, cache);
|
entity = await getExtendedEntity(this._hass, config.camera_entity, cache);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -736,77 +750,94 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.frigate.camera_name && entity) {
|
if (entity && !hasCameraName(config)) {
|
||||||
const resolvedName = this._getFrigateCameraNameFromEntity(entity);
|
const resolvedName = this._getFrigateCameraNameFromEntity(entity);
|
||||||
if (resolvedName) {
|
if (resolvedName) {
|
||||||
config.frigate.camera_name = resolvedName;
|
config.frigate.camera_name = resolvedName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity && entityList) {
|
if (entity && entityList && hasAnyTriggers(config)) {
|
||||||
// Try to find the correct entities for the motion & occupancy sensors.
|
if (hasAutoTriggers(config)) {
|
||||||
// We know they are binary_sensors, and that they'll have the same
|
// Try to find the correct entities for the motion & occupancy sensors.
|
||||||
// config entry ID as the camera. Searching via unique_id ensures this
|
// We know they are binary_sensors, and that they'll have the same
|
||||||
// search still works if the user renames the entity_id.
|
// config entry ID as the camera. Searching via unique_id ensures this
|
||||||
const binarySensorEntities = entityList.filter(
|
// search still works if the user renames the entity_id.
|
||||||
(ent) =>
|
const binarySensorEntities = entityList.filter(
|
||||||
ent.config_entry_id === entity?.config_entry_id &&
|
(ent) =>
|
||||||
!ent.disabled_by &&
|
ent.config_entry_id === entity?.config_entry_id &&
|
||||||
ent.entity_id.startsWith('binary_sensor.'),
|
!ent.disabled_by &&
|
||||||
);
|
ent.entity_id.startsWith('binary_sensor.'),
|
||||||
|
|
||||||
try {
|
|
||||||
await getExtendedEntities(
|
|
||||||
this._hass,
|
|
||||||
binarySensorEntities.map((ent) => ent.entity_id),
|
|
||||||
cache,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await getExtendedEntities(
|
||||||
|
this._hass,
|
||||||
|
binarySensorEntities.map((ent) => ent.entity_id),
|
||||||
|
cache,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
errorToConsole(e as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.triggers.motion) {
|
||||||
|
const motionEntity = this._getMotionSensor(cache, config);
|
||||||
|
if (motionEntity) {
|
||||||
|
config.triggers.entities.push(motionEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.triggers.occupancy) {
|
||||||
|
const occupancyEntity = this._getOccupancySensor(cache, config);
|
||||||
|
if (occupancyEntity) {
|
||||||
|
config.triggers.entities.push(occupancyEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (e) {
|
||||||
errorToConsole(e as Error);
|
errorToConsole(e as Error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.triggers.motion) {
|
// Load all cameras in parallel, but remember the order they were provided
|
||||||
const motionEntity = this._getMotionSensor(cache, config);
|
// (they must be added to the cameraMap in this same order).
|
||||||
if (motionEntity) {
|
await Promise.all(configCameras.map((configCamera, index) => addCameraConfig(configCamera, index)))
|
||||||
config.triggers.entities.push(motionEntity);
|
|
||||||
}
|
loadedCameras.forEach((loadedCamera: CameraConfig) => {
|
||||||
|
const id = getCameraID(loadedCamera);
|
||||||
|
if (!id) {
|
||||||
|
this._setMessageAndUpdate({
|
||||||
|
message: localize('error.no_camera_id'),
|
||||||
|
type: 'error',
|
||||||
|
context: loadedCamera,
|
||||||
|
});
|
||||||
|
errorFree = false;
|
||||||
|
} else if (cameras.has(id)) {
|
||||||
|
this._setMessageAndUpdate({
|
||||||
|
message: localize('error.duplicate_camera_id'),
|
||||||
|
type: 'error',
|
||||||
|
context: loadedCamera,
|
||||||
|
});
|
||||||
|
errorFree = false;
|
||||||
|
} else {
|
||||||
|
cameras.set(id, loadedCamera);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (config.triggers.occupancy) {
|
|
||||||
const occupancyEntity = this._getOccupancySensor(cache, config);
|
|
||||||
if (occupancyEntity) {
|
|
||||||
config.triggers.entities.push(occupancyEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.triggers.entities = [...new Set(config.triggers.entities)];
|
|
||||||
|
|
||||||
const id = getCameraID(config);
|
|
||||||
if (!id) {
|
|
||||||
this._setMessageAndUpdate({
|
|
||||||
message: localize('error.no_camera_id'),
|
|
||||||
type: 'error',
|
|
||||||
context: config,
|
|
||||||
});
|
|
||||||
errorFree = false;
|
|
||||||
} else if (cameras.has(id)) {
|
|
||||||
this._setMessageAndUpdate({
|
|
||||||
message: localize('error.duplicate_camera_id'),
|
|
||||||
type: 'error',
|
|
||||||
context: config,
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cameras.size) {
|
if (!cameras.size) {
|
||||||
|
|||||||
+3
-2
@@ -40,7 +40,7 @@ import {
|
|||||||
/**
|
/**
|
||||||
* Set a configuration value.
|
* Set a configuration value.
|
||||||
* @param obj The configuration.
|
* @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.
|
* @param value The value to set.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -55,7 +55,8 @@ export const setConfigValue = (
|
|||||||
/**
|
/**
|
||||||
* Get a configuration value.
|
* Get a configuration value.
|
||||||
* @param obj The configuration.
|
* @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.
|
* @returns The property or undefined if not found.
|
||||||
*/
|
*/
|
||||||
export const getConfigValue = (
|
export const getConfigValue = (
|
||||||
|
|||||||
+29
-7
@@ -9,8 +9,9 @@ import {
|
|||||||
FrigateCardConfig,
|
FrigateCardConfig,
|
||||||
RawFrigateCardConfig,
|
RawFrigateCardConfig,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { getConfigValue, setConfigValue } from './config-mgmt.js';
|
import { getArrayConfigPath, getConfigValue, setConfigValue } from './config-mgmt.js';
|
||||||
import {
|
import {
|
||||||
|
CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY,
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
|
||||||
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
|
||||||
@@ -64,7 +65,6 @@ const LOW_PROFILE_DEFAULTS = {
|
|||||||
[CONF_TIMELINE_SHOW_RECORDINGS]: false,
|
[CONF_TIMELINE_SHOW_RECORDINGS]: false,
|
||||||
|
|
||||||
// Take no automatic media actions.
|
// Take no automatic media actions.
|
||||||
[CONF_LIVE_AUTO_PLAY]: 'never' as const,
|
|
||||||
[CONF_LIVE_AUTO_MUTE]: 'never' as const,
|
[CONF_LIVE_AUTO_MUTE]: 'never' as const,
|
||||||
[CONF_MEDIA_VIEWER_AUTO_PLAY]: 'never' as const,
|
[CONF_MEDIA_VIEWER_AUTO_PLAY]: 'never' as const,
|
||||||
[CONF_MEDIA_VIEWER_AUTO_PAUSE]: '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,
|
[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
|
* Set low performance profile mode. Sets flags as defined in
|
||||||
* LOW_PROFILE_DEFAULTS unless they are explicitly overriden in the
|
* LOW_PROFILE_DEFAULTS unless they are explicitly overriden in the
|
||||||
@@ -126,16 +130,34 @@ export const setLowPerformanceProfile = (
|
|||||||
inputConfig: RawFrigateCardConfig,
|
inputConfig: RawFrigateCardConfig,
|
||||||
parsedConfig: FrigateCardConfig,
|
parsedConfig: FrigateCardConfig,
|
||||||
): 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(
|
const defaultLessParseResult = deepRemoveDefaults(frigateCardConfigSchema).safeParse(
|
||||||
inputConfig,
|
inputConfig,
|
||||||
);
|
);
|
||||||
if (defaultLessParseResult.success) {
|
if (defaultLessParseResult.success) {
|
||||||
const defaultLessConfig = defaultLessParseResult.data;
|
const defaultLessConfig = defaultLessParseResult.data;
|
||||||
Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) => {
|
Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) =>
|
||||||
if (getConfigValue(defaultLessConfig, k) === undefined) {
|
setIfNotSpecified(defaultLessConfig, parsedConfig, k, v),
|
||||||
setConfigValue(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;
|
return parsedConfig;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1213,6 +1213,11 @@ const performanceConfigDefault = {
|
|||||||
const performanceConfigSchema = z.object({
|
const performanceConfigSchema = z.object({
|
||||||
profile: z.enum(['low', 'high'])
|
profile: z.enum(['low', 'high'])
|
||||||
}).default(performanceConfigDefault);
|
}).default(performanceConfigDefault);
|
||||||
|
export type PerformanceConfig = z.infer<typeof performanceConfigSchema>;
|
||||||
|
|
||||||
|
export interface CardWideConfig {
|
||||||
|
performance?: PerformanceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main card config.
|
* Main card config.
|
||||||
|
|||||||
Reference in New Issue
Block a user