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 support for perform-action calls #1520

Merged
merged 2 commits into from
Sep 14, 2024
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
22 changes: 22 additions & 0 deletions docs/configuration/actions/stock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Call a service. See [Home Assistant actions documentation](https://www.home-assistant.io/dashboards/actions/).

!> Home Assistant has deprecated the `call-service` action, please use [`perform-action`](#perform-action) instead.

```yaml
action: call-service
# [...]
Expand All @@ -27,6 +29,15 @@ action: navigate
# [...]
```

## `perform-action`

Perform a Home Assistant action. See [Home Assistant actions documentation](https://www.home-assistant.io/dashboards/actions/).

```yaml
action: perform-action
# [...]
```

## `toggle`

Toggle an entity. See [Home Assistant actions documentation](https://www.home-assistant.io/dashboards/actions/).
Expand Down Expand Up @@ -117,4 +128,15 @@ elements:
tap_action:
action: fire-dom-event
key: value
- type: icon
icon: mdi:numeric-8-box
title: Perform action
style:
left: 200px
top: 400px
tap_action:
action: perform-action
perform_action: homeassistant.toggle
target:
entity_id: light.office_main_lights
```
22 changes: 11 additions & 11 deletions docs/configuration/cameras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ the integration currently offers.

| Option | Default | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions_left`, `actions_right`, `actions_up`, `actions_down`, `actions_zoom_in`, `actions_zoom_out`, `actions_home` | Set by camera [engine](./engine.md) of the selected camera | The [call-service](../actions/stock/README.md?id=call-service) action that will be called for each PTZ action for relative movements. |
| `actions_left_start`, `actions_left_stop`, `actions_right_start`, `actions_right_stop`,`actions_up_start`, `actions_up_stop`,`actions_down_start`, `actions_down_stop`,`actions_zoom_in_start`, `actions_zoom_in_stop`,`actions_zoom_out_start`, `actions_zoom_out_stop` | Set by camera [engine](./engine.md) of the selected camera | The [call-service](../actions/stock/README.md?id=call-service) action that will be called for each PTZ action for continous movements. Both a `_start` and `_stop` variety must be provided for an action to be usable. |
| `actions_left`, `actions_right`, `actions_up`, `actions_down`, `actions_zoom_in`, `actions_zoom_out`, `actions_home` | Set by camera [engine](./engine.md) of the selected camera | The [perform-action](../actions/stock/README.md?id=perform-action) action that will be called for each PTZ action for relative movements. |
| `actions_left_start`, `actions_left_stop`, `actions_right_start`, `actions_right_stop`,`actions_up_start`, `actions_up_stop`,`actions_down_start`, `actions_down_stop`,`actions_zoom_in_start`, `actions_zoom_in_stop`,`actions_zoom_out_start`, `actions_zoom_out_stop` | Set by camera [engine](./engine.md) of the selected camera | The [perform-action](../actions/stock/README.md?id=perform-action) action that will be called for each PTZ action for continous movements. Both a `_start` and `_stop` variety must be provided for an action to be usable. |
| `c2r_delay_between_calls_seconds` | `0.2` | When the camera is configured with continuous actions only (e.g. `left_start` and `left_stop`, but not `left`), if something requests a relative action (e.g. a manually configured [action](../actions/README.md)), then `start` will be called, followed by a delay of this number of seconds and finally `stop` will be called. Cameras / integrations that are slower to respond to continuous steps may need to increase this value to avoid the continuous motion being too small. Cameras / integrations that are rapid to respond may need to decrease this value to avoid the "relative step" being too large. |
| `data_left`, `data_right`, `data_up`, `data_down`, `data_zoom_in`, `data_zoom_out`, `data_home` | | Shorthand for relative actions that call the service defined by the `service` parameter, with the data provided in this argument. Internally, this is just translated into the longer-form `actions_[action]`. 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). |
| `data_left_start`, `data_left_stop`, `data_right_start`, `data_right_stop`, `data_up_start`, `data_up_stop`, `data_down_start`, `data_down_stop`, `data_zoom_in_start`, `data_zoom_in_stop`, `data_zoom_out_start`, `data_zoom_out_stop` | | Shorthand for continuous actions that call the service defined by the `service` parameter, with the data provided in this argument. Internally, this is just translated into the longer-form `actions_[action]_start` and `actions_[action]_stop`. 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). Both a `_start` and `_stop` variety must be provided for an action to be usable. |
Expand All @@ -232,7 +232,7 @@ cameras:
? [action]
```

`[action]` is any [call-service](../actions/stock/README.md?id=call-service) action.
`[action]` is any [perform-action](../actions/stock/README.md?id=perform-action) action.

## `triggers`

Expand Down Expand Up @@ -382,22 +382,22 @@ cameras:
r2c_delay_between_calls_seconds: 0.5
# Relative action (only `left` shown)
actions_left:
action: call-service
service: service.of_your_choice
action: perform-action
perform_action: service.of_your_choice
data:
device: '048123'
cmd: left
# Continuous action (only `right` shown)
actions_right_start:
action: call-service
service: service.of_your_choice
action: perform-action
perform_action: service.of_your_choice
data:
device: '048123'
cmd: right
phase: start
actions_right_stop:
action: call-service
service: service.of_your_choice
action: perform-action
perform_action: service.of_your_choice
data:
device: '048123'
phase: stop
Expand All @@ -419,8 +419,8 @@ cameras:
presets:
# Preset using long form.
armchair:
action: call-service
service: service.of_your_choice
action: perform-action
perform_action: service.of_your_choice
data:
device: '048123'
cmd: preset
Expand Down
8 changes: 4 additions & 4 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ elements:
right: 25px
bottom: 50px
tap_action:
action: call-service
action: perform-action
service: amcrest.ptz_control
data:
entity_id: camera.kitchen
Expand Down Expand Up @@ -252,9 +252,9 @@ elements:
style:
color: red
tap_action:
action: call-service
service: homeassistant.toggle
data:
action: perform-action
perform_action: homeassistant.toggle
target:
entity_id: siren.siren
conditions:
- condition: triggered
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"license": "MIT",
"dependencies": {
"@cycjimmy/jsmpeg-player": "^6.0.5",
"@dermotduffy/custom-card-helpers": "^1.9.0",
"@dermotduffy/custom-card-helpers": "^1.9.1",
"@dermotduffy/panzoom": "^4.5.1",
"@egjs/hammerjs": "^2.0.17",
"@graphiteds/core": "^1.9.21",
Expand All @@ -29,7 +29,7 @@
"date-fns-tz": "^3.1.3",
"embla-carousel": "^8.1.4",
"embla-carousel-wheel-gestures": "^8.0.1",
"home-assistant-js-websocket": "^8.2.0",
"home-assistant-js-websocket": "^9.4.0",
"keycharm": "^0.4.0",
"lit": "^3.1.4",
"lodash-es": "^4.17.21",
Expand Down
1 change: 0 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ const config = {
moduleContext: {
'./node_modules/@formatjs/intl-utils/lib/src/diff.js': 'window',
'./node_modules/@formatjs/intl-utils/lib/src/resolve-locale.js': 'window',
'./node_modules/flatpickr/dist/esm/index.js': 'window',
},
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
this._cachedValueController?.clearValue();
return true;
}
return false;
return !this.hasUpdated;
}
return true;
}
Expand Down
8 changes: 5 additions & 3 deletions src/components/submenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,14 @@ export class FrigateCardSubmenuSelect extends LitElement {
title: title || option,
...((entityID.startsWith('select.') || entityID.startsWith('input_select.')) && {
tap_action: {
action: 'call-service',
service: entityID.startsWith('select.')
action: 'perform-action',
perform_action: entityID.startsWith('select.')
? 'select.select_option'
: 'input_select.select_option',
data: {
target: {
entity_id: entityID,
},
data: {
option: option,
},
},
Expand Down
29 changes: 29 additions & 0 deletions src/config/management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,21 @@ const conditionToConditionsTransform = (data: unknown): boolean => {
return false;
};

const callServiceToPerformActionTransform = (data: unknown): boolean => {
if (
typeof data !== 'object' ||
!data ||
data['action'] !== 'call-service' ||
typeof data['service'] !== 'string'
) {
return false;
}
data['action'] = 'perform-action';
data['perform_action'] = data['service'];
delete data['service'];
return true;
};

/**
* Transform service_data -> data
* See: https://github.com/dermotduffy/frigate-hass-card/issues/1103
Expand Down Expand Up @@ -849,4 +864,18 @@ const UPGRADES = [
}),
deleteWithOverrides('live.controls.title'),
deleteWithOverrides('media_viewer.controls.title'),

// Upgrade call-service calls throughout the card config. They could show up
// attached to any element, any automation, or any card/view action (i.e. very
// broadly across the config), so it's challenging to better target this
// upgrade. As written, this will convert things that look like call-service
// calls recurseively throughout the whole card config, but this could
// conceivably be an overreach if (e.g.) some totally unrelated object has {
// action: 'call-service', service: '<any string>' } that means something
// different.
(data: unknown): boolean => {
return upgradeObjectRecursively(callServiceToPerformActionTransform)(
typeof data === 'object' && data ? (data as RawFrigateCardConfig) : {},
);
},
];
68 changes: 47 additions & 21 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
MoreInfoActionConfig,
NavigateActionConfig,
NoActionConfig,
PerformActionActionConfig,
ToggleActionConfig,
UrlActionConfig,
} from '@dermotduffy/custom-card-helpers';
import { HassServiceTarget } from 'home-assistant-js-websocket';
import { z } from 'zod';
import { MEDIA_CHUNK_SIZE_DEFAULT, MEDIA_CHUNK_SIZE_MAX } from '../const.js';
import { capabilityKeys } from '../types.js';
Expand Down Expand Up @@ -168,13 +170,36 @@ const toggleActionSchema = schemaForType<
}),
);

const targetSchema = schemaForType<HassServiceTarget>()(
z.object({
entity_id: z.string().optional(),
device_id: z.string().optional(),
area_id: z.string().optional(),
}),
);

const performActionActionSchema = schemaForType<
PerformActionActionConfig & ExtendedConfirmationRestrictionConfig
>()(
actionBaseSchema.extend({
action: z.literal('perform-action'),
perform_action: z.string(),
data: z.object({}).passthrough().optional(),
target: targetSchema.optional(),
}),
);

// Note: call-service is deprecated and will eventually go away. Please use
// perform-action instead.
// See: https://www.home-assistant.io/blog/2024/08/07/release-20248/#goodbye-service-calls-hello-actions-
const callServiceActionSchema = schemaForType<
CallServiceActionConfig & ExtendedConfirmationRestrictionConfig
>()(
actionBaseSchema.extend({
action: z.literal('call-service'),
service: z.string(),
data: z.object({}).passthrough().optional(),
target: targetSchema.optional(),
}),
);

Expand Down Expand Up @@ -391,6 +416,7 @@ export type FrigateCardCustomAction = z.infer<typeof frigateCardCustomActionSche
export const actionSchema = z.union([
toggleActionSchema,
callServiceActionSchema,
performActionActionSchema,
navigateActionSchema,
urlActionSchema,
moreInfoActionSchema,
Expand Down Expand Up @@ -814,8 +840,8 @@ const dataPTZFormatToFullFormat = function (suffix: string): (data: unknown) =>
const name = match?.[1];
if (name && !(`${suffix}${name}` in data)) {
out[`${suffix}${name}`] = {
action: 'call-service',
service: data['service'],
action: 'perform-action',
perform_action: data['service'],
data: data[key],
};
delete out[key];
Expand All @@ -830,29 +856,29 @@ const ptzCameraConfigSchema = z.preprocess(
dataPTZFormatToFullFormat('actions_'),
z
.object({
actions_left: callServiceActionSchema.optional(),
actions_left_start: callServiceActionSchema.optional(),
actions_left_stop: callServiceActionSchema.optional(),
actions_left: performActionActionSchema.optional(),
actions_left_start: performActionActionSchema.optional(),
actions_left_stop: performActionActionSchema.optional(),

actions_right: callServiceActionSchema.optional(),
actions_right_start: callServiceActionSchema.optional(),
actions_right_stop: callServiceActionSchema.optional(),
actions_right: performActionActionSchema.optional(),
actions_right_start: performActionActionSchema.optional(),
actions_right_stop: performActionActionSchema.optional(),

actions_up: callServiceActionSchema.optional(),
actions_up_start: callServiceActionSchema.optional(),
actions_up_stop: callServiceActionSchema.optional(),
actions_up: performActionActionSchema.optional(),
actions_up_start: performActionActionSchema.optional(),
actions_up_stop: performActionActionSchema.optional(),

actions_down: callServiceActionSchema.optional(),
actions_down_start: callServiceActionSchema.optional(),
actions_down_stop: callServiceActionSchema.optional(),
actions_down: performActionActionSchema.optional(),
actions_down_start: performActionActionSchema.optional(),
actions_down_stop: performActionActionSchema.optional(),

actions_zoom_in: callServiceActionSchema.optional(),
actions_zoom_in_start: callServiceActionSchema.optional(),
actions_zoom_in_stop: callServiceActionSchema.optional(),
actions_zoom_in: performActionActionSchema.optional(),
actions_zoom_in_start: performActionActionSchema.optional(),
actions_zoom_in_stop: performActionActionSchema.optional(),

actions_zoom_out: callServiceActionSchema.optional(),
actions_zoom_out_start: callServiceActionSchema.optional(),
actions_zoom_out_stop: callServiceActionSchema.optional(),
actions_zoom_out: performActionActionSchema.optional(),
actions_zoom_out_start: performActionActionSchema.optional(),
actions_zoom_out_stop: performActionActionSchema.optional(),

// The number of seconds between subsequent relative calls when converting a
// relative request into a continuous request.
Expand All @@ -870,7 +896,7 @@ const ptzCameraConfigSchema = z.preprocess(
.preprocess(
dataPTZFormatToFullFormat(''),
z.union([
z.record(callServiceActionSchema),
z.record(performActionActionSchema),

// This is used by the data_ style of action.
z.object({ service: z.string().optional() }),
Expand Down
Loading
Loading