Add support for multiple zones/labels per Frigate camera.
This commit is contained in:
1 parent
d9190ade01
commit
342d988bbd
10 files changed
+125
-63
No files matched your search
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,`,
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
|
||||
Reference in new issue
Block a user