Convert camera configuration to YAML dictionary.
This commit is contained in:
+30
-32
@@ -1,5 +1,3 @@
|
|||||||
// TODO change url to frigate_url?
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -33,7 +31,6 @@ import {
|
|||||||
CameraConfig,
|
CameraConfig,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import type {
|
import type {
|
||||||
BrowseMediaQueryParameters,
|
|
||||||
Entity,
|
Entity,
|
||||||
ExtendedHomeAssistant,
|
ExtendedHomeAssistant,
|
||||||
FrigateCardConfig,
|
FrigateCardConfig,
|
||||||
@@ -53,6 +50,7 @@ import {
|
|||||||
homeAssistantSignPath,
|
homeAssistantSignPath,
|
||||||
homeAssistantWSRequest,
|
homeAssistantWSRequest,
|
||||||
isValidMediaShowInfo,
|
isValidMediaShowInfo,
|
||||||
|
refreshCameraConfigDynamicParameters,
|
||||||
shouldUpdateBasedOnHass,
|
shouldUpdateBasedOnHass,
|
||||||
} from './common.js';
|
} from './common.js';
|
||||||
import { localize } from './localize/localize.js';
|
import { localize } from './localize/localize.js';
|
||||||
@@ -280,18 +278,24 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.menu.buttons.cameras && this._cameras && this._cameras.size > 1) {
|
if (this.config.menu.buttons.cameras && this._cameras && this._cameras.size > 1) {
|
||||||
const menuItems = Array.from(this._cameras, ([camera, config]) => ({
|
const menuItems = Array.from(this._cameras, ([camera, config]) => {
|
||||||
icon: config.icon || 'mdi:cctv',
|
const dynamicConfig = refreshCameraConfigDynamicParameters(
|
||||||
entity: config.camera_entity,
|
{ ...config },
|
||||||
|
this._hass,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
icon: dynamicConfig.icon || 'mdi:cctv',
|
||||||
|
entity: dynamicConfig.camera_entity,
|
||||||
state_color: true,
|
state_color: true,
|
||||||
title: config.title,
|
title: dynamicConfig.title,
|
||||||
tap_action: createFrigateCardCustomAction('camera_select', camera),
|
tap_action: createFrigateCardCustomAction('camera_select', camera),
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
type: 'custom:frigate-card-menu-submenu',
|
type: 'custom:frigate-card-menu-submenu',
|
||||||
title: localize('config.menu.buttons.cameras'),
|
title: localize('config.menu.buttons.cameras'),
|
||||||
icon: 'mdi:camera-switch',
|
icon: 'mdi:video-switch',
|
||||||
items: menuItems,
|
items: menuItems,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -399,8 +403,13 @@ export class FrigateCard extends LitElement {
|
|||||||
protected async _loadCameras(): Promise<void> {
|
protected async _loadCameras(): Promise<void> {
|
||||||
const cameras: Map<string, CameraConfig> = new Map();
|
const cameras: Map<string, CameraConfig> = new Map();
|
||||||
|
|
||||||
const addCameraConfig = async (config: CameraConfig) => {
|
const addCameraConfig = async (id: string, config: CameraConfig) => {
|
||||||
if (!config.camera_name && config.camera_entity) {
|
if (!config.camera_name && (config.camera_entity || id.startsWith('camera.'))) {
|
||||||
|
if (!config.camera_entity) {
|
||||||
|
// If the camera_entity isn't explicitly set and the id looks like
|
||||||
|
// one, use that.
|
||||||
|
config.camera_entity = id;
|
||||||
|
}
|
||||||
const resolvedName = await this._getFrigateCameraNameFromEntity(
|
const resolvedName = await this._getFrigateCameraNameFromEntity(
|
||||||
config.camera_entity,
|
config.camera_entity,
|
||||||
);
|
);
|
||||||
@@ -408,30 +417,17 @@ export class FrigateCard extends LitElement {
|
|||||||
config.camera_name = resolvedName;
|
config.camera_name = resolvedName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!config.camera_name) {
|
||||||
if (config.camera_name) {
|
config.camera_name = id;
|
||||||
const id = config.id || config.camera_name;
|
|
||||||
if (cameras.has(id)) {
|
|
||||||
this._setMessageAndUpdate(
|
|
||||||
{
|
|
||||||
message: localize('error.duplicate_frigate_camera_name'),
|
|
||||||
type: 'error',
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
cameras.set(config.id || config.camera_name, config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cameras.set(id, config);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.config.camera) {
|
await Promise.all(
|
||||||
if (Array.isArray(this.config.camera)) {
|
Object.keys(this.config.cameras).map((key) =>
|
||||||
await Promise.all(this.config.camera.map(addCameraConfig.bind(this)));
|
addCameraConfig(key, this.config.cameras[key]),
|
||||||
} else {
|
),
|
||||||
await addCameraConfig(this.config.camera);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cameras.size) {
|
if (!cameras.size) {
|
||||||
return this._setMessageAndUpdate(
|
return this._setMessageAndUpdate(
|
||||||
@@ -945,6 +941,8 @@ export class FrigateCard extends LitElement {
|
|||||||
if (requestRefresh) {
|
if (requestRefresh) {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
// TODO: Remove
|
||||||
|
console.info(`Showing media: ${JSON.stringify(mediaShowInfo)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+22
-10
@@ -287,7 +287,8 @@ export function convertActionToFrigateCardCustomAction(
|
|||||||
*/
|
*/
|
||||||
export function createFrigateCardCustomAction(
|
export function createFrigateCardCustomAction(
|
||||||
action: FrigateCardAction,
|
action: FrigateCardAction,
|
||||||
camera?: string): FrigateCardCustomAction | undefined {
|
camera?: string,
|
||||||
|
): FrigateCardCustomAction | undefined {
|
||||||
if (action == 'camera_select') {
|
if (action == 'camera_select') {
|
||||||
if (!camera) {
|
if (!camera) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -296,12 +297,12 @@ export function createFrigateCardCustomAction(
|
|||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
camera: camera,
|
camera: camera,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,13 +398,24 @@ export function refreshDynamicStateParameters(
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCameraConfigDynamicParameters(
|
function prettifyCameraName(input: string): string {
|
||||||
hass: HomeAssistant,
|
const words = input.split(/[_\s]+/);
|
||||||
config: CameraConfig): CameraConfig {
|
return words
|
||||||
if (hass && config.camera_entity) {
|
.map((word) => {
|
||||||
const state = hass.states[config.camera_entity];
|
return word[0].toUpperCase() + word.substring(1);
|
||||||
config.title = config.title ?? (state?.attributes?.friendly_name || config.camera_entity);
|
})
|
||||||
config.icon = config.icon ?? stateIcon(state);
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function refreshCameraConfigDynamicParameters(
|
||||||
|
config: CameraConfig,
|
||||||
|
hass?: HomeAssistant,
|
||||||
|
): CameraConfig {
|
||||||
|
const state =
|
||||||
|
hass && config.camera_entity ? hass.states[config.camera_entity] : undefined;
|
||||||
|
config.title =
|
||||||
|
config.title ??
|
||||||
|
(state?.attributes?.friendly_name || prettifyCameraName(config.camera_name || ''));
|
||||||
|
config.icon = config.icon ?? (state ? stateIcon(state) : 'mdi:video');
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
// TODO editor
|
// TODO editor
|
||||||
|
// TODO webrtc entities in camera section?
|
||||||
|
// TODO conditional elements based on camera name (requires event changed to propagate upwards)
|
||||||
|
// TODO change url to frigate_url?
|
||||||
|
// TODO Remove media load event warning
|
||||||
// TODO readme
|
// TODO readme
|
||||||
// TODO search for TODOs
|
// TODO search for TODOs
|
||||||
|
|
||||||
@@ -237,12 +241,10 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
|||||||
}
|
}
|
||||||
return Array.from(this.cameras.values()).map((cameraConfig, index) => {
|
return Array.from(this.cameras.values()).map((cameraConfig, index) => {
|
||||||
let refreshedConfig = { ...cameraConfig };
|
let refreshedConfig = { ...cameraConfig };
|
||||||
if (this.hass) {
|
|
||||||
refreshedConfig = refreshCameraConfigDynamicParameters(
|
refreshedConfig = refreshCameraConfigDynamicParameters(
|
||||||
this.hass,
|
|
||||||
refreshedConfig,
|
refreshedConfig,
|
||||||
|
this.hass,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return this._renderLive(refreshedConfig, index);
|
return this._renderLive(refreshedConfig, index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -303,13 +305,13 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
|||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
prev = this.cameras.get(keys[currentIndex - 1]) ?? null;
|
prev = this.cameras.get(keys[currentIndex - 1]) ?? null;
|
||||||
if (prev) {
|
if (prev) {
|
||||||
prev = refreshCameraConfigDynamicParameters(this.hass, { ...prev });
|
prev = refreshCameraConfigDynamicParameters({ ...prev }, this.hass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentIndex + 1 < this.cameras.size) {
|
if (currentIndex + 1 < this.cameras.size) {
|
||||||
next = this.cameras.get(keys[currentIndex + 1]) ?? null;
|
next = this.cameras.get(keys[currentIndex + 1]) ?? null;
|
||||||
if (next) {
|
if (next) {
|
||||||
next = refreshCameraConfigDynamicParameters(this.hass, { ...next });
|
next = refreshCameraConfigDynamicParameters({ ...next }, this.hass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [prev, next];
|
return [prev, next];
|
||||||
|
|||||||
@@ -154,7 +154,6 @@
|
|||||||
"upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor",
|
"upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor",
|
||||||
"missing_webrtc": "WebRTC component not found",
|
"missing_webrtc": "WebRTC component not found",
|
||||||
"no_cameras": "No cameras found, you must configure at least one camera configured with a camera_entity or camera_name",
|
"no_cameras": "No cameras found, you must configure at least one camera configured with a camera_entity or camera_name",
|
||||||
"duplicate_frigate_camera_name": "Duplicate Frigate camera name, use the 'id' parameter to uniquely identify them",
|
|
||||||
"could_not_render_elements": "Could not render picture elements",
|
"could_not_render_elements": "Could not render picture elements",
|
||||||
"invalid_elements_config": "Invalid picture elements configuration",
|
"invalid_elements_config": "Invalid picture elements configuration",
|
||||||
"jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path",
|
"jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path",
|
||||||
|
|||||||
+3
-3
@@ -368,12 +368,12 @@ const cameraConfigDefaultSchema = z
|
|||||||
// specified).
|
// specified).
|
||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|
||||||
id: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
.default(cameraConfigDefault);
|
.default(cameraConfigDefault);
|
||||||
export type CameraConfig = z.infer<typeof cameraConfigDefaultSchema>;
|
export type CameraConfig = z.infer<typeof cameraConfigDefaultSchema>;
|
||||||
|
|
||||||
|
const camerasConfigDefaultSchema = z.record(cameraConfigDefaultSchema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View configuration section.
|
* View configuration section.
|
||||||
*/
|
*/
|
||||||
@@ -650,7 +650,7 @@ const dimensionsConfigSchema = z
|
|||||||
*/
|
*/
|
||||||
export const frigateCardConfigSchema = z.object({
|
export const frigateCardConfigSchema = z.object({
|
||||||
// Main configuration sections.
|
// Main configuration sections.
|
||||||
camera: cameraConfigDefaultSchema.or(cameraConfigDefaultSchema.array().nonempty()),
|
cameras: camerasConfigDefaultSchema,
|
||||||
view: viewConfigSchema,
|
view: viewConfigSchema,
|
||||||
menu: menuConfigSchema,
|
menu: menuConfigSchema,
|
||||||
live: liveConfigSchema,
|
live: liveConfigSchema,
|
||||||
|
|||||||
Reference in New Issue
Block a user