Skip to content

Commit

Permalink
Merge pull request #1520 from dermotduffy/perform-action
Browse files Browse the repository at this point in the history
Add support for `perform-action` calls
  • Loading branch information
dermotduffy authored Sep 14, 2024
2 parents b2e8bfe + d3754b0 commit c7c134b
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 111 deletions.
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

0 comments on commit c7c134b

Please sign in to comment.