fix: Fix issue with detection of zoom in/out buttons (#1986)
- Related: #1964
This commit is contained in:
@@ -9,7 +9,7 @@ A "Camera Engine" defines what "type" of camera is being configured (e.g. `friga
|
|||||||
| `frigate` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: |
|
| `frigate` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: |
|
||||||
| `generic` | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|
| `generic` | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|
||||||
| `motioneye` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :white_check_mark: |
|
| `motioneye` | :white_check_mark: | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :white_check_mark: |
|
||||||
| `reolink` | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: |
|
| `reolink` | :white_check_mark: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :eight_spoked_asterisk: | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :heavy_multiplication_x: | :white_check_mark: | :heavy_multiplication_x: |
|
||||||
|
|
||||||
### Live providers supported per Engine
|
### Live providers supported per Engine
|
||||||
|
|
||||||
@@ -101,6 +101,12 @@ cameras:
|
|||||||
| `media_resolution` | `low` | Whether to retrieve `high` or `low` resolution media items. |
|
| `media_resolution` | `low` | Whether to retrieve `high` or `low` resolution media items. |
|
||||||
| `url` | | The URL of the Reolink camera/NVR UI. If set, this value will be (exclusively) used for a `Camera UI` menu button. |
|
| `url` | | The URL of the Reolink camera/NVR UI. If set, this value will be (exclusively) used for a `Camera UI` menu button. |
|
||||||
|
|
||||||
|
### PTZ Support
|
||||||
|
|
||||||
|
Zero-configuration PTZ support is available for Reolink if your camera supports it.
|
||||||
|
|
||||||
|
!> For Home Assistant control of Reolink camera PTZ functions, the relevent `button` entities must be enabled. To verify, navigate to `Settings -> Devices & services -> Reolink -> [Choose Device]`, and ensure the `PTZ` entities are enabled. Disabled entities are shown under the `+X disabled entities` label. :eight_spoked_asterisk:
|
||||||
|
|
||||||
## Fully expanded reference
|
## Fully expanded reference
|
||||||
|
|
||||||
[](../common/expanded-warning.md ':include')
|
[](../common/expanded-warning.md ':include')
|
||||||
|
|||||||
@@ -18,20 +18,21 @@ interface ReolinkCameraInitializationOptions extends CameraInitializationOptions
|
|||||||
|
|
||||||
class ReolinkInitializationError extends CameraInitializationError {}
|
class ReolinkInitializationError extends CameraInitializationError {}
|
||||||
|
|
||||||
interface PTZActionToButtonEntity {
|
interface PTZButtonEntities {
|
||||||
stop?: string;
|
stop?: string;
|
||||||
left?: string;
|
left?: string;
|
||||||
right?: string;
|
right?: string;
|
||||||
up?: string;
|
up?: string;
|
||||||
down?: string;
|
down?: string;
|
||||||
zoomIn?: string;
|
zoom_in?: string;
|
||||||
zoomOut?: string;
|
zoom_out?: string;
|
||||||
}
|
}
|
||||||
|
type PTZButton = keyof PTZButtonEntities;
|
||||||
|
|
||||||
export class ReolinkCamera extends BrowseMediaCamera {
|
export class ReolinkCamera extends BrowseMediaCamera {
|
||||||
protected _channel: number | null = null;
|
protected _channel: number | null = null;
|
||||||
protected _reolinkUniqueID: string | null = null;
|
protected _reolinkUniqueID: string | null = null;
|
||||||
protected _ptzButtons: PTZActionToButtonEntity | null = null;
|
protected _ptzButtons: PTZButtonEntities | null = null;
|
||||||
|
|
||||||
public async initialize(options: ReolinkCameraInitializationOptions): Promise<Camera> {
|
public async initialize(options: ReolinkCameraInitializationOptions): Promise<Camera> {
|
||||||
await super.initialize(options);
|
await super.initialize(options);
|
||||||
@@ -65,18 +66,29 @@ export class ReolinkCamera extends BrowseMediaCamera {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const config = this.getConfig();
|
const config = this.getConfig();
|
||||||
|
|
||||||
const ptzButtonMap = await this._getPTZButtonEntities(hass, entityRegistry);
|
const ptzButtons = await this._getPTZButtons(hass, entityRegistry);
|
||||||
const configPTZCapabilities = getPTZCapabilitiesFromCameraConfig(this.getConfig());
|
const configPTZCapabilities = getPTZCapabilitiesFromCameraConfig(this.getConfig());
|
||||||
const reolinkPTZCapabilities = ptzButtonMap
|
|
||||||
? Object.keys(ptzButtonMap).reduce(
|
const reolinkPTZCapabilities: PTZCapabilities = {};
|
||||||
(acc, key) =>
|
for (const key of Object.keys(ptzButtons ?? {})) {
|
||||||
key === 'stop' ? acc : { [key]: [PTZMovementType.Continuous], ...acc },
|
switch (key) {
|
||||||
{},
|
case 'left':
|
||||||
)
|
case 'right':
|
||||||
: null;
|
case 'up':
|
||||||
|
case 'down':
|
||||||
|
reolinkPTZCapabilities[key] = [PTZMovementType.Continuous];
|
||||||
|
break;
|
||||||
|
case 'zoom_in':
|
||||||
|
reolinkPTZCapabilities.zoomIn = [PTZMovementType.Continuous];
|
||||||
|
break;
|
||||||
|
case 'zoom_out':
|
||||||
|
reolinkPTZCapabilities.zoomOut = [PTZMovementType.Continuous];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const combinedPTZCapabilities: PTZCapabilities | null =
|
const combinedPTZCapabilities: PTZCapabilities | null =
|
||||||
configPTZCapabilities || reolinkPTZCapabilities
|
configPTZCapabilities || Object.keys(reolinkPTZCapabilities).length
|
||||||
? {
|
? {
|
||||||
...reolinkPTZCapabilities,
|
...reolinkPTZCapabilities,
|
||||||
...configPTZCapabilities,
|
...configPTZCapabilities,
|
||||||
@@ -103,13 +115,13 @@ export class ReolinkCamera extends BrowseMediaCamera {
|
|||||||
disableExcept: config.capabilities?.disable_except,
|
disableExcept: config.capabilities?.disable_except,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
this._ptzButtons = ptzButtonMap;
|
this._ptzButtons = ptzButtons;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _getPTZButtonEntities(
|
protected async _getPTZButtons(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryManager,
|
entityRegistry: EntityRegistryManager,
|
||||||
): Promise<PTZActionToButtonEntity | null> {
|
): Promise<PTZButtonEntities | null> {
|
||||||
/* istanbul ignore next: this path cannot be reached as an exception is
|
/* istanbul ignore next: this path cannot be reached as an exception is
|
||||||
thrown in initialize() if this value is not found -- @preserve */
|
thrown in initialize() if this value is not found -- @preserve */
|
||||||
if (!this._reolinkUniqueID) {
|
if (!this._reolinkUniqueID) {
|
||||||
@@ -127,29 +139,29 @@ export class ReolinkCamera extends BrowseMediaCamera {
|
|||||||
ent.entity_id.startsWith('button.'),
|
ent.entity_id.startsWith('button.'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const capabilityMap = {
|
const uniqueSuffixes: PTZButton[] = [
|
||||||
_ptz_stop: 'stop',
|
'stop',
|
||||||
_ptz_left: 'left',
|
'left',
|
||||||
_ptz_right: 'right',
|
'right',
|
||||||
_ptz_up: 'up',
|
'up',
|
||||||
_ptz_down: 'down',
|
'down',
|
||||||
_ptz_zoom_in: 'zoomIn',
|
'zoom_in',
|
||||||
_ptz_zoom_out: 'zoomOut',
|
'zoom_out',
|
||||||
};
|
];
|
||||||
|
|
||||||
const buttonMap: PTZActionToButtonEntity = {};
|
const buttons: PTZButtonEntities = {};
|
||||||
for (const buttonEntity of buttonEntities) {
|
for (const buttonEntity of buttonEntities) {
|
||||||
for (const [uniqueIDSuffix, capability] of Object.entries(capabilityMap)) {
|
for (const uniqueIDSuffix of uniqueSuffixes) {
|
||||||
if (
|
if (
|
||||||
buttonEntity.unique_id &&
|
buttonEntity.unique_id &&
|
||||||
String(buttonEntity.unique_id).endsWith(uniqueIDSuffix)
|
String(buttonEntity.unique_id).endsWith(uniqueIDSuffix)
|
||||||
) {
|
) {
|
||||||
buttonMap[capability] = buttonEntity.entity_id;
|
buttons[uniqueIDSuffix] = buttonEntity.entity_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(buttonMap).length ? buttonMap : null;
|
return Object.keys(buttons).length ? buttons : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChannel(): number | null {
|
public getChannel(): number | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user