Add support for multiple zones/labels per Frigate camera.

This commit is contained in:
Dermot Duffy committed 2023-03-12 15:35:53 -07:00
1 parent d9190ade01
commit 342d988bbd
10 files changed
+125 -63

No files matched your search

+53 -27
View File
@@ -56,8 +56,10 @@ import {
NativeFrigateRecordingSegmentsQuery,
retainEvent,
} from './requests';
import isEqual from 'lodash-es/isEqual';
import orderBy from 'lodash-es/orderBy';
import throttle from 'lodash-es/throttle';
import uniqWith from 'lodash-es/uniqWith';
import {
allPromises,
formatDate,
@@ -194,11 +196,11 @@ export class FrigateCameraManagerEngine
}
if (cameraConfig.triggers.occupancy) {
const occupancyEntity = this._getOccupancySensor(cameraConfig, [
const occupancyEntities = this._getOccupancySensor(cameraConfig, [
...binarySensorEntities.values(),
]);
if (occupancyEntity) {
cameraConfig.triggers.entities.push(occupancyEntity);
if (occupancyEntities) {
cameraConfig.triggers.entities.push(...occupancyEntities);
}
}
@@ -238,11 +240,7 @@ export class FrigateCameraManagerEngine
entities.find(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:motion_sensor:${
cameraConfig.frigate.zone || cameraConfig.frigate.camera_name
}`,
),
new RegExp(`:motion_sensor:${cameraConfig.frigate.camera_name}`),
),
)?.entity_id ?? null
);
@@ -259,20 +257,39 @@ export class FrigateCameraManagerEngine
protected _getOccupancySensor(
cameraConfig: CameraConfig,
entities: Entity[],
): string | null {
if (cameraConfig.frigate.camera_name) {
return (
): string[] | null {
const entityIDs: string[] = [];
const addEntityIDIfFound = (cameraOrZone: string, label: string): void => {
const entityID =
entities.find(
(ent) =>
!!ent.unique_id?.match(
new RegExp(
`:occupancy_sensor:${
cameraConfig.frigate.zone || cameraConfig.frigate.camera_name
}_${cameraConfig.frigate.label || 'all'}`,
),
new RegExp(`:occupancy_sensor:${cameraOrZone}_${label}`),
),
)?.entity_id ?? null
);
)?.entity_id ?? null;
if (entityID) {
entityIDs.push(entityID);
}
};
if (cameraConfig.frigate.camera_name) {
// If zone(s) are specified, the master occupancy sensor for the overall
// camera is not used by default (but could be manually added by the
// user).
const camerasAndZones = cameraConfig.frigate.zones
? cameraConfig.frigate.zones
: [cameraConfig.frigate.camera_name];
const labels = cameraConfig.frigate.labels ? cameraConfig.frigate.labels : ['all'];
for (const cameraOrZone of camerasAndZones) {
for (const label of labels) {
addEntityIDIfFound(cameraOrZone, label);
}
}
if (entityIDs.length) {
return entityIDs;
}
}
return null;
}
@@ -308,17 +325,26 @@ export class FrigateCameraManagerEngine
cameras.get(cameraID),
);
// If there isn't a label or zone specified, we can come up with a single
// batch query for Frigate that will match across all cameras.
const canDoBatchQuery = relevantCameraConfigs.every(
(cameraConfig) => !cameraConfig?.frigate.label && !cameraConfig?.frigate.zone,
// If all cameras specify exactly the same zones or labels (incl. none), we
// can use a single batch query which will be better performance wise,
// otherwise we must fan out to multiple queries in order to precisely match
// the user's intent.
const uniqueZoneArrays = uniqWith(
relevantCameraConfigs.map((config) => config?.frigate.zones),
isEqual,
);
const uniqueLabelArrays = uniqWith(
relevantCameraConfigs.map((config) => config?.frigate.labels),
isEqual,
);
if (canDoBatchQuery) {
if (uniqueZoneArrays.length === 1 && uniqueLabelArrays.length === 1) {
return [
{
type: QueryType.Event,
cameraIDs: cameraIDs,
...(uniqueLabelArrays[0] && { what: new Set(uniqueLabelArrays[0]) }),
...(uniqueZoneArrays[0] && { where: new Set(uniqueZoneArrays[0]) }),
...query,
},
];
@@ -331,11 +357,11 @@ export class FrigateCameraManagerEngine
output.push({
type: QueryType.Event,
cameraIDs: new Set([cameraID]),
...(cameraConfig.frigate.label && {
what: new Set([cameraConfig.frigate.label]),
...(cameraConfig.frigate.labels && {
what: new Set(cameraConfig.frigate.labels),
}),
...(cameraConfig.frigate.zone && {
where: new Set([cameraConfig.frigate.zone]),
...(cameraConfig.frigate.zones && {
where: new Set(cameraConfig.frigate.zones),
}),
...query,
});
+1 -1
View File
@@ -233,7 +233,7 @@ export class CameraManager {
log(
this._cardWideConfig,
'Frigate Card CameraManager initialized (Cameras: ',
camerasConfig,
this._store.getCameras(),
`, Duration: ${
(new Date().getTime() - initializationStartTime.getTime()) / 1000
}s,`,
+13
View File
@@ -34,6 +34,7 @@ import {
THUMBNAIL_WIDTH_MAX,
THUMBNAIL_WIDTH_MIN,
} from './types';
import { arrayify } from './utils/basic';
/**
* Set a configuration value.
@@ -588,4 +589,16 @@ const UPGRADES = [
upgradeMoveToWithOverrides('live.image', CONF_CAMERAS_GLOBAL_IMAGE),
upgradeMoveToWithOverrides('live.jsmpeg', CONF_CAMERAS_GLOBAL_JSMPEG),
upgradeMoveToWithOverrides('live.webrtc_card', CONF_CAMERAS_GLOBAL_WEBRTC_CARD),
upgradeArrayValue(
CONF_CAMERAS,
upgradeMoveToWithOverrides('frigate.zone', 'frigate.zones', {
transform: (zone) => arrayify(zone),
}),
),
upgradeArrayValue(
CONF_CAMERAS,
upgradeMoveToWithOverrides('frigate.label', 'frigate.labels', {
transform: (label) => arrayify(label),
}),
),
];
+3 -3
View File
@@ -8,10 +8,10 @@ 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_LABELS =
`${CONF_CAMERAS}.#.frigate.labels` 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_FRIGATE_ZONES = `${CONF_CAMERAS}.#.frigate.zones` as const;
export const CONF_CAMERAS_ARRAY_GO2RTC_MODES = `${CONF_CAMERAS}.#.go2rtc.modes` as const;
export const CONF_CAMERAS_ARRAY_GO2RTC_STREAM =
`${CONF_CAMERAS}.#.go2rtc.stream` as const;
+24 -9
View File
@@ -18,9 +18,9 @@ import {
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_LABELS,
CONF_CAMERAS_ARRAY_FRIGATE_URL,
CONF_CAMERAS_ARRAY_FRIGATE_ZONE,
CONF_CAMERAS_ARRAY_FRIGATE_ZONES,
CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
@@ -620,7 +620,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
*/
protected _renderOptionSelector(
configPath: string,
options: string[] | { value: string; label: string }[],
options: string[] | { value: string; label: string }[] = [],
params?: {
multiple?: boolean;
label?: string;
@@ -634,7 +634,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
<ha-selector
.hass=${this.hass}
.selector=${{
select: { mode: 'dropdown', multiple: !!params?.multiple, options: options },
select: {
mode: 'dropdown',
multiple: !!params?.multiple,
custom_value: !options.length,
options: options,
},
}}
.label=${params?.label || this._getLabel(configPath)}
.value=${getConfigValue(this._config, configPath, '')}
@@ -1370,11 +1375,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_URL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_LABEL, cameraIndex),
${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_LABELS, cameraIndex),
[],
{
multiple: true,
label: localize('config.cameras.frigate.labels'),
},
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_ZONE, cameraIndex),
${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_ZONES, cameraIndex),
[],
{
multiple: true,
label: localize('config.cameras.frigate.zones'),
},
)}
${this._renderStringInput(
getArrayConfigPath(
@@ -1657,7 +1672,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderMenuButton('download')}
${this._renderMenuButton('camera_ui')}
${this._renderMenuButton('fullscreen')}
${this._renderMenuButton('expand')}
${this._renderMenuButton('expand') /* */}
${this._renderMenuButton('timeline')}
${this._renderMenuButton('media_player')}
</div>
+2 -2
View File
@@ -21,10 +21,10 @@
"frigate": {
"camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)",
"label": "Frigate label/object filter",
"labels": "Frigate labels/object filters",
"editor_label": "Frigate Options",
"url": "Frigate server URL",
"zone": "Frigate zone"
"zones": "Frigate zones"
},
"go2rtc": {
"editor_label": "go2rtc Options",
+2 -2
View File
@@ -21,10 +21,10 @@
"frigate": {
"camera_name": "Nome della telecamera frigate (autodificato dall'entità)",
"client_id": "ID client Frigate (per > 1 Frigate server)",
"label": "Filtro etichetta/oggetto Frigate",
"labels": "",
"editor_label": "Frigate Opzione",
"url": "Frigate URL del server",
"zone": "Frigate zona"
"zones": ""
},
"go2rtc": {
"editor_label": "",
+2 -2
View File
@@ -21,10 +21,10 @@
"frigate": {
"camera_name": "Nome da câmera do Frigate (detectado automaticamente pela entidade)",
"client_id": "ID do cliente do Frigate (para >1 servidor Frigate)",
"label": "Filtro de rótulo/objeto do Frigate",
"label": "",
"editor_label": "Opções do Frigate",
"url": "URL do servidor Frigate",
"zone": "Zona do Frigate"
"zone": ""
},
"go2rtc": {
"editor_label": "",
+2 -2
View File
@@ -506,8 +506,8 @@ const cameraConfigSchema = z
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(),
labels: z.string().array().optional(),
zones: z.string().array().optional(),
})
.default(cameraConfigDefault.frigate),