Skip to content

Commit

Permalink
Merge pull request #1501 from dermotduffy/admin-trigger
Browse files Browse the repository at this point in the history
Use custom websocket to avoid needing admin privileges
  • Loading branch information
dermotduffy committed Aug 25, 2024
2 parents 5a1d08f + 429dd90 commit 800259d
Show file tree
Hide file tree
Showing 47 changed files with 1,221 additions and 876 deletions.
12 changes: 8 additions & 4 deletions src/camera-manager/browse-media/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { localize } from '../../localize/localize';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { Entity } from '../../utils/ha/entity-registry/types';
import { Camera } from '../camera';
import { Camera, CameraInitializationOptions } from '../camera';
import { CameraInitializationError } from '../error';

interface BrowseMediaCameraInitializationOptions extends CameraInitializationOptions {
entityRegistryManager: EntityRegistryManager;
hass: HomeAssistant;
}

export class BrowseMediaCamera extends Camera {
protected _entity: Entity | null = null;

public async initialize(
hass: HomeAssistant,
entityRegistryManager: EntityRegistryManager,
options: BrowseMediaCameraInitializationOptions,
): Promise<Camera> {
const config = this.getConfig();
const entity = config.camera_entity
? await entityRegistryManager.getEntity(hass, config.camera_entity)
? await options.entityRegistryManager.getEntity(options.hass, config.camera_entity)
: null;

if (!entity || !config.camera_entity) {
Expand Down
16 changes: 12 additions & 4 deletions src/camera-manager/browse-media/engine-browse-media.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher';
import { CameraConfig } from '../../config/types';
import { ExtendedHomeAssistant } from '../../types';
import { canonicalizeHAURL } from '../../utils/ha';
Expand Down Expand Up @@ -28,10 +29,10 @@ import {
PartialEventQuery,
QueryType,
} from '../types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
import { BrowseMediaCamera } from './camera';
import { BrowseMediaViewMediaFactory } from './media';
import { BrowseMediaMetadata } from './types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';

/**
* A utility method to determine if a browse media object matches against a
Expand Down Expand Up @@ -123,24 +124,27 @@ export class BrowseMediaCameraManagerEngine
implements CameraManagerEngine
{
protected _browseMediaManager: BrowseMediaManager<BrowseMediaMetadata>;
protected _entityRegistryManager: EntityRegistryManager;
protected _resolvedMediaCache: ResolvedMediaCache;
protected _requestCache: RequestCache;

public constructor(
entityRegistryManager: EntityRegistryManager,
stateWatcher: StateWatcherSubscriptionInterface,
browseMediaManager: BrowseMediaManager<BrowseMediaMetadata>,
resolvedMediaCache: ResolvedMediaCache,
requestCache: RequestCache,
eventCallback?: CameraEventCallback,
) {
super(eventCallback);
super(stateWatcher, eventCallback);
this._entityRegistryManager = entityRegistryManager;
this._browseMediaManager = browseMediaManager;
this._resolvedMediaCache = resolvedMediaCache;
this._requestCache = requestCache;
}

public async createCamera(
hass: HomeAssistant,
entityRegistryManager: EntityRegistryManager,
cameraConfig: CameraConfig,
): Promise<Camera> {
const camera = new BrowseMediaCamera(cameraConfig, this, {
Expand All @@ -164,7 +168,11 @@ export class BrowseMediaCameraManagerEngine
),
eventCallback: this._eventCallback,
});
return await camera.initialize(hass, entityRegistryManager);
return await camera.initialize({
entityRegistryManager: this._entityRegistryManager,
hass,
stateWatcher: this._stateWatcher,
});
}

public generateDefaultEventQuery(
Expand Down
71 changes: 25 additions & 46 deletions src/camera-manager/camera.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { StateWatcherSubscriptionInterface } from '../card-controller/hass/state-watcher';
import { CameraConfig } from '../config/types';
import { localize } from '../localize/localize';
import { allPromises } from '../utils/basic';
import {
DestroyCallback,
isTriggeredState,
parseStateChangeTrigger,
subscribeToTrigger,
} from '../utils/ha';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { HassStateDifference, isTriggeredState } from '../utils/ha';
import { Capabilities } from './capabilities';
import { CameraManagerEngine } from './engine';
import { CameraNoIDError } from './error';
import { CameraEventCallback } from './types';

export interface CameraInitializationOptions {
stateWatcher: StateWatcherSubscriptionInterface;
}
type DestroyCallback = () => void | Promise<void>;

export class Camera {
protected _config: CameraConfig;
protected _engine: CameraManagerEngine;
Expand All @@ -35,47 +33,17 @@ export class Camera {
this._eventCallback = options?.eventCallback;
}

protected async _convertStateChangeToCameraEvent(data: unknown): Promise<void> {
const stateChange = parseStateChangeTrigger(data);
if (!stateChange) {
return;
}

this._eventCallback?.({
cameraID: this.getID(),
type: isTriggeredState(stateChange.to_state.state) ? 'new' : 'end',
});
}

protected async _subscribeToTriggerEntities(hass: HomeAssistant): Promise<void> {
if (!this._config.triggers.entities.length) {
return;
}

this._destroyCallbacks.push(
await subscribeToTrigger(
hass,
(data) => this._convertStateChangeToCameraEvent(data),
{
entityID: this._config.triggers.entities,
platform: 'state',
stateOnly: true,
},
),
async initialize(options: CameraInitializationOptions): Promise<Camera> {
options.stateWatcher.subscribe(
this._stateChangeHandler,
this._config.triggers.entities,
);
}

async initialize(
hass: HomeAssistant,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_entityRegistryManager: EntityRegistryManager,
): Promise<Camera> {
await this._subscribeToTriggerEntities(hass);
this._onDestroy(() => options.stateWatcher.unsubscribe(this._stateChangeHandler));
return this;
}

async destroy(): Promise<void> {
await allPromises(this._destroyCallbacks, (cb) => cb());
public async destroy(): Promise<void> {
this._destroyCallbacks.forEach((callback) => callback());
}

public getConfig(): CameraConfig {
Expand All @@ -100,4 +68,15 @@ export class Camera {
public getCapabilities(): Capabilities | null {
return this._capabilities ?? null;
}

protected _stateChangeHandler = (difference: HassStateDifference): void => {
this._eventCallback?.({
cameraID: this.getID(),
type: isTriggeredState(difference.newState.state) ? 'new' : 'end',
});
};

protected _onDestroy(callback: DestroyCallback): void {
this._destroyCallbacks.push(callback);
}
}
32 changes: 21 additions & 11 deletions src/camera-manager/engine-factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { StateWatcherSubscriptionInterface } from '../card-controller/hass/state-watcher';
import { CameraConfig } from '../config/types';
import { localize } from '../localize/localize';
import { BrowseMediaManager } from '../utils/ha/browse-media/browse-media-manager';
Expand All @@ -12,45 +13,54 @@ import { CameraInitializationError } from './error';
import { CameraEventCallback, Engine } from './types';
import { getCameraEntityFromConfig } from './utils/camera-entity-from-config';

interface CameraManagerEngineFactoryOptions {
stateWatcher: StateWatcherSubscriptionInterface;
resolvedMediaCache: ResolvedMediaCache;
eventCallback?: CameraEventCallback;
}

export class CameraManagerEngineFactory {
// Entity registry manager is required for the actual function of the factory.
protected _entityRegistryManager: EntityRegistryManager;
protected _resolvedMediaCache: ResolvedMediaCache;

constructor(
entityRegistryManager: EntityRegistryManager,
resolvedMediaCache: ResolvedMediaCache,
) {
constructor(entityRegistryManager: EntityRegistryManager) {
this._entityRegistryManager = entityRegistryManager;
this._resolvedMediaCache = resolvedMediaCache;
}

public async createEngine(
engine: Engine,
eventCallback?: CameraEventCallback,
options: CameraManagerEngineFactoryOptions,
): Promise<CameraManagerEngine> {
let cameraManagerEngine: CameraManagerEngine;
switch (engine) {
case Engine.Generic:
const { GenericCameraManagerEngine } = await import('./generic/engine-generic');
cameraManagerEngine = new GenericCameraManagerEngine(eventCallback);
cameraManagerEngine = new GenericCameraManagerEngine(
options.stateWatcher,
options.eventCallback,
);
break;
case Engine.Frigate:
const { FrigateCameraManagerEngine } = await import('./frigate/engine-frigate');
cameraManagerEngine = new FrigateCameraManagerEngine(
this._entityRegistryManager,
options.stateWatcher,
new RecordingSegmentsCache(),
new RequestCache(),
eventCallback,
options.eventCallback,
);
break;
case Engine.MotionEye:
const { MotionEyeCameraManagerEngine } = await import(
'./motioneye/engine-motioneye'
);
cameraManagerEngine = new MotionEyeCameraManagerEngine(
this._entityRegistryManager,
options.stateWatcher,
new BrowseMediaManager(new MemoryRequestCache<string, BrowseMedia>()),
this._resolvedMediaCache,
options.resolvedMediaCache,
new RequestCache(),
eventCallback,
options.eventCallback,
);
}
return cameraManagerEngine;
Expand Down
11 changes: 3 additions & 8 deletions src/camera-manager/engine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { CameraConfig, ActionPhase } from '../config/types';
import { PTZAction } from '../config/ptz';
import { ActionPhase, CameraConfig } from '../config/types';
import { ExtendedHomeAssistant } from '../types';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { ViewMedia } from '../view/media';
import { Camera } from './camera';
import { CameraManagerReadOnlyConfigStore } from './store';
Expand All @@ -27,18 +27,13 @@ import {
RecordingSegmentsQuery,
RecordingSegmentsQueryResultsMap,
} from './types';
import { PTZAction } from '../config/ptz';

export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;

export interface CameraManagerEngine {
getEngineType(): Engine;

createCamera(
hass: HomeAssistant,
entityRegistryManager: EntityRegistryManager,
cameraConfig: CameraConfig,
): Promise<Camera>;
createCamera(hass: HomeAssistant, cameraConfig: CameraConfig): Promise<Camera>;

generateDefaultEventQuery(
store: CameraManagerReadOnlyConfigStore,
Expand Down
Loading

0 comments on commit 800259d

Please sign in to comment.