Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add out-of-the-box Frigate PTZ support #1312

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 98 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ That's it!

### Manual Resource Management

For most users, HACS should automatically add the necessary resources. Should this auto-registration does not work, you will need to complete one additional step.
For most users, HACS should automatically add the necessary resources. Should this auto-registration not work you will need to complete one additional step.

#### Lovelace in "Storage mode" (default)

Expand Down Expand Up @@ -167,11 +167,11 @@ See the [fully expanded cameras configuration example](#config-expanded-cameras)

##### Engine Capabilities

|Engine|Live|Supports clips|Supports Snapshots|Supports Recordings|Supports Timeline|Favorite events|Favorite recordings|
| - | - | - | - | - | - | - | - |
|`frigate`| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: |
|`generic`| :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|`motioneye`| :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|Engine|Live|Supports clips|Supports Snapshots|Supports Recordings|Supports Timeline|Supports PTZ out of the box|Supports manually configured PTZ|Favorite events|Favorite recordings|
| - | - | - | - | - | - | - | - | - | - |
|`frigate`| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: |
|`generic`| :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|`motioneye`| :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |

##### Live providers supported per Engine

Expand Down Expand Up @@ -485,6 +485,7 @@ menu:
| `timeline` | :white_check_mark: | The `timeline` menu button: show the event timeline. |
| `media_player` | :white_check_mark: | The `media_player` menu button: sends the visible media to a remote media player. Supports Frigate clips, snapshots and live camera (only for cameras that specify a `camera_entity` and only using the default HA stream (equivalent to the `ha` live provider). `jsmpeg` or `webrtc-card` are not supported, although live can still be played as long as `camera_entity` is specified. In the player list, a `tap` will send the media to the player, a `hold` will stop the media on the player. |
| `microphone` | :white_check_mark: | The `microphone` button allows usage of 2-way audio in certain configurations. See [Using 2-way audio](#using-2-way-audio). |
| `show_ptz` | :white_check_mark: | The `show_ptz` button shows or hide the PTZ controls. |

##### Configuration on each button

Expand Down Expand Up @@ -628,6 +629,30 @@ live:

See [Using 2-way audio](#using-2-way-audio) for more information about the very particular requirements that must be followed for 2-way audio to work.

<a name="frigate-card-ptz"></a>

#### Live: PTZ

Controls a PTZ (Pan Tilt Zoom) controller overlay. All configuration is under:

```yaml
live:
ptz:
```

| Option | Default | Overridable | Description |
| - | - | - | - |
| `mode` | `on` | :white_check_mark: | When `on` will show a PTZ control if so configured (manually, or by the camera engine), if `off` will not show any control. |
| `position` | `bottom-right` | :white_check_mark: | Whether to position the control on the `top-left`, `top-right`, `bottom-left` or `bottom-right`. This may be overridden by using the `style` parameter to precisely control placement. |
| `actions_left`, `actions_right`, `actions_up`, `actions_down`, `actions_zoom_in`, `actions_zoom_out`, `actions_home` | Default is set by camera engine of the selected camera | :white_check_mark: | The [Home Assistant actions](https://www.home-assistant.io/dashboards/actions/) to call when this icon is interacted with. |
| `orientation` | `horizontal` | :white_check_mark: | Whether to show a `vertical` or `horizontal` PTZ control. |
| `hide_pan_tilt` | `false` | :white_check_mark: | When `true` the Pan & Tilt buttons of the control is hidden |
| `hide_zoom` | `false` | :white_check_mark: | When `true` the Zoom button of the control is hidden |
| `hide_home` | `false` | :white_check_mark: | When `true` the Home button of the control is hidden |
| `data_left`, `data_right`, `data_up`, `data_down`, `data_zoom_in`, `data_zoom_out`, `data_home` | | :white_check_mark: | Shorthand for a `tap_action` that calls the `service` with the data provided in this argument. Internally, this is just translated into the longer-form `actions_[button]`. If both `actions_X` and `data_X` are specified, `actions_X` takes priority. This is compatible with [AlexxIT's WebRTC Card PTZ configuration](https://github.com/AlexxIT/WebRTC/wiki/PTZ-Config-Examples). |
| `service` | | :white_check_mark: | An optional Home Assistant service to call when the `data_` parameters are used. |
| `style` | | :white_check_mark: | Optionally position and style the element using CSS. Similar to [Picture Element styling](https://www.home-assistant.io/dashboards/picture-elements/#how-to-use-the-style-object), except without any default, e.g. `left: 42%` |

<a name="live-display"></a>

#### Live: Display
Expand Down Expand Up @@ -1315,8 +1340,6 @@ elements to add special Frigate card functionality.
| `custom:frigate-card-menu-submenu` | Add a configurable submenu dropdown. See [configuration below](#frigate-card-menu-submenu).|
| `custom:frigate-card-menu-submenu-select` | Add a submenu based on a `select` or `input_select`. See [configuration below](#frigate-card-submenu-select).|
| `custom:frigate-card-conditional` | Restrict a set of elements to only render when the card is showing particular a particular [view](#views). See [configuration below](#frigate-card-conditional).|
| `custom:frigate-card-ptz` | Add a PTZ (Pan Tilt Zoom) controller overlay. See [configuration below](#frigate-card-ptz).|


**Note**: ℹ️ Manual positioning of custom menu icons or submenus via the `style`
parameter is not supported as the menu buttons displayed are context sensitive
Expand Down Expand Up @@ -1377,19 +1400,6 @@ Parameters for the `custom:frigate-card-conditional` element:
`elements` | The elements to render. Can be any supported element, include additional condition or custom elements. |
| `conditions` | A set of conditions that must evaluate to true in order for the elements to be rendered. See [Frigate Card Conditions](#frigate-card-conditions). |

#### `custom:frigate-card-ptz`

Parameters for the `custom:frigate-card-ptz` element:

| Parameter | Default | Description |
| ------------- | - | -------------------------------------------- |
| `type` | | Must be `custom:frigate-card-ptz`. |
| `style` | `translate(-50%, -50%)` | Position and style the element using CSS. See [Picture Element styling](https://www.home-assistant.io/dashboards/picture-elements/#how-to-use-the-style-object). |
| `orientation` | `vertical` | Whether to show a `vertical` or `horizontal` PTZ control. |
| `actions_left`, `actions_right`, `actions_up`, `actions_down`, `actions_zoom_in`, `actions_zoom_out`, `actions_home` | The [Home Assistant actions](https://www.home-assistant.io/dashboards/actions/) to call when this icon is interacted with. |
| `data_left`, `data_right`, `data_up`, `data_down`, `data_zoom_in`, `data_zoom_out`, `data_home` | Shorthand for a `tap_action` that calls the `service` with the data provided in this argument. Internally, this is just translated into the longer-form `actions_[button]`. If both `actions_X` and `data_X` are specified, `actions_X` takes priority. This is compatible with [AlexxIT's WebRTC Card PTZ configuration](https://github.com/AlexxIT/WebRTC/wiki/PTZ-Config-Examples). |
| `service` | | An optional Home Assistant service to call when the `data_` parameters are used. |

<a name="frigate-card-actions"></a>

### Special Actions
Expand All @@ -1399,7 +1409,7 @@ Parameters for the `custom:frigate-card-ptz` element:
| Parameter | Description |
| - | - |
| `action` | Must be `custom:frigate-card-action`. |
| `frigate_card_action` | Call a Frigate Card action. Acceptable values are `default`, `clip`, `clips`, `image`, `live`, `recording`, `recordings`, `snapshot`, `snapshots`, `download`, `timeline`, `camera_ui`, `fullscreen`, `camera_select`, `menu_toggle`, `media_player`, `live_substream_on`, `live_substream_off`, `live_substream_select`, `expand`, `microphone_mute`, `microphone_unmute`, `mute`, `unmute`, `play`, `pause`, `screenshot`|
| `frigate_card_action` | Call a Frigate Card action. Acceptable values are `default`, `clip`, `clips`, `image`, `live`, `recording`, `recordings`, `snapshot`, `snapshots`, `download`, `timeline`, `camera_ui`, `fullscreen`, `camera_select`, `menu_toggle`, `media_player`, `live_substream_on`, `live_substream_off`, `live_substream_select`, `expand`, `microphone_mute`, `microphone_unmute`, `mute`, `unmute`, `play`, `pause`, `screenshot`, `show_ptz`, `ptz`|

<a name="custom-actions"></a>

Expand All @@ -1421,6 +1431,8 @@ Parameters for the `custom:frigate-card-ptz` element:
|`mute`, `unmute`| Mute or unmute the loaded media. |
|`play`, `pause`| Play or pause the loaded media. |
|`screenshot`| Take a screenshot of the loaded media (e.g. a still from a video). |
|`show_ptz`| Show or hide the PTZ controls. Takes a `show_ptz` boolean parameter to indicate whether the controls show be shown or not. |
|`ptz`| Execute a native PTZ action (only for native out-of-the-box PTZ camera engines, e.g. Frigate). Takes a required `ptz_action` parameter that is one of `left`, `right`, `up`, `down`, `zoom_in`, `zoom_out` or `preset`. Takes an optional `ptz_phase` parameter that is one of `start` or `stop` to start or stop the movement discretely. Takes an optional `ptz_preset` parameter as the preset to execute when the `ptz_action` parameter is `preset`. |

<a name="views"></a>

Expand All @@ -1438,6 +1450,7 @@ This card supports several different views:
|`recordings`|Shows a gallery of recent (last day) recordings for this camera and its dependents.|
|`recording`|Shows a viewer for the most recent recording for this camera. Can also be accessed by holding down the `recordings` menu icon.|
|`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.timeout_seconds`).|
|`timeline`|Shows an event timeline.|

### Navigating From A Snapshot To A Clip

Expand Down Expand Up @@ -2012,6 +2025,11 @@ menu:
enabled: false
alignment: matching
icon: mdi:play
show_ptz:
priority: 50
enabled: false
alignment: matching
icon: mdi:pan
button_size: 40
```
</details>
Expand Down Expand Up @@ -2066,6 +2084,29 @@ live:
microphone:
always_connected: false
disconnect_seconds: 60
ptz:
mode: on
position: bottom-right
orientation: horizontal
hide_pan_tilt: false
hide_zoom: false
hide_home: false
style:
# Optionally override the default style.
right: 5%
# Manually specifying actions.
actions_left:
tap_action:
action: call-service
service: sonoff.send_command
service_data:
device: '048123'
cmd: left
# Equivalent short form PTZ actions (only right button shown)
service: sonoff.send_command
data_right:
device: '048123'
cmd: right
display:
mode: single
grid_selected_width_factor: 2
Expand Down Expand Up @@ -2432,31 +2473,6 @@ elements:
state: on
state_not: off
media_loaded: true
# Full form PTZ actions (only left button shown).
- type: custom:frigate-card-ptz
orientation: vertical
style:
transform: none
right: 5%
top: 50%
actions_left:
tap_action:
action: call-service
service: sonoff.send_command
service_data:
device: '048123'
cmd: left
# Equivalent short form PTZ actions (only left button shown)
- type: custom:frigate-card-ptz
orientation: vertical
style:
transform: none
right: 20px
top: 180px
service: sonoff.send_command
data_left:
device: '048123'
cmd: left
```
</details>

Expand Down Expand Up @@ -2562,6 +2578,21 @@ elements:
tap_action:
action: custom:frigate-card-action
frigate_card_action: screenshot
- type: custom:frigate-card-menu-icon
icon: mdi:alpha-p-circle
title: Show PTZ
tap_action:
action: custom:frigate-card-action
frigate_card_action: show_ptz
show_ptz: true
- type: custom:frigate-card-menu-icon
icon: mdi:alpha-q-circle
title: Native PTZ Preset
tap_action:
action: custom:frigate-card-action
frigate_card_action: ptz
ptz_action: preset
ptz_preset: 'doorway
```
</details>

Expand Down Expand Up @@ -3416,44 +3447,33 @@ elements:
```
</details>

### Using a PTZ picture element
### Using a PTZ control

The card supports a custom PTZ element (`custom:frigate-card-ptz`) to conveniently control pan, tilt and zoom for cameras.
The card supports using PTZ controls to conveniently control pan, tilt and zoom for cameras.

<details>
<summary>Expand: Using the native PTZ picture element</summary>
<summary>Expand: Using the PTZ controls</summary>

This example shows the native PTZ element when the `live` or `image` view is displayed and the stream (media) has loaded.
This example shows the PTZ controls of the `live` view. Note that if your camera engine supports it (e.g. `frigate`) this will just work out of the box with no configuration at all.

```yaml
[...]
elements:
- type: custom:frigate-card-conditional
conditions:
media_loaded: true
view:
- live
- image
elements:
- type: custom:frigate-card-ptz
orientation: horizontal
style:
transform: none
right: 20px
top: 180px
service: sonoff.send_command
data_left:
device: '048123'
cmd: left
data_right:
device: '048123'
cmd: right
data_up:
device: '048123'
cmd: up
data_down:
device: '048123'
cmd: down
live:
ptz:
orientation: horizontal
service: sonoff.send_command
data_left:
device: '048123'
cmd: left
data_right:
device: '048123'
cmd: right
data_up:
device: '048123'
cmd: up
data_down:
device: '048123'
cmd: down
```
</details>

Expand Down
9 changes: 8 additions & 1 deletion src/action-handler-directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
protected doubleClickTimer = new Timer();

protected held = false;
protected started = false;

public connectedCallback(): void {
[
Expand Down Expand Up @@ -83,7 +84,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.held = true;
});

fireEvent(element, 'action', { action: 'start_tap' });
// Without this check we get double start_tap events from touchstart and
// mousedown events (on Android).
if (!this.started) {
this.started = true;
fireEvent(element, 'action', { action: 'start_tap' });
}
};

const end = (ev: Event): void => {
Expand All @@ -105,6 +111,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {

this.holdTimer.stop();

this.started = false;
fireEvent(element, 'action', { action: 'end_tap' });

if (options?.hasHold && this.held) {
Expand Down
34 changes: 14 additions & 20 deletions src/camera-manager/browse-media/engine-browse-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import { Entity } from '../../utils/ha/entity-registry/types';
import { ResolvedMediaCache, resolveMedia } from '../../utils/ha/resolved-media';
import { ViewMedia } from '../../view/media';
import { RequestCache } from '../cache';
import { Camera } from '../camera';
import { CameraManagerEngine } from '../engine';
import { CameraInitializationError } from '../error';
import { GenericCameraManagerEngine } from '../generic/engine-generic';
import { rangesOverlap } from '../range';
import { CameraManagerReadOnlyConfigStore } from '../store';
import {
CameraConfigs,
CameraEndpoint,
CameraManagerCameraCapabilities,
CameraManagerMediaCapabilities,
DataQuery,
EventQuery,
Expand Down Expand Up @@ -141,7 +141,7 @@ export class BrowseMediaCameraManagerEngine
hass: HomeAssistant,
entityRegistryManager: EntityRegistryManager,
cameraConfig: CameraConfig,
): Promise<CameraConfig> {
): Promise<Camera> {
const entity = cameraConfig.camera_entity
? await entityRegistryManager.getEntity(hass, cameraConfig.camera_entity)
: null;
Expand All @@ -152,11 +152,20 @@ export class BrowseMediaCameraManagerEngine
);
}
this._cameraEntities.set(cameraConfig.camera_entity, entity);
return cameraConfig;

return new Camera(cameraConfig, this, {
canFavoriteEvents: false,
canFavoriteRecordings: false,
canSeek: false,
supportsClips: true,
supportsRecordings: false,
supportsSnapshots: true,
supportsTimeline: true,
});
}

public generateDefaultEventQuery(
_cameras: CameraConfigs,
_store: CameraManagerReadOnlyConfigStore,
cameraIDs: Set<string>,
query: PartialEventQuery,
): EventQuery[] | null {
Expand Down Expand Up @@ -191,21 +200,6 @@ export class BrowseMediaCameraManagerEngine
return null;
}

public getCameraCapabilities(
cameraConfig: CameraConfig,
): CameraManagerCameraCapabilities | null {
const parentCapabilities = super.getCameraCapabilities(cameraConfig);
if (!parentCapabilities) {
return null;
}
return {
...parentCapabilities,
supportsClips: true,
supportsSnapshots: true,
supportsTimeline: true,
};
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getMediaCapabilities(_media: ViewMedia): CameraManagerMediaCapabilities {
return {
Expand Down
Loading
Loading