feat: Add control/output of selected camera via an entity (#1895)
- Closes #1871
This commit is contained in:
@@ -12,7 +12,11 @@ export class CameraSelectAction extends AdvancedCameraCardAction<CameraSelectAct
|
||||
const view = api.getViewManager().getView();
|
||||
const config = api.getConfigManager().getConfig();
|
||||
|
||||
if (selectCameraID && view) {
|
||||
// Don't do anything if the camera is already selected (especially important
|
||||
// for control entities, as otherwise every camera change will generate a
|
||||
// double request for events, once when the camera changes and another when
|
||||
// the observed state of the control entity changes to match).
|
||||
if (selectCameraID && view && selectCameraID !== view.camera) {
|
||||
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
||||
const targetViewName =
|
||||
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { InternalCallbackActionConfig } from '../../../config/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class InternalCallbackAction extends AdvancedCameraCardAction<InternalCallbackActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await this._action.callback(api);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ActionConfig } from '@dermotduffy/custom-card-helpers';
|
||||
import { ActionContext } from 'action';
|
||||
import { ActionType } from '../../config/types';
|
||||
import { ActionType, INTERNAL_CALLBACK_ACTION } from '../../config/types';
|
||||
import { convertActionToCardCustomAction } from '../../utils/action';
|
||||
import { CameraSelectAction } from './actions/camera-select';
|
||||
import { CameraUIAction } from './actions/camera-ui';
|
||||
@@ -10,6 +10,7 @@ import { DownloadAction } from './actions/download';
|
||||
import { ExpandAction } from './actions/expand';
|
||||
import { FullscreenAction } from './actions/fullscreen';
|
||||
import { GenericAction } from './actions/generic';
|
||||
import { InternalCallbackAction } from './actions/internal-callback';
|
||||
import { LogAction } from './actions/log';
|
||||
import { MediaPlayerAction } from './actions/media-player';
|
||||
import { MenuToggleAction } from './actions/menu-toggle';
|
||||
@@ -132,6 +133,8 @@ export class ActionFactory {
|
||||
return new LogAction(context, cardCustomAction, options?.config);
|
||||
case 'status_bar':
|
||||
return new StatusBarAction(context, cardCustomAction, options?.config);
|
||||
case INTERNAL_CALLBACK_ACTION:
|
||||
return new InternalCallbackAction(context, cardCustomAction, options?.config);
|
||||
}
|
||||
|
||||
/* istanbul ignore next: this path cannot be reached -- @preserve */
|
||||
|
||||
@@ -14,6 +14,7 @@ import { InitializationAspect } from '../initialization-manager.js';
|
||||
import { CardConfigAPI } from '../types.js';
|
||||
import { getOverriddenConfig } from './get-overridden-config.js';
|
||||
import { setAutomationsFromConfig } from './load-automations.js';
|
||||
import { setRemoteControlEntityFromConfig } from './load-control-entities.js';
|
||||
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
|
||||
|
||||
export class ConfigManager {
|
||||
@@ -109,7 +110,8 @@ export class ConfigManager {
|
||||
this._api.getMessageManager().reset();
|
||||
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
||||
|
||||
setKeyboardShortcutsFromConfig(this._api, this);
|
||||
setKeyboardShortcutsFromConfig(this._api);
|
||||
setRemoteControlEntityFromConfig(this._api);
|
||||
setAutomationsFromConfig(this._api);
|
||||
|
||||
this._processOverrideConfig();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
createCameraAction,
|
||||
createInternalCallbackAction,
|
||||
createPerformAction,
|
||||
} from '../../utils/action';
|
||||
import { CardActionsAPI, CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||
|
||||
export const setRemoteControlEntityFromConfig = (api: CardConfigLoaderAPI) => {
|
||||
const automationTag = setRemoteControlEntityFromConfig;
|
||||
|
||||
api.getAutomationsManager().deleteAutomations(automationTag);
|
||||
|
||||
const cameraControlEntity = api.getConfigManager().getConfig()?.remote_control
|
||||
?.entities?.camera;
|
||||
if (!cameraControlEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const createSelectOptionAction = (option: string) =>
|
||||
createPerformAction('input_select.select_option', {
|
||||
target: {
|
||||
entity_id: cameraControlEntity,
|
||||
},
|
||||
data: {
|
||||
option: option,
|
||||
},
|
||||
});
|
||||
|
||||
// Control entities functionality is implemented entirely by populating
|
||||
// automations.
|
||||
|
||||
const automations: TaggedAutomation[] = [
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
condition: 'config' as const,
|
||||
paths: ['remote_control.entities.camera'],
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
// Set the possible options on the entity to the camera IDs via a
|
||||
// callback to `setCameraOptionsOnEntity` (below).
|
||||
createInternalCallbackAction((api: CardActionsAPI) =>
|
||||
setCameraOptionsOnEntity(cameraControlEntity, api),
|
||||
),
|
||||
// Set the selected option to the current camera ID.
|
||||
createSelectOptionAction('{{ advanced_camera_card.camera }}'),
|
||||
],
|
||||
tag: automationTag,
|
||||
},
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
condition: 'camera' as const,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
// When the camera changes, update the entity to match.
|
||||
createSelectOptionAction('{{ advanced_camera_card.trigger.camera.to }}'),
|
||||
],
|
||||
tag: automationTag,
|
||||
},
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: cameraControlEntity,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
// When the entity state changes, updated the selected option.
|
||||
createCameraAction(
|
||||
'camera_select',
|
||||
'{{ advanced_camera_card.trigger.state.to }}',
|
||||
),
|
||||
],
|
||||
tag: automationTag,
|
||||
},
|
||||
];
|
||||
|
||||
api.getAutomationsManager().addAutomations(automations);
|
||||
};
|
||||
|
||||
const setCameraOptionsOnEntity = async (entity: string, api: CardActionsAPI) => {
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
const cameraIDs = api.getCameraManager().getStore().getCameraIDs();
|
||||
|
||||
await hass?.callService(
|
||||
'input_select',
|
||||
'set_options',
|
||||
{
|
||||
options: [...cameraIDs],
|
||||
},
|
||||
{
|
||||
entity_id: entity,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -6,18 +6,16 @@ import { PTZAction } from '../../config/ptz';
|
||||
import { createPTZMultiAction } from '../../utils/action';
|
||||
import { CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||
|
||||
export const setKeyboardShortcutsFromConfig = (
|
||||
api: CardConfigLoaderAPI,
|
||||
tag: unknown,
|
||||
) => {
|
||||
api.getAutomationsManager().deleteAutomations(tag);
|
||||
export const setKeyboardShortcutsFromConfig = (api: CardConfigLoaderAPI) => {
|
||||
const automationTag = setKeyboardShortcutsFromConfig;
|
||||
api.getAutomationsManager().deleteAutomations(automationTag);
|
||||
|
||||
const shortcuts = api.getConfigManager().getConfig()?.view.keyboard_shortcuts;
|
||||
if (!shortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const automations = convertKeyboardShortcutsToAutomations(tag, shortcuts);
|
||||
const automations = convertKeyboardShortcutsToAutomations(automationTag, shortcuts);
|
||||
if (automations.length) {
|
||||
api.getAutomationsManager().addAutomations(automations);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { AdvancedCameraCardCustomAction, ViewActionConfig } from '../config/types';
|
||||
import { createCameraAction, createGeneralAction } from '../utils/action.js';
|
||||
import {
|
||||
createCameraAction,
|
||||
createGeneralAction,
|
||||
createViewAction,
|
||||
} from '../utils/action.js';
|
||||
import { ViewParameters } from '../view/view';
|
||||
import { CardQueryStringAPI } from './types';
|
||||
import { SubstreamSelectViewModifier } from './view/modifiers/substream-select';
|
||||
@@ -113,21 +117,25 @@ export class QueryStringManager {
|
||||
}
|
||||
break;
|
||||
case 'camera_ui':
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'default':
|
||||
case 'diagnostics':
|
||||
case 'download':
|
||||
case 'expand':
|
||||
case 'menu_toggle':
|
||||
customAction = createGeneralAction(action, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'diagnostics':
|
||||
case 'image':
|
||||
case 'live':
|
||||
case 'menu_toggle':
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
case 'snapshot':
|
||||
case 'snapshots':
|
||||
case 'timeline':
|
||||
customAction = createGeneralAction(action, {
|
||||
customAction = createViewAction(action, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -90,6 +90,7 @@ export interface CardConfigAPI {
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getDefaultManager(): DefaultManager;
|
||||
getHASSManager(): HASSManager;
|
||||
getInitializationManager(): InitializationManager;
|
||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||
getMediaPlayerManager(): MediaPlayerManager;
|
||||
@@ -101,8 +102,9 @@ export interface CardConfigAPI {
|
||||
}
|
||||
|
||||
export interface CardConfigLoaderAPI {
|
||||
getConfigManager(): ConfigManager;
|
||||
getAutomationsManager(): AutomationsManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getHASSManager(): HASSManager;
|
||||
}
|
||||
|
||||
export interface CardDefaultManagerAPI {
|
||||
@@ -178,6 +180,7 @@ export interface CardInitializerAPI {
|
||||
getCardElementManager(): CardElementManager;
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getDefaultManager(): DefaultManager;
|
||||
getEntityRegistryManager(): EntityRegistryManager;
|
||||
getHASSManager(): HASSManager;
|
||||
|
||||
Reference in New Issue
Block a user