mirror of
https://github.com/koush/scrypted.git
synced 2026-06-11 21:30:29 +01:00
Merge branch 'main' of github.com:koush/scrypted
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.96",
|
||||
"version": "0.0.97",
|
||||
"description": "Reolink Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -94,6 +94,35 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
this.updatePtzCaps();
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
subgroup: 'Advanced',
|
||||
title: 'Presets',
|
||||
description: 'PTZ Presets in the format "id=name". Where id is the PTZ Preset identifier and name is a friendly name.',
|
||||
multiple: true,
|
||||
defaultValue: [],
|
||||
combobox: true,
|
||||
onPut: async (ov, presets: string[]) => {
|
||||
const caps = {
|
||||
...this.ptzCapabilities,
|
||||
presets: {},
|
||||
};
|
||||
for (const preset of presets) {
|
||||
const [key, name] = preset.split('=');
|
||||
caps.presets[key] = name;
|
||||
}
|
||||
this.ptzCapabilities = caps;
|
||||
},
|
||||
mapGet: () => {
|
||||
const presets = this.ptzCapabilities?.presets || {};
|
||||
return Object.entries(presets).map(([key, name]) => key + '=' + name);
|
||||
},
|
||||
},
|
||||
cachedPresets: {
|
||||
multiple: true,
|
||||
hide: true,
|
||||
json: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
deviceInfo: {
|
||||
json: true,
|
||||
hide: true
|
||||
@@ -134,9 +163,21 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
}
|
||||
};
|
||||
|
||||
this.storageSettings.settings.presets.onGet = async () => {
|
||||
const choices = this.storageSettings.values.cachedPresets.map((preset) => preset.id + '=' + preset.name);
|
||||
return {
|
||||
choices,
|
||||
};
|
||||
};
|
||||
|
||||
this.updateDeviceInfo();
|
||||
(async () => {
|
||||
this.updatePtzCaps();
|
||||
try {
|
||||
await this.getPresets();
|
||||
} catch (e) {
|
||||
this.console.log('Fail fetching presets', e);
|
||||
}
|
||||
const api = this.getClient();
|
||||
const deviceInfo = await api.getDeviceInfo();
|
||||
this.storageSettings.values.deviceInfo = deviceInfo;
|
||||
@@ -160,12 +201,20 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
updatePtzCaps() {
|
||||
const { ptz } = this.storageSettings.values;
|
||||
this.ptzCapabilities = {
|
||||
...this.ptzCapabilities,
|
||||
pan: ptz?.includes('Pan'),
|
||||
tilt: ptz?.includes('Tilt'),
|
||||
zoom: ptz?.includes('Zoom'),
|
||||
}
|
||||
}
|
||||
|
||||
async getPresets() {
|
||||
const client = this.getClient();
|
||||
const ptzPresets = await client.getPtzPresets();
|
||||
this.console.log(`Presets: ${JSON.stringify(ptzPresets)}`)
|
||||
this.storageSettings.values.cachedPresets = ptzPresets;
|
||||
}
|
||||
|
||||
async updateAbilities() {
|
||||
const api = this.getClient();
|
||||
const abilities = await api.getAbility();
|
||||
|
||||
@@ -39,6 +39,11 @@ export type SirenResponse = {
|
||||
rspCode: number;
|
||||
}
|
||||
|
||||
export interface PtzPreset {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ReolinkCameraClient {
|
||||
credential: AuthFetchCredentialState;
|
||||
parameters: Record<string, string>;
|
||||
@@ -61,6 +66,13 @@ export class ReolinkCameraClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
private createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
async login() {
|
||||
if (this.tokenLease > Date.now()) {
|
||||
return;
|
||||
@@ -201,23 +213,37 @@ export class ReolinkCameraClient {
|
||||
return response.body?.[0]?.value?.DevInfo;
|
||||
}
|
||||
|
||||
private async ptzOp(op: string, speed: number) {
|
||||
async getPtzPresets(): Promise<PtzPreset[]> {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'GetPtzPreset');
|
||||
const body = [
|
||||
{
|
||||
cmd: "GetPtzPreset",
|
||||
action: 1,
|
||||
param: {
|
||||
channel: this.channelId
|
||||
}
|
||||
}
|
||||
];
|
||||
const response = await this.requestWithLogin({
|
||||
url,
|
||||
responseType: 'json',
|
||||
method: 'POST'
|
||||
}, this.createReadable(body));
|
||||
return response.body?.[0]?.value?.PtzPreset?.filter(preset => preset.enable === 1);
|
||||
}
|
||||
|
||||
private async ptzOp(op: string, speed: number, id?: number) {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'PtzCtrl');
|
||||
|
||||
const createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
const c1 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
@@ -225,6 +251,7 @@ export class ReolinkCameraClient {
|
||||
op,
|
||||
speed,
|
||||
timeout: 1,
|
||||
id
|
||||
}
|
||||
},
|
||||
]));
|
||||
@@ -234,7 +261,7 @@ export class ReolinkCameraClient {
|
||||
const c2 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
@@ -248,10 +275,37 @@ export class ReolinkCameraClient {
|
||||
this.console.log(await c2);
|
||||
}
|
||||
|
||||
private async presetOp(speed: number, id: number) {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'PtzCtrl');
|
||||
|
||||
const c1 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
channel: this.channelId,
|
||||
op: 'ToPos',
|
||||
speed,
|
||||
id
|
||||
}
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
||||
async ptz(command: PanTiltZoomCommand) {
|
||||
// reolink doesnt accept signed values to ptz
|
||||
// in favor of explicit direction.
|
||||
// so we need to convert the signed values to abs explicit direction.
|
||||
if (command.preset && !Number.isNaN(Number(command.preset))) {
|
||||
await this.presetOp(1, Number(command.preset));
|
||||
return;
|
||||
}
|
||||
|
||||
let op = '';
|
||||
if (command.pan < 0)
|
||||
op += 'Left';
|
||||
@@ -263,7 +317,7 @@ export class ReolinkCameraClient {
|
||||
op += 'Up';
|
||||
|
||||
if (op) {
|
||||
await this.ptzOp(op, Math.round(Math.abs(command?.pan || command?.tilt || 1) * 10));
|
||||
await this.ptzOp(op, Math.ceil(Math.abs(command?.pan || command?.tilt || 1) * 10));
|
||||
}
|
||||
|
||||
op = undefined;
|
||||
@@ -273,7 +327,7 @@ export class ReolinkCameraClient {
|
||||
op = 'ZoomInc';
|
||||
|
||||
if (op) {
|
||||
await this.ptzOp(op, Math.round(Math.abs(command?.zoom || 1) * 10));
|
||||
await this.ptzOp(op, Math.ceil(Math.abs(command?.zoom || 1) * 10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,12 +335,6 @@ export class ReolinkCameraClient {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'AudioAlarmPlay');
|
||||
const createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
let alarmMode;
|
||||
if (duration) {
|
||||
@@ -306,7 +354,7 @@ export class ReolinkCameraClient {
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "AudioAlarmPlay",
|
||||
action: 0,
|
||||
|
||||
Reference in New Issue
Block a user