feat: Add out of the box support for Reolink PTZ (#1982)

- Closes: #1964
This commit is contained in:
Dermot Duffy
2025-03-26 21:26:02 -07:00
committed by GitHub
parent f6bb8001e3
commit b5eee5878a
50 changed files with 953 additions and 679 deletions
+11 -21
View File
@@ -1,11 +1,9 @@
import { ActionContext } from 'action';
import { z } from 'zod';
import { ConditionsTriggerData } from '../../conditions/types.js';
import {
ActionConfig,
Actions,
ActionsConfig,
AuxillaryActionConfig,
} from '../../config/schema/actions/types.js';
import { forwardHaptic } from '../../ha/haptic.js';
import {
@@ -16,7 +14,7 @@ import { allPromises } from '../../utils/basic.js';
import { TemplateRenderer } from '../templates/index.js';
import { CardActionsManagerAPI } from '../types.js';
import { ActionSet } from './actions/set.js';
import { ActionExecutionRequest } from './types.js';
import { ActionsExecutionRequest, ActionsExecutor } from './types.js';
const INTERACTIONS = ['tap', 'double_tap', 'hold', 'start_tap', 'end_tap'] as const;
export type InteractionName = (typeof INTERACTIONS)[number];
@@ -30,7 +28,7 @@ const interactionEventSchema = z.object({
detail: interactionSchema,
});
export class ActionsManager {
export class ActionsManager implements ActionsExecutor {
protected _api: CardActionsManagerAPI;
protected _actionsInFlight: ActionSet[] = [];
protected _actionContext: ActionContext = {};
@@ -86,7 +84,7 @@ export class ActionsManager {
// actions).
actionConfig
) {
await this.executeActions(actionConfig, { config });
await this.executeActions({ actions: actionConfig, config });
}
};
@@ -113,7 +111,7 @@ export class ActionsManager {
return;
}
await this.executeActions(action);
await this.executeActions({ actions: action });
};
/**
@@ -121,11 +119,9 @@ export class ActionsManager {
* Card itself (e.g. menu, PTZ controller).
*/
public handleActionExecutionRequestEvent = async (
ev: CustomEvent<ActionExecutionRequest>,
ev: CustomEvent<ActionsExecutionRequest>,
): Promise<void> => {
await this.executeActions(ev.detail.action, {
config: ev.detail.config,
});
await this.executeActions(ev.detail);
};
public async uninitialize(): Promise<void> {
@@ -133,24 +129,18 @@ export class ActionsManager {
await allPromises(this._actionsInFlight, (actionSet) => actionSet.stop());
}
public async executeActions(
action: ActionConfig | ActionConfig[],
options?: {
config?: AuxillaryActionConfig;
triggerData?: ConditionsTriggerData;
},
): Promise<void> {
public async executeActions(request: ActionsExecutionRequest): Promise<void> {
const hass = this._api.getHASSManager().getHASS();
const renderedAction: ActionConfig | ActionConfig[] =
hass && this._templateRenderer
? (this._templateRenderer.renderRecursively(hass, action, {
? (this._templateRenderer.renderRecursively(hass, request.actions, {
conditionState: this._api.getConditionStateManager().getState(),
triggerData: options?.triggerData,
triggerData: request?.triggerData,
}) as ActionConfig | ActionConfig[])
: action;
: request.actions;
const actionSet = new ActionSet(this._actionContext, renderedAction, {
config: options?.config,
config: request.config,
cardID: this._api.getConfigManager().getConfig()?.card_id,
});
+2 -1
View File
@@ -1,4 +1,5 @@
import { PTZActionConfig } from '../../../config/schema/actions/custom/ptz';
import { PTZMovementType } from '../../../types';
import { getPTZTarget, ptzActionToCapabilityKey } from '../../../utils/ptz';
import { Timer } from '../../../utils/timer';
import { CardActionsAPI } from '../../types';
@@ -66,7 +67,7 @@ export class PTZAction extends AdvancedCameraCardAction<PTZActionConfig> {
if (
(capabilityKey &&
ptzCapabilities[capabilityKey]?.includes(
this._action.ptz_phase ? 'continuous' : 'relative',
this._action.ptz_phase ? PTZMovementType.Continuous : PTZMovementType.Relative,
)) ||
this._action.ptz_action === 'preset'
) {
+8 -2
View File
@@ -1,3 +1,4 @@
import { ConditionsTriggerData } from '../../conditions/types.js';
import {
ActionConfig,
AuxillaryActionConfig,
@@ -10,9 +11,14 @@ export interface Action {
stop(): Promise<void>;
}
export interface ActionExecutionRequest {
action: ActionConfig[] | ActionConfig;
export interface ActionsExecutionRequest {
actions: ActionConfig[] | ActionConfig;
config?: AuxillaryActionConfig;
triggerData?: ConditionsTriggerData;
}
export interface ActionsExecutor {
executeActions(request: ActionsExecutionRequest): Promise<void>;
}
export interface TargetedActionContext {
@@ -1,9 +1,9 @@
import { fireAdvancedCameraCardEvent } from '../../../utils/fire-advanced-camera-card-event';
import { ActionExecutionRequest } from '../types';
import { ActionsExecutionRequest } from '../types';
export const dispatchActionExecutionRequest = (
element: HTMLElement,
request: ActionExecutionRequest,
request: ActionsExecutionRequest,
) => {
fireAdvancedCameraCardEvent(element, 'action:execution-request', request);
};
@@ -13,7 +13,7 @@ export interface ActionExecutionRequestEventTarget extends EventTarget {
event: 'advanced-camera-card:action:execution-request',
listener: (
this: ActionExecutionRequestEventTarget,
ev: CustomEvent<ActionExecutionRequest>,
ev: CustomEvent<ActionsExecutionRequest>,
) => void,
options?: AddEventListenerOptions | boolean,
): void;
@@ -26,7 +26,7 @@ export interface ActionExecutionRequestEventTarget extends EventTarget {
event: 'advanced-camera-card:action:execution-request',
listener: (
this: ActionExecutionRequestEventTarget,
ev: CustomEvent<ActionExecutionRequest>,
ev: CustomEvent<ActionsExecutionRequest>,
) => void,
options?: boolean | EventListenerOptions,
): void;
+1 -1
View File
@@ -75,7 +75,7 @@ export class AutomationsManager {
await this._api
.getActionsManager()
.executeActions(actions, { triggerData: result.triggerData });
.executeActions({ actions, triggerData: result.triggerData });
--this._nestedAutomationExecutions;
};
+3 -2
View File
@@ -9,8 +9,9 @@ import {
} from '../utils/ha/registry/device';
import {
createEntityRegistryCache,
EntityRegistryManager,
EntityRegistryManagerLive,
} from '../utils/ha/registry/entity';
import { EntityRegistryManager } from '../utils/ha/registry/entity/types';
import { ResolvedMediaCache } from '../utils/ha/resolved-media';
import { ActionsManager } from './actions/actions-manager';
import { AutomationsManager } from './automations-manager';
@@ -100,7 +101,7 @@ export class CardController
protected _deviceRegistryManager = new DeviceRegistryManager(
createDeviceRegistryCache(),
);
protected _entityRegistryManager = new EntityRegistryManager(
protected _entityRegistryManager = new EntityRegistryManagerLive(
createEntityRegistryCache(),
);
protected _resolvedMediaCache = new ResolvedMediaCache();
+1 -1
View File
@@ -69,7 +69,7 @@ export class QueryStringManager {
protected async _executeNonViewRelated(intent: QueryStringViewIntent): Promise<void> {
if (intent.other) {
await this._api.getActionsManager().executeActions(intent.other);
await this._api.getActionsManager().executeActions({ actions: intent.other });
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import type { CameraManager } from '../camera-manager/manager';
import type { ConditionStateManager } from '../conditions/state-manager';
import type { Automation } from '../config/schema/automations';
import type { EntityRegistryManager } from '../utils/ha/registry/entity';
import type { EntityRegistryManager } from '../utils/ha/registry/entity/types';
import type { ResolvedMediaCache } from '../utils/ha/resolved-media';
import type { ActionsManager } from './actions/actions-manager';
import type { AutomationsManager } from './automations-manager';