feat: Add hardened error handling and retries (#2451)
- Closes #1830 - Closes #2099
This commit is contained in:
committed by
dermotduffy
parent
4bc787e2b7
commit
47bcce93d3
@@ -5,10 +5,9 @@ import { DeviceRegistryManager } from '../ha/registry/device';
|
||||
import { EntityRegistryManager } from '../ha/registry/entity/types';
|
||||
import { ResolvedMediaCache } from '../ha/resolved-media';
|
||||
import { HomeAssistant } from '../ha/types';
|
||||
import { localize } from '../localize/localize';
|
||||
import { RecordingSegmentsCache } from './cache';
|
||||
import { CameraManagerEngine } from './engine';
|
||||
import { CameraInitializationError } from './error';
|
||||
import { CameraNoEntityError } from './error';
|
||||
import { CameraEventCallback, CameraManagerRequestCache, Engine } from './types';
|
||||
import { getCameraEntityFromConfig } from './utils/camera-entity-from-config';
|
||||
|
||||
@@ -117,10 +116,7 @@ export class CameraManagerEngineFactory {
|
||||
return Engine.Generic;
|
||||
}
|
||||
// Otherwise, it's probably a typo so throw an exception.
|
||||
throw new CameraInitializationError(
|
||||
localize('error.no_camera_entity'),
|
||||
cameraConfig,
|
||||
);
|
||||
throw new CameraNoEntityError(cameraConfig);
|
||||
}
|
||||
|
||||
switch (entity?.platform) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Entity, EntityRegistryManager } from '../ha/registry/entity/types';
|
||||
import { localize } from '../localize/localize';
|
||||
import { Camera, CameraInitializationOptions } from './camera';
|
||||
import { CameraInitializationError } from './error';
|
||||
import { CameraNoEntityError } from './error';
|
||||
import { getCameraEntityFromConfig } from './utils/camera-entity-from-config';
|
||||
|
||||
export interface EntityCameraInitializationOptions extends CameraInitializationOptions {
|
||||
@@ -19,7 +18,7 @@ export class EntityCamera extends Camera {
|
||||
: null;
|
||||
|
||||
if (!entity || !cameraEntityID) {
|
||||
throw new CameraInitializationError(localize('error.no_camera_entity'), config);
|
||||
throw new CameraNoEntityError(config);
|
||||
}
|
||||
this._entity = entity;
|
||||
return await super.initialize(options);
|
||||
|
||||
@@ -1,4 +1,34 @@
|
||||
import { localize } from '../localize/localize.js';
|
||||
import { AdvancedCameraCardError } from '../types.js';
|
||||
|
||||
export class CameraInitializationError extends AdvancedCameraCardError {}
|
||||
export class CameraNoIDError extends AdvancedCameraCardError {}
|
||||
class CameraInitializationError extends AdvancedCameraCardError {}
|
||||
|
||||
export class CameraNoEngineError extends CameraInitializationError {
|
||||
constructor(context?: unknown) {
|
||||
super(localize('error.no_camera_engine'), context);
|
||||
}
|
||||
}
|
||||
|
||||
export class CameraNoIDError extends CameraInitializationError {
|
||||
constructor(context?: unknown) {
|
||||
super(localize('error.no_camera_id'), context);
|
||||
}
|
||||
}
|
||||
|
||||
export class CameraDuplicateIDError extends CameraInitializationError {
|
||||
constructor(context?: unknown) {
|
||||
super(localize('error.duplicate_camera_id'), context);
|
||||
}
|
||||
}
|
||||
|
||||
export class CameraNoEntityError extends CameraInitializationError {
|
||||
constructor(context?: unknown) {
|
||||
super(localize('error.no_camera_entity'), context);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReolinkInitializationError extends CameraInitializationError {
|
||||
constructor(context?: unknown) {
|
||||
super(localize('error.camera_initialization_reolink'), context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PTZAction, PTZActionPhase } from '../../config/schema/actions/custom/pt
|
||||
import { CameraConfig } from '../../config/schema/cameras';
|
||||
import { Entity, EntityRegistryManager } from '../../ha/registry/entity/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { SEVERITIES } from '../../severity';
|
||||
import {
|
||||
CapabilitiesRaw,
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
} from '../../types';
|
||||
import { errorToConsole } from '../../utils/basic';
|
||||
import { Camera, CameraInitializationOptions } from '../camera';
|
||||
import { CameraInitializationError } from '../error';
|
||||
import { CameraNoEntityError } from '../error';
|
||||
import { CameraEndpoints, CameraEndpointsContext } from '../types';
|
||||
import { getCameraEntityFromConfig } from '../utils/camera-entity-from-config';
|
||||
import {
|
||||
@@ -123,7 +122,7 @@ export class FrigateCamera extends Camera {
|
||||
if (cameraEntity && (!hasCameraName || hasAutoTriggers)) {
|
||||
entity = await entityRegistryManager.getEntity(hass, cameraEntity);
|
||||
if (!entity) {
|
||||
throw new CameraInitializationError(localize('error.no_camera_entity'), config);
|
||||
throw new CameraNoEntityError(config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from '../config/schema/actions/custom/ptz.js';
|
||||
import { CameraConfig, Rotation } from '../config/schema/cameras.js';
|
||||
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
|
||||
import { localize } from '../localize/localize.js';
|
||||
import { Endpoint } from '../types.js';
|
||||
import {
|
||||
allPromises,
|
||||
@@ -28,7 +27,11 @@ import { ViewItemCapabilities } from '../view/types.js';
|
||||
import { Capabilities } from './capabilities.js';
|
||||
import { CameraManagerEngineFactory } from './engine-factory.js';
|
||||
import { CameraManagerEngine } from './engine.js';
|
||||
import { CameraInitializationError } from './error.js';
|
||||
import {
|
||||
CameraDuplicateIDError,
|
||||
CameraNoEngineError,
|
||||
CameraNoIDError,
|
||||
} from './error.js';
|
||||
import { CameraManagerReadOnlyConfigStore, CameraManagerStore } from './store.js';
|
||||
import {
|
||||
CameraEndpoints,
|
||||
@@ -150,12 +153,12 @@ export class CameraManager {
|
||||
this._store = options?.store ?? new CameraManagerStore();
|
||||
}
|
||||
|
||||
public async initializeCamerasFromConfig(): Promise<boolean> {
|
||||
public async initializeCamerasFromConfig(): Promise<void> {
|
||||
const config = this._api.getConfigManager().getConfig();
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
|
||||
if (!config || !hass) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._requestLimit.concurrency =
|
||||
@@ -169,15 +172,7 @@ export class CameraManager {
|
||||
recursivelyMergeObjectsNotArrays(config?.cameras_global, camera),
|
||||
);
|
||||
|
||||
try {
|
||||
await this._initializeCameras(cameras);
|
||||
} catch (e: unknown) {
|
||||
this._api
|
||||
.getMessageManager()
|
||||
.setErrorIfHigherPriority(e, localize('error.camera_initialization'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
await this._initializeCameras(cameras);
|
||||
}
|
||||
|
||||
public async destroy(): Promise<void> {
|
||||
@@ -214,12 +209,9 @@ export class CameraManager {
|
||||
}))
|
||||
: null;
|
||||
if (!engine || !engineType) {
|
||||
throw new CameraInitializationError(
|
||||
localize('error.no_camera_engine'),
|
||||
// Camera initialization may modify the configuration. Keep the
|
||||
// original config unchanged.
|
||||
cloneDeep(cameraConfig),
|
||||
);
|
||||
// Camera initialization may modify the configuration. Keep the
|
||||
// original config unchanged.
|
||||
throw new CameraNoEngineError(cloneDeep(cameraConfig));
|
||||
}
|
||||
engines.set(engineType, engine);
|
||||
output.set(cameraConfig, engine);
|
||||
@@ -272,18 +264,12 @@ export class CameraManager {
|
||||
|
||||
if (!cameraID) {
|
||||
await destroyCameras();
|
||||
throw new CameraInitializationError(
|
||||
localize('error.no_camera_id'),
|
||||
camera.getConfig(),
|
||||
);
|
||||
throw new CameraNoIDError(camera.getConfig());
|
||||
}
|
||||
|
||||
if (cameraIDs.has(cameraID)) {
|
||||
await destroyCameras();
|
||||
throw new CameraInitializationError(
|
||||
localize('error.duplicate_camera_id'),
|
||||
camera.getConfig(),
|
||||
);
|
||||
throw new CameraDuplicateIDError(camera.getConfig());
|
||||
}
|
||||
|
||||
// Always ensure the actual ID used in the card is in the configuration itself.
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { DeviceRegistryManager } from '../../ha/registry/device/index';
|
||||
import { Entity, EntityRegistryManager } from '../../ha/registry/entity/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { localize } from '../../localize/localize';
|
||||
import {
|
||||
CapabilitiesRaw,
|
||||
Endpoint,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
import { createSelectOptionAction } from '../../utils/action.js';
|
||||
import { Camera, CameraInitializationOptions } from '../camera';
|
||||
import { EntityCamera } from '../entity-camera';
|
||||
import { CameraInitializationError } from '../error';
|
||||
import { ReolinkInitializationError } from '../error';
|
||||
import { CameraEndpointsContext, CameraProxyConfig } from '../types';
|
||||
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
|
||||
|
||||
@@ -44,8 +43,6 @@ interface ReolinkCameraInitializationOptions extends CameraInitializationOptions
|
||||
deviceRegistryManager: DeviceRegistryManager;
|
||||
}
|
||||
|
||||
class ReolinkInitializationError extends CameraInitializationError {}
|
||||
|
||||
// Button entities for continuous PTZ movement (press to start, press stop to
|
||||
// end). Discovered from button.{name}_ptz_{action} entities. Zoom button
|
||||
// entities are disabled by default in the Reolink integration.
|
||||
@@ -153,10 +150,7 @@ export class ReolinkCamera extends EntityCamera {
|
||||
const channelOrUID = match?.groups?.channel_or_uid ?? null;
|
||||
|
||||
if (hostid === null || channelOrUID === null) {
|
||||
throw new ReolinkInitializationError(
|
||||
localize('error.camera_initialization_reolink'),
|
||||
this.getConfig(),
|
||||
);
|
||||
throw new ReolinkInitializationError(this.getConfig());
|
||||
}
|
||||
|
||||
const channelCandidate = Number(channelOrUID);
|
||||
|
||||
Reference in New Issue
Block a user