Add shortcut for a camera to depend on all others.

This commit is contained in:
Dermot Duffy
2022-06-08 21:45:11 -07:00
parent a2e1ad161b
commit 40be446c69
7 changed files with 41 additions and 17 deletions
+3
View File
@@ -114,6 +114,7 @@ See the [fully expanded cameras configuration example](#config-expanded-cameras)
| `webrtc_card` | | :heavy_multiplication_x: | The WebRTC entity/URL to use for this camera with the `webrtc-card` live provider. See below. | | `webrtc_card` | | :heavy_multiplication_x: | The WebRTC entity/URL to use for this camera with the `webrtc-card` live provider. See below. |
| `id` | `camera_entity`, `webrtc_card.entity` or `camera_name` if set (in that preference order). | :heavy_multiplication_x: | An optional identifier to use throughout the card configuration to refer unambiguously to this camera. See [camera IDs](#camera-ids). | | `id` | `camera_entity`, `webrtc_card.entity` or `camera_name` if set (in that preference order). | :heavy_multiplication_x: | An optional identifier to use throughout the card configuration to refer unambiguously to this camera. See [camera IDs](#camera-ids). |
| `dependent_cameras` | | :heavy_multiplication_x: | An optional array of other camera identifiers (see [camera IDs](#camera-ids)). If specified the card will fetch events for this camera and *also* recursively events for the named `dependent_cameras`. All `dependent_cameras` must themselves be a configured camera in the card. This can be useful to group events for cameras that are close together, or to show events for the `birdseye` camera that otherwise would not have events itself.| | `dependent_cameras` | | :heavy_multiplication_x: | An optional array of other camera identifiers (see [camera IDs](#camera-ids)). If specified the card will fetch events for this camera and *also* recursively events for the named `dependent_cameras`. All `dependent_cameras` must themselves be a configured camera in the card. This can be useful to group events for cameras that are close together, or to show events for the `birdseye` camera that otherwise would not have events itself.|
| `dependent_cameras_all` | `false` | :heavy_multiplication_x: | Shortcut to specify all other cameras as dependent cameras (see `dependent_cameras` above).|
| `trigger_by_motion` | `false` | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) by automatically detecting and using the motion `binary_sensor` for this camera. This autodetection only works for Frigate cameras, and only when the motion `binary_sensor` entity has been enabled in Home Assistant.| | `trigger_by_motion` | `false` | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) by automatically detecting and using the motion `binary_sensor` for this camera. This autodetection only works for Frigate cameras, and only when the motion `binary_sensor` entity has been enabled in Home Assistant.|
| `trigger_by_occupancy` | `true` | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) by automatically detecting and using the occupancy `binary_sensor` for this camera. This autodetection only works for Frigate cameras, and only when the occupancy `binary_sensor` entity has been enabled in Home Assistant.| | `trigger_by_occupancy` | `true` | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) by automatically detecting and using the occupancy `binary_sensor` for this camera. This autodetection only works for Frigate cameras, and only when the occupancy `binary_sensor` entity has been enabled in Home Assistant.|
| `trigger_by_entities` | | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) when the state of any Home Assistant entity becomes active (i.e. state becomes `on` or `open`). This works for Frigate or non-Frigate cameras.| | `trigger_by_entities` | | :heavy_multiplication_x: | Whether to not to trigger the camera (used to trigger [scan mode](#scan-mode) or reseting the default view) when the state of any Home Assistant entity becomes active (i.e. state becomes `on` or `open`). This works for Frigate or non-Frigate cameras.|
@@ -1013,6 +1014,7 @@ cameras:
# Show events for camera-2 when this camera is viewed. # Show events for camera-2 when this camera is viewed.
dependent_cameras: dependent_cameras:
- camera-2 - camera-2
dependent_cameras_all: false
trigger_by_motion: false trigger_by_motion: false
trigger_by_occupancy: true trigger_by_occupancy: true
trigger_by_entities: trigger_by_entities:
@@ -1035,6 +1037,7 @@ cameras:
trigger_by_occupancy: true trigger_by_occupancy: true
trigger_by_entities: trigger_by_entities:
- binary_sensor.entrance_sensor - binary_sensor.entrance_sensor
dependent_cameras_all: false
``` ```
</details> </details>
+11 -9
View File
@@ -5,7 +5,7 @@ import {
LitElement, LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
unsafeCSS, unsafeCSS
} from 'lit'; } from 'lit';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
@@ -20,7 +20,7 @@ import {
ConditionState, ConditionState,
conditionStateRequestHandler, conditionStateRequestHandler,
getOverriddenConfig, getOverriddenConfig,
getOverridesByKey, getOverridesByKey
} from './card-condition.js'; } from './card-condition.js';
import './components/elements.js'; import './components/elements.js';
import { FrigateCardElements } from './components/elements.js'; import { FrigateCardElements } from './components/elements.js';
@@ -40,7 +40,7 @@ import {
CAMERA_BIRDSEYE, CAMERA_BIRDSEYE,
CARD_VERSION, CARD_VERSION,
MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA,
REPO_URL, REPO_URL
} from './const.js'; } from './const.js';
import './editor.js'; import './editor.js';
import { localize } from './localize/localize.js'; import { localize } from './localize/localize.js';
@@ -63,14 +63,14 @@ import {
MediaShowInfo, MediaShowInfo,
MenuButton, MenuButton,
Message, Message,
RawFrigateCardConfig, RawFrigateCardConfig
} from './types.js'; } from './types.js';
import { import {
convertActionToFrigateCardCustomAction, convertActionToFrigateCardCustomAction,
createFrigateCardCustomAction, createFrigateCardCustomAction,
frigateCardHandleAction, frigateCardHandleAction,
frigateCardHasAction, frigateCardHasAction,
getActionConfigGivenAction, getActionConfigGivenAction
} from './utils/action.js'; } from './utils/action.js';
import { contentsChanged } from './utils/basic.js'; import { contentsChanged } from './utils/basic.js';
import { getCameraIcon, getCameraID, getCameraTitle } from './utils/camera.js'; import { getCameraIcon, getCameraID, getCameraTitle } from './utils/camera.js';
@@ -81,7 +81,7 @@ import {
homeAssistantSignPath, homeAssistantSignPath,
isHassDifferent, isHassDifferent,
isTriggeredState, isTriggeredState,
sideLoadHomeAssistantElements, sideLoadHomeAssistantElements
} from './utils/ha'; } from './utils/ha';
import { getEventID } from './utils/ha/browse-media.js'; import { getEventID } from './utils/ha/browse-media.js';
import { DeviceList, getAllDevices } from './utils/ha/device-registry.js'; import { DeviceList, getAllDevices } from './utils/ha/device-registry.js';
@@ -89,7 +89,7 @@ import {
ExtendedEntityCache, ExtendedEntityCache,
getAllEntities, getAllEntities,
getExtendedEntities, getExtendedEntities,
getExtendedEntity, getExtendedEntity
} from './utils/ha/entity-registry.js'; } from './utils/ha/entity-registry.js';
import { ResolvedMediaCache } from './utils/ha/resolved-media.js'; import { ResolvedMediaCache } from './utils/ha/resolved-media.js';
import { supportsFeature } from './utils/ha/update.js'; import { supportsFeature } from './utils/ha/update.js';
@@ -398,7 +398,8 @@ export class FrigateCard extends LitElement {
if ( if (
cameraConfig?.camera_name && cameraConfig?.camera_name &&
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE || (cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig?.dependent_cameras?.length) cameraConfig?.dependent_cameras?.length ||
cameraConfig?.dependent_cameras_all)
) { ) {
buttons.push({ buttons.push({
icon: 'mdi:filmstrip', icon: 'mdi:filmstrip',
@@ -416,7 +417,8 @@ export class FrigateCard extends LitElement {
if ( if (
cameraConfig?.camera_name && cameraConfig?.camera_name &&
(cameraConfig?.camera_name !== CAMERA_BIRDSEYE || (cameraConfig?.camera_name !== CAMERA_BIRDSEYE ||
cameraConfig?.dependent_cameras?.length) cameraConfig?.dependent_cameras?.length ||
cameraConfig?.dependent_cameras_all)
) { ) {
buttons.push({ buttons.push({
icon: 'mdi:camera', icon: 'mdi:camera',
+2
View File
@@ -23,6 +23,8 @@ export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER =
`${CONF_CAMERAS}.#.live_provider` as const; `${CONF_CAMERAS}.#.live_provider` as const;
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS = export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS =
`${CONF_CAMERAS}.#.dependent_cameras` as const; `${CONF_CAMERAS}.#.dependent_cameras` as const;
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS_ALL =
`${CONF_CAMERAS}.#.dependent_cameras_all` as const;
export const CONF_CAMERAS_ARRAY_TRIGGER_BY_MOTION = export const CONF_CAMERAS_ARRAY_TRIGGER_BY_MOTION =
`${CONF_CAMERAS}.#.trigger_by_motion` as const; `${CONF_CAMERAS}.#.trigger_by_motion` as const;
export const CONF_CAMERAS_ARRAY_TRIGGER_BY_OCCUPANCY = export const CONF_CAMERAS_ARRAY_TRIGGER_BY_OCCUPANCY =
+5
View File
@@ -17,6 +17,7 @@ import {
CONF_CAMERAS_ARRAY_CAMERA_NAME, CONF_CAMERAS_ARRAY_CAMERA_NAME,
CONF_CAMERAS_ARRAY_CLIENT_ID, CONF_CAMERAS_ARRAY_CLIENT_ID,
CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS,
CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS_ALL,
CONF_CAMERAS_ARRAY_ICON, CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID, CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_LABEL, CONF_CAMERAS_ARRAY_LABEL,
@@ -882,6 +883,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderStringInput( ${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex), getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)} )}
${this._renderSwitch(
getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS_ALL, cameraIndex),
frigateCardConfigDefaults.cameras.dependent_cameras_all,
)}
${this._renderOptionSelector( ${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, cameraIndex), getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, cameraIndex),
dependentCameras, dependentCameras,
+2 -1
View File
@@ -15,7 +15,8 @@
"camera_entity": "Camera Entity", "camera_entity": "Camera Entity",
"camera_name": "Frigate camera name (Autodetected from entity)", "camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)", "client_id": "Frigate client id (For >1 Frigate server)",
"dependent_cameras": "Dependent cameras to also show events for", "dependent_cameras": "Show events for specific cameras with this camera",
"dependent_cameras_all": "Show events for all cameras with this camera",
"id": "Unique id for this camera in this card", "id": "Unique id for this camera in this card",
"label": "Frigate label/object filter", "label": "Frigate label/object filter",
"frigate_url": "Frigate server URL", "frigate_url": "Frigate server URL",
+8 -1
View File
@@ -375,6 +375,7 @@ export const cameraConfigDefault = {
trigger_by_motion: false, trigger_by_motion: false,
trigger_by_occupancy: true, trigger_by_occupancy: true,
trigger_by_entities: [], trigger_by_entities: [],
dependent_cameras_all: false,
}; };
const webrtcCardCameraConfigSchema = z.object({ const webrtcCardCameraConfigSchema = z.object({
entity: z.string().optional(), entity: z.string().optional(),
@@ -405,6 +406,9 @@ const cameraConfigSchema = z
// Set of cameras IDs upon which this camera depends. // Set of cameras IDs upon which this camera depends.
dependent_cameras: z.string().array().optional(), dependent_cameras: z.string().array().optional(),
dependent_cameras_all: z
.boolean()
.default(cameraConfigDefault.dependent_cameras_all),
trigger_by_motion: z.boolean().default(cameraConfigDefault.trigger_by_motion), trigger_by_motion: z.boolean().default(cameraConfigDefault.trigger_by_motion),
trigger_by_occupancy: z.boolean().default(cameraConfigDefault.trigger_by_occupancy), trigger_by_occupancy: z.boolean().default(cameraConfigDefault.trigger_by_occupancy),
@@ -1015,7 +1019,10 @@ const timelineConfigSchema = z
.max(24 * 60 * 60) .max(24 * 60 * 60)
.optional() .optional()
.default(timelineConfigDefault.window_seconds), .default(timelineConfigDefault.window_seconds),
show_recordings: z.boolean().optional().default(timelineConfigDefault.show_recordings), show_recordings: z
.boolean()
.optional()
.default(timelineConfigDefault.show_recordings),
controls: z controls: z
.object({ .object({
thumbnails: thumbnailsControlSchema thumbnails: thumbnailsControlSchema
+10 -6
View File
@@ -264,7 +264,14 @@ export const getFullDependentBrowseMediaQueryParameters = (
const cameraConfig = cameras.get(camera); const cameraConfig = cameras.get(camera);
if (cameraConfig) { if (cameraConfig) {
cameraIDs.add(camera); cameraIDs.add(camera);
for (const eventCameraID of cameraConfig.dependent_cameras || []) { const dependentCameras: Set<string> = new Set();
(cameraConfig.dependent_cameras || []).forEach((item) =>
dependentCameras.add(item),
);
if (cameraConfig.dependent_cameras_all) {
cameras.forEach((_, key) => dependentCameras.add(key));
}
for (const eventCameraID of dependentCameras) {
if (!cameraIDs.has(eventCameraID)) { if (!cameraIDs.has(eventCameraID)) {
getDependentCameras(eventCameraID); getDependentCameras(eventCameraID);
} }
@@ -281,12 +288,9 @@ export const getFullDependentBrowseMediaQueryParameters = (
cameras.get(cameraID), cameras.get(cameraID),
mediaType ? { mediaType: mediaType } : {}, mediaType ? { mediaType: mediaType } : {},
); );
// Fail on a single bad camera, as it's almost certainly a user error that if (param) {
// should be fixed rather than hidden. params.push(param);
if (!param) {
return null;
} }
params.push(param);
} }
return params.length ? params : null; return params.length ? params : null;
}; };