Change configuration path for Frigate options.

This commit is contained in:
Dermot Duffy
2022-06-10 13:44:10 -07:00
parent 2d8f3adb7d
commit a62eaf4bc9
14 changed files with 247 additions and 173 deletions
+24 -21
View File
@@ -396,8 +396,8 @@ export class FrigateCard extends LitElement {
// Don't show `clips` button if there's no `camera_name` (e.g. non-Frigate
// cameras), or is birdseye (unless there are dependent cameras).
if (
cameraConfig?.camera_name &&
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig?.frigate.camera_name &&
(cameraConfig?.frigate.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig.dependencies.cameras.length ||
cameraConfig.dependencies.all_cameras)
) {
@@ -415,8 +415,8 @@ export class FrigateCard extends LitElement {
// Don't show `snapshots` button if there's no `camera_name` (e.g. non-Frigate
// cameras), or is birdseye (unless there are dependent cameras).
if (
cameraConfig?.camera_name &&
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig?.frigate.camera_name &&
(cameraConfig?.frigate.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig?.dependencies.cameras.length ||
cameraConfig?.dependencies.all_cameras)
) {
@@ -449,7 +449,8 @@ export class FrigateCard extends LitElement {
if (
this._cameras &&
[...this._cameras.values()].some(
(config) => config.camera_name && config.camera_name !== CAMERA_BIRDSEYE,
(config) =>
config.frigate.camera_name && config.frigate.camera_name !== CAMERA_BIRDSEYE,
)
) {
buttons.push({
@@ -475,7 +476,7 @@ export class FrigateCard extends LitElement {
});
}
if (cameraConfig?.frigate_url) {
if (cameraConfig?.frigate.url) {
buttons.push({
icon: 'mdi:web',
...this._getConfig().menu.buttons.frigate_ui,
@@ -598,13 +599,15 @@ export class FrigateCard extends LitElement {
cache: ExtendedEntityCache,
cameraConfig: CameraConfig,
): string | null {
if (cameraConfig.camera_name) {
if (cameraConfig.frigate.camera_name) {
return (
cache.getMatch(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:motion_sensor:${cameraConfig.zone || cameraConfig.camera_name}`,
`:motion_sensor:${
cameraConfig.frigate.zone || cameraConfig.frigate.camera_name
}`,
),
),
)?.entity_id ?? null
@@ -623,15 +626,15 @@ export class FrigateCard extends LitElement {
cache: ExtendedEntityCache,
cameraConfig: CameraConfig,
): string | null {
if (cameraConfig.camera_name) {
if (cameraConfig.frigate.camera_name) {
return (
cache.getMatch(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:occupancy_sensor:${cameraConfig.zone || cameraConfig.camera_name}_${
cameraConfig.label || 'all'
}`,
`:occupancy_sensor:${
cameraConfig.frigate.zone || cameraConfig.frigate.camera_name
}_${cameraConfig.frigate.label || 'all'}`,
),
),
)?.entity_id ?? null
@@ -673,10 +676,10 @@ export class FrigateCard extends LitElement {
}
}
if (!config.camera_name && entity) {
if (!config.frigate.camera_name && entity) {
const resolvedName = this._getFrigateCameraNameFromEntity(entity);
if (resolvedName) {
config.camera_name = resolvedName;
config.frigate.camera_name = resolvedName;
}
}
@@ -1146,7 +1149,7 @@ export class FrigateCard extends LitElement {
}
const path =
`/api/frigate/${cameraConfig.client_id}` +
`/api/frigate/${cameraConfig.frigate.client_id}` +
`/notifications/${event_id}/` +
`${this._view.isClipRelatedView() ? 'clip.mp4' : 'snapshot.jpg'}` +
`?download=true`;
@@ -1299,7 +1302,7 @@ export class FrigateCard extends LitElement {
: (this._getConfig().view.camera_select as FrigateCardView);
this._changeView({
view: new View({
view: this._cameras?.get(camera)?.camera_name
view: this._cameras?.get(camera)?.frigate.camera_name
? targetView
: // Fallback to supported views for non-Frigate cameras.
View.selectBestViewForNonFrigateCameras(targetView),
@@ -1368,16 +1371,16 @@ export class FrigateCard extends LitElement {
*/
protected _getFrigateURLFromContext(): string | null {
const cameraConfig = this._getSelectedCameraConfig();
if (!cameraConfig || !cameraConfig.frigate_url || !this._view) {
if (!cameraConfig || !cameraConfig.frigate.url || !this._view) {
return null;
}
if (!cameraConfig.camera_name) {
return cameraConfig.frigate_url;
if (!cameraConfig.frigate.camera_name) {
return cameraConfig.frigate.url;
}
if (this._view.isViewerView() || this._view.isGalleryView()) {
return `${cameraConfig.frigate_url}/events?camera=${cameraConfig.camera_name}`;
return `${cameraConfig.frigate.url}/events?camera=${cameraConfig.frigate.camera_name}`;
}
return `${cameraConfig.frigate_url}/cameras/${cameraConfig.camera_name}`;
return `${cameraConfig.frigate.url}/cameras/${cameraConfig.frigate.camera_name}`;
}
/**
+5 -5
View File
@@ -639,7 +639,7 @@ export class FrigateCardLiveProvider extends LitElement {
return 'webrtc-card';
} else if (this.cameraConfig?.camera_entity) {
return 'ha';
} else if (this.cameraConfig?.camera_name) {
} else if (this.cameraConfig?.frigate.camera_name) {
return 'frigate-jsmpeg';
}
return frigateCardConfigDefaults.cameras.live_provider;
@@ -1013,7 +1013,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
* @returns A URL or null.
*/
protected async _getURL(): Promise<string | null> {
if (!this.hass || !this.cameraConfig?.client_id || !this.cameraConfig?.camera_name) {
if (!this.hass || !this.cameraConfig?.frigate.client_id || !this.cameraConfig?.frigate.camera_name) {
return null;
}
@@ -1021,8 +1021,8 @@ export class FrigateCardLiveJSMPEG extends LitElement {
try {
response = await homeAssistantSignPath(
this.hass,
`/api/frigate/${this.cameraConfig.client_id}` +
`/jsmpeg/${this.cameraConfig.camera_name}`,
`/api/frigate/${this.cameraConfig.frigate.client_id}` +
`/jsmpeg/${this.cameraConfig.frigate.camera_name}`,
URL_SIGN_EXPIRY_SECONDS,
);
} catch (err) {
@@ -1131,7 +1131,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
this._jsmpegCanvasElement = document.createElement('canvas');
this._jsmpegCanvasElement.className = 'media';
if (!this.cameraConfig?.camera_name) {
if (!this.cameraConfig?.frigate.camera_name) {
return dispatchErrorMessageEvent(
this,
localize('error.no_camera_name'),
+2 -21
View File
@@ -27,30 +27,11 @@ import {
frigateCardHasAction,
getActionConfigGivenAction
} from '../utils/action.js';
import { FRIGATE_ICON_SVG_PATH } from '../utils/frigate.js';
import { refreshDynamicStateParameters } from '../utils/ha';
import './submenu.js';
export const FRIGATE_BUTTON_MENU_ICON = 'frigate';
export const FRIGATE_ICON_FILLED =
'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' +
'-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' +
'-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' +
'5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' +
'1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' +
'-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' +
'-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' +
'-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' +
'-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' +
'-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' +
'0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' +
'0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' +
'0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' +
'0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' +
'-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' +
'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' +
'-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' +
'-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' +
'-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z';
/**
* A menu for the FrigateCard.
@@ -266,7 +247,7 @@ export class FrigateCardMenu extends LitElement {
let stateParameters: StateParameters = { ...button };
const svgPath =
stateParameters.icon === FRIGATE_BUTTON_MENU_ICON ? FRIGATE_ICON_FILLED : '';
stateParameters.icon === FRIGATE_BUTTON_MENU_ICON ? FRIGATE_ICON_SVG_PATH : '';
if (this.hass && button.type === 'custom:frigate-card-menu-state-icon') {
stateParameters = refreshDynamicStateParameters(this.hass, stateParameters);
+26 -15
View File
@@ -324,12 +324,16 @@ class TimelineDataManager {
camera: string,
config: CameraConfig,
): Promise<void> => {
if (!config.camera_name) {
if (!config.frigate.camera_name) {
return;
}
let summary: RecordingSummary;
try {
summary = await getRecordingsSummary(hass, config.client_id, config.camera_name);
summary = await getRecordingsSummary(
hass,
config.frigate.client_id,
config.frigate.camera_name,
);
} catch (e) {
return dispatchFrigateCardErrorEvent(element, e as FrigateCardError);
}
@@ -382,7 +386,7 @@ class TimelineDataManager {
const params: BrowseMediaQueryParameters[] = [];
cameras.forEach((cameraConfig, cameraID) => {
(media === 'all' ? ['clips', 'snapshots'] : [media]).forEach((mediaType) => {
if (cameraConfig.camera_name !== CAMERA_BIRDSEYE) {
if (cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE) {
const param = getBrowseMediaQueryParameters(hass, cameraID, cameraConfig, {
before: end.getTime() / 1000,
after: start.getTime() / 1000,
@@ -584,10 +588,10 @@ export class FrigateCardTimelineCore extends LitElement {
const processedCameras: Set<string> = new Set();
// Get results in the order the cameras are specified in the configuration.
for (const camera of (this.cameras?.keys() || [])) {
for (const camera of this.cameras?.keys() || []) {
const recording = results.get(camera);
const config = this.cameras?.get(camera);
if (!recording || !config?.camera_name) {
if (!recording || !config?.frigate.camera_name) {
continue;
}
@@ -595,7 +599,7 @@ export class FrigateCardTimelineCore extends LitElement {
// Zones on that same camera do not get separate recordings. The card may
// have multiple instances of the same camera for different zoness, so
// need to enforce uniqueness here.
const uniqueID = `${config.client_id}/${config.camera_name}`;
const uniqueID = `${config.frigate.client_id}/${config.frigate.camera_name}`;
if (processedCameras.has(uniqueID)) {
continue;
}
@@ -616,21 +620,21 @@ export class FrigateCardTimelineCore extends LitElement {
if (!onlyMatchingHour || isMatchingHour) {
children.push(
createVideoChild(
`${prettifyTitle(config.camera_name)} ${format(
`${prettifyTitle(config.frigate.camera_name)} ${format(
hour,
'yyyy-MM-dd HH:mm',
)}`,
generateRecordingIdentifier({
clientId: config.client_id,
clientId: config.frigate.client_id,
year: dayData.day.getFullYear(),
month: dayData.day.getMonth() + 1,
day: dayData.day.getDate(),
hour: hourData.hour,
cameraName: config.camera_name,
cameraName: config.frigate.camera_name,
}),
{
recording: {
camera: config.camera_name,
camera: config.frigate.camera_name,
start_time: getUnixTime(startHour),
end_time: getUnixTime(endHour),
events: hourData.events,
@@ -665,7 +669,7 @@ export class FrigateCardTimelineCore extends LitElement {
const results: Map<string, CameraRecordings> = new Map();
const fetch = async (camera: string, config?: CameraConfig): Promise<void> => {
if (!config || !config.camera_name || !this.hass) {
if (!config || !config.frigate.camera_name || !this.hass) {
return;
}
@@ -673,12 +677,16 @@ export class FrigateCardTimelineCore extends LitElement {
const cameraResults = await Promise.all([
getRecordingSegments(
this.hass,
config.client_id,
config.camera_name,
config.frigate.client_id,
config.frigate.camera_name,
before,
after,
),
getRecordingsSummary(this.hass, config.client_id, config.camera_name),
getRecordingsSummary(
this.hass,
config.frigate.client_id,
config.frigate.camera_name,
),
]);
results.set(camera, { segments: cameraResults[0], summary: cameraResults[1] });
} catch (e) {}
@@ -879,7 +887,10 @@ export class FrigateCardTimelineCore extends LitElement {
protected _getGroups(): DataGroupCollectionType {
const groups: FrigateCardGroupData[] = [];
this.cameras?.forEach((cameraConfig, camera) => {
if (cameraConfig.camera_name && cameraConfig.camera_name !== CAMERA_BIRDSEYE) {
if (
cameraConfig.frigate.camera_name &&
cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE
) {
groups.push({
id: camera,
content: getCameraTitle(this.hass, cameraConfig),
+10 -10
View File
@@ -2,12 +2,7 @@ import { cloneDeep, get, isEqual, set } from 'lodash-es';
import {
CONF_CAMERAS,
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
CONF_CAMERAS_ARRAY_CAMERA_NAME,
CONF_CAMERAS_ARRAY_CLIENT_ID,
CONF_CAMERAS_ARRAY_LABEL,
CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
CONF_CAMERAS_ARRAY_URL,
CONF_CAMERAS_ARRAY_ZONE,
CONF_IMAGE_URL,
CONF_LIVE_AUTO_UNMUTE,
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
@@ -378,11 +373,11 @@ const upgradeToMultipleCameras = (): ((obj: RawFrigateCardConfig) => boolean) =>
const imports = {
camera_entity: CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
'frigate.camera_name': CONF_CAMERAS_ARRAY_CAMERA_NAME,
'frigate.client_id': CONF_CAMERAS_ARRAY_CLIENT_ID,
'frigate.label': CONF_CAMERAS_ARRAY_LABEL,
'frigate.url': CONF_CAMERAS_ARRAY_URL,
'frigate.zone': CONF_CAMERAS_ARRAY_ZONE,
'frigate.camera_name': 'cameras.#.camera_name',
'frigate.client_id': 'cameras.#.client_id',
'frigate.label': 'cameras.#.label',
'frigate.url': 'cameras.#.frigate_url',
'frigate.zone': 'cameras.#.zone',
'live.webrtc.entity': `cameras.#.webrtc.entity`,
'live.webrtc.url': `cameras.#.webrtc.url`,
'live.provider': CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
@@ -600,4 +595,9 @@ const UPGRADES = [
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgradeMoveToWithOverrides('event_viewer', CONF_MEDIA_VIEWER),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('camera_name', 'frigate.camera_name')),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('client_id', 'frigate.client_id')),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('label', 'frigate.label')),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('frigate_url', 'frigate.url')),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('zone', 'frigate.zone')),
];
+5 -5
View File
@@ -7,11 +7,11 @@ export const TROUBLESHOOTING_URL = `${REPO_URL}#troubleshooting` as const;
export const CONF_CAMERAS = 'cameras' as const;
export const CONF_CAMERAS_ARRAY_CAMERA_ENTITY =
`${CONF_CAMERAS}.#.camera_entity` as const;
export const CONF_CAMERAS_ARRAY_CAMERA_NAME = `${CONF_CAMERAS}.#.camera_name` as const;
export const CONF_CAMERAS_ARRAY_CLIENT_ID = `${CONF_CAMERAS}.#.client_id` as const;
export const CONF_CAMERAS_ARRAY_LABEL = `${CONF_CAMERAS}.#.label` as const;
export const CONF_CAMERAS_ARRAY_URL = `${CONF_CAMERAS}.#.frigate_url` as const;
export const CONF_CAMERAS_ARRAY_ZONE = `${CONF_CAMERAS}.#.zone` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME = `${CONF_CAMERAS}.#.frigate.camera_name` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID = `${CONF_CAMERAS}.#.frigate.client_id` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_LABEL = `${CONF_CAMERAS}.#.frigate.label` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_URL = `${CONF_CAMERAS}.#.frigate.url` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_ZONE = `${CONF_CAMERAS}.#.frigate.zone` as const;
export const CONF_CAMERAS_ARRAY_ID = `${CONF_CAMERAS}.#.id` as const;
export const CONF_CAMERAS_ARRAY_TITLE = `${CONF_CAMERAS}.#.title` as const;
export const CONF_CAMERAS_ARRAY_ICON = `${CONF_CAMERAS}.#.icon` as const;
+65 -42
View File
@@ -14,22 +14,22 @@ import {
import {
CONF_CAMERAS,
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
CONF_CAMERAS_ARRAY_CAMERA_NAME,
CONF_CAMERAS_ARRAY_CLIENT_ID,
CONF_CAMERAS_ARRAY_DEPENDENCIES_ALL_CAMERAS,
CONF_CAMERAS_ARRAY_DEPENDENCIES_CAMERAS,
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
CONF_CAMERAS_ARRAY_FRIGATE_LABEL,
CONF_CAMERAS_ARRAY_FRIGATE_URL,
CONF_CAMERAS_ARRAY_FRIGATE_ZONE,
CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_LABEL,
CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
CONF_CAMERAS_ARRAY_TITLE,
CONF_CAMERAS_ARRAY_TRIGGERS_ENTITIES,
CONF_CAMERAS_ARRAY_TRIGGERS_MOTION,
CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY,
CONF_CAMERAS_ARRAY_URL,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL,
CONF_CAMERAS_ARRAY_ZONE,
CONF_DIMENSIONS_ASPECT_RATIO,
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_CONTROLS,
@@ -110,11 +110,13 @@ import {
} from './types.js';
import { arrayMove } from './utils/basic.js';
import { getCameraID, getCameraTitle } from './utils/camera.js';
import { FRIGATE_ICON_SVG_PATH } from './utils/frigate.js';
import { getEntitiesFromHASS, sideLoadHomeAssistantElements } from './utils/ha';
const MENU_BUTTONS = 'buttons';
const MENU_CAMERAS = 'cameras';
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
const MENU_CAMERAS_WEBRTC = 'cameras.webrtc';
const MENU_OPTIONS = 'options';
@@ -721,8 +723,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
protected _putInSubmenu(
domain: string,
key: unknown,
icon: string,
labelPath: string,
icon: {
name?: string;
path?: string;
},
template: TemplateResult,
): TemplateResult {
const submenuClasses = {
@@ -737,15 +742,16 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
.domain=${domain}
.key=${key}
>
<ha-icon .icon=${icon}></ha-icon>
<span>${localize(labelPath)}</span>
</div>
${
this._expandedMenus[domain] === key
? html`<div class="values">${template}</div>`
: ''
}
${icon.name
? html` <ha-icon .icon=${icon.name}></ha-icon> `
: icon.path
? html` <ha-svg-icon .path="${icon.path}"></ha-svg-icon> `
: ``}
<span>${localize(labelPath)}</span>
</div>
${this._expandedMenus[domain] === key
? html`<div class="values">${template}</div>`
: ''}
</div>`;
}
@@ -891,52 +897,56 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
getArrayConfigPath(CONF_CAMERAS_ARRAY_CAMERA_ENTITY, cameraIndex),
'camera',
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_CAMERA_NAME, cameraIndex),
)}
${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_LIVE_PROVIDER, cameraIndex),
liveProviders,
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_URL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_LABEL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_ZONE, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_CLIENT_ID, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_TITLE, cameraIndex),
)}
${this._renderStringInput(
${this._renderIconSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_ICON, cameraIndex),
{
label: localize('config.cameras.icon'),
},
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_ID, cameraIndex),
)}
${this._putInSubmenu(
MENU_CAMERAS_WEBRTC,
MENU_CAMERAS_FRIGATE,
cameraIndex,
'mdi:webrtc',
'config.cameras.webrtc_card.options',
html`${this._renderEntitySelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY, cameraIndex),
'camera',
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)}`,
'config.cameras.frigate.options',
{ path: FRIGATE_ICON_SVG_PATH },
html`
${this._renderStringInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
cameraIndex,
),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_URL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_LABEL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_ZONE, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
cameraIndex,
),
)}
`,
)}
${this._putInSubmenu(
MENU_CAMERAS_DEPENDENCIES,
cameraIndex,
'mdi:graph',
'config.cameras.dependencies.options',
{ name: 'mdi:graph' },
html` ${this._renderSwitch(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DEPENDENCIES_ALL_CAMERAS,
@@ -958,8 +968,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._putInSubmenu(
MENU_CAMERAS_TRIGGERS,
cameraIndex,
'mdi:magnify-scan',
'config.cameras.triggers.options',
{ name: 'mdi:magnify-scan' },
html` ${this._renderSwitch(
getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY, cameraIndex),
frigateCardConfigDefaults.cameras.triggers.occupancy,
@@ -976,6 +986,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
},
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_WEBRTC,
cameraIndex,
'config.cameras.webrtc_card.options',
{ name: 'mdi:webrtc' },
html`${this._renderEntitySelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY, cameraIndex),
'camera',
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)}`,
)}
</div>`
: ``}
</div>
+12 -9
View File
@@ -13,18 +13,22 @@
"config": {
"cameras": {
"camera_entity": "Camera Entity",
"camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)",
"icon": "Icon for this camera (Autodetected from entity)",
"id": "Unique id for this camera in this card",
"title": "Title for this camera (Autodetected from entity)",
"dependencies": {
"options": "Dependency Options",
"cameras": "Show events for specific cameras with this camera",
"all_cameras": "Show events for all cameras with this camera"
},
"id": "Unique id for this camera in this card",
"label": "Frigate label/object filter",
"frigate_url": "Frigate server URL",
"title": "Title for this camera (Autodetected from entity)",
"icon": "Icon for this camera (Autodetected from entity)",
"frigate": {
"options": "Frigate Options",
"camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)",
"label": "Frigate label/object filter",
"url": "Frigate server URL",
"zone": "Frigate zone"
},
"live_provider": "Live view provider for this camera",
"live_providers": {
"auto": "Automatic",
@@ -42,8 +46,7 @@
"options": "WebRTC Card Options",
"entity": "WebRTC Card Camera Entity (Not a Frigate camera)",
"url": "WebRTC Card Camera URL"
},
"zone": "Frigate zone"
}
},
"common": {
"media_action_conditions": {
+2 -2
View File
@@ -62,7 +62,8 @@ div.upgrade span {
color: var(--secondary-text-color, 'black');
}
.submenu-header ha-icon {
.submenu-header ha-icon,
.submenu-header ha-svg-icon {
padding-right: 15px;
}
@@ -78,7 +79,6 @@ div.upgrade span {
width: calc(100% - 10px * 2);
margin: auto;
margin-bottom: 10px;
}
.submenu :not(.submenu.selected) {
border: none;
+12 -7
View File
@@ -370,8 +370,10 @@ const customSchema = z
* Camera configuration section
*/
export const cameraConfigDefault = {
client_id: 'frigate' as const,
live_provider: 'auto' as const,
frigate: {
client_id: 'frigate' as const,
},
dependencies: {
all_cameras: false,
cameras: [],
@@ -388,12 +390,6 @@ const webrtcCardCameraConfigSchema = z.object({
});
const cameraConfigSchema = z
.object({
// No URL validation to allow relative URLs within HA (e.g. Frigate addon).
frigate_url: z.string().optional(),
client_id: z.string().default(cameraConfigDefault.client_id),
camera_name: z.string().optional(),
label: z.string().optional(),
zone: z.string().optional(),
camera_entity: z.string().optional(),
live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider),
@@ -405,6 +401,15 @@ const cameraConfigSchema = z
// Optional identifier to separate different camera configurations used in
// this card.
id: z.string().optional(),
frigate: z.object({
// No URL validation to allow relative URLs within HA (e.g. Frigate addon).
url: z.string().optional(),
client_id: z.string().default(cameraConfigDefault.frigate.client_id),
camera_name: z.string().optional(),
label: z.string().optional(),
zone: z.string().optional(),
}).default(cameraConfigDefault.frigate),
// Camera identifiers for WebRTC.
webrtc_card: webrtcCardCameraConfigSchema.optional(),
+10 -2
View File
@@ -18,7 +18,10 @@ export function getCameraID(
config.webrtc_card &&
typeof config.webrtc_card['entity'] === 'string' &&
config.webrtc_card['entity']) ||
(typeof config?.camera_name === 'string' && config.camera_name) ||
(typeof config?.frigate === 'object' &&
config.frigate &&
typeof config?.frigate['camera_name'] === 'string' &&
config.frigate['camera_name']) ||
''
);
}
@@ -46,7 +49,12 @@ export function getCameraTitle(
config.webrtc_card &&
typeof config.webrtc_card['entity'] === 'string' &&
config.webrtc_card['entity']) ||
(typeof config?.camera_name === 'string' ? prettifyTitle(config.camera_name) : '') ||
(typeof config?.frigate === 'object' &&
config.frigate &&
typeof config?.frigate['camera_name'] === 'string' &&
config.frigate['camera_name']
? prettifyTitle(config.frigate['camera_name'])
: '') ||
(typeof config?.id === 'string' && config.id) ||
''
);
+21
View File
@@ -2,6 +2,27 @@ import { z } from 'zod';
import { ExtendedHomeAssistant } from '../types';
import { homeAssistantHTTPRequest } from './ha';
export const FRIGATE_ICON_SVG_PATH =
'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' +
'-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' +
'-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' +
'5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' +
'1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' +
'-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' +
'-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' +
'-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' +
'-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' +
'-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' +
'0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' +
'0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' +
'0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' +
'0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' +
'-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' +
'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' +
'-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' +
'-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' +
'-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z';
const recordingSummaryHourSchema = z.object({
hour: z.preprocess((arg) => Number(arg), z.number().min(0).max(23)),
duration: z.number().min(0),
+5 -5
View File
@@ -214,14 +214,14 @@ export const getBrowseMediaQueryParameters = (
cameraConfig?: CameraConfig,
overrides?: Partial<BrowseMediaQueryParameters>,
): BrowseMediaQueryParameters | null => {
if (!cameraConfig || !cameraConfig.camera_name) {
if (!cameraConfig || !cameraConfig.frigate.camera_name) {
return null;
}
return {
clientId: cameraConfig.client_id,
cameraName: cameraConfig.camera_name,
label: cameraConfig.label,
zone: cameraConfig.zone,
clientId: cameraConfig.frigate.client_id,
cameraName: cameraConfig.frigate.camera_name,
label: cameraConfig.frigate.label,
zone: cameraConfig.frigate.zone,
title: getCameraTitle(hass, cameraConfig),
cameraID: cameraID,
...overrides,