iobroker.reolink
Version:
1,225 lines • 129 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReolinkErrorMessages = void 0;
exports.getReolinkErrorMessage = getReolinkErrorMessage;
const adapter_core_1 = require("@iobroker/adapter-core");
const axios_1 = __importDefault(require("axios"));
const node_https_1 = __importDefault(require("node:https"));
const node_path_1 = __importDefault(require("node:path"));
const utils = __importStar(require("@iobroker/adapter-core"));
const neolink_manager_1 = require("./neolink-manager");
const dependency_check_1 = require("./dependency-check");
const snapshot_helper_1 = require("./snapshot-helper");
const mqtt_helper_1 = require("./mqtt-helper");
// typescript
exports.ReolinkErrorMessages = {
[-1]: 'Missing parameters',
[-2]: 'Used up memory',
[-3]: 'Check error',
[-4]: 'Parameters error',
[-5]: 'Reached the max session number.',
[-6]: 'Login required',
[-7]: 'Login error',
[-8]: 'Operation timeout',
[-9]: 'Not supported',
[-10]: 'Protocol error',
[-11]: 'Failed to read operation',
[-12]: 'Failed to get configuration.',
[-13]: 'Failed to set configuration.',
[-14]: 'Failed to apply for memory',
[-15]: 'Failed to created socket',
[-16]: 'Failed to send data',
[-17]: 'Failed to receiver data',
[-18]: 'Failed to open file',
[-19]: 'Failed to read file',
[-20]: 'Failed to write file',
[-21]: 'Token error',
[-22]: 'The length of the string exceeds the limit.',
[-23]: 'Missing parameters',
[-24]: 'Command error',
[-25]: 'Internal error',
[-26]: 'Ability error',
[-27]: 'Invalid user',
[-28]: 'User already exist',
[-29]: 'Reached the maximum number of users',
[-30]: 'The version is identical to the current one.',
[-31]: 'Ensure only one user can upgrade',
[-32]: 'Modify IP conflicted with used IP',
[-34]: 'Cloud login need bind email first',
[-35]: 'Cloud login unbind camera',
[-36]: 'Cloud login get login information out of time',
[-37]: 'Cloud login password error',
[-38]: 'Cloud bind camera uid error',
[-39]: 'Cloud login user doesn’t exist',
[-40]: 'Cloud unbind camera failed',
[-41]: 'The device doesn’t support cloud',
[-42]: 'Cloud login server failed',
[-43]: 'Cloud bind camera failed',
[-44]: 'Cloud unknown error',
[-45]: 'Cloud bind camera need verify code',
[-46]: 'An error occurred while using the digest authentication process',
[-47]: 'An expired nonce is used in the authentication process',
[-48]: 'Snap a picture failed',
[-49]: 'Channel is invalid',
[-99]: 'Device offline',
[-100]: 'Test Email、Ftp、WiFi failed',
[-101]: 'Upgrade checking firmware failed',
[-102]: 'Upgrade download online failed',
[-103]: 'Upgrade get upgrade status failed',
[-105]: 'Frequent logins, please try again later!',
[-220]: 'Error downloading video file',
[-221]: 'Busy video recording task',
[-222]: 'The video file does not exist',
[-301]: 'Digest Authentication nonce error',
[-310]: 'Aes decryption failure',
[-451]: 'ftp test login failed',
[-452]: 'Create ftp dir failed',
[-453]: 'Upload ftp file failed',
[-454]: 'Cannot connect ftp server',
[-480]: 'Some undefined errors',
[-481]: 'Cannot connect email server',
[-482]: 'Auth user failed',
[-483]: 'Email network err',
[-484]: 'Something wrong with email server',
[-485]: 'Something wrong with memory',
[-500]: 'The number of IP addresses reaches the upper limit',
[-501]: 'The user does not exist',
[-502]: 'Password err',
[-503]: 'Login deny',
[-505]: 'Login not init',
[-506]: 'Login locked',
[-507]: 'The number of logins reached the upper limit',
};
function getReolinkErrorMessage(code) {
return exports.ReolinkErrorMessages[code] ?? `Unknown error ${code}`;
}
class ReoLinkCamAdapter extends adapter_core_1.Adapter {
sslValidation = false;
refreshIntervalRecording = 10;
refreshIntervalRecordingTimer = 0;
apiConnected = false;
reolinkApiClient = null;
refreshStateTimeout = undefined;
neolinkManager = null;
neolinkConfig = null; // Store for MQTT control
streamAutoDisableTimer = undefined;
mqttAutoDisableTimer = undefined;
ptzAutoStopTimer = undefined;
ffmpegAvailable = false;
mqttHelper = null;
mqttBatteryQueryInterval = undefined;
mqttControlBusy = false;
constructor(options) {
super({
...options,
name: 'reolink',
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
}
/**
* @param command send to reolink
* @param genRndSeed needed for snap
* @param withChannel for multi devices
*/
genUrl(command, genRndSeed, withChannel) {
let urlString = `/api.cgi?cmd=${command}&`;
let password = encodeURIComponent(this.config.cameraPassword);
if (this.config.UriEncodedPassword !== undefined) {
if (!this.config.UriEncodedPassword) {
password = this.config.cameraPassword;
}
}
if (withChannel === true) {
urlString += `channel=${this.config.cameraChannel}&`;
}
if (genRndSeed === true) {
const randomSeed = Math.round(Math.random() * 100000000000000).toString(16);
urlString += `rs=${randomSeed}&`;
}
urlString += `user=${this.config.cameraUser}&password=${password}`;
return urlString;
}
async onReady() {
await this.setState('info.connection', false, true);
this.log.info('Reolink adapter has started');
if (!this.config.cameraIp) {
this.log.error('Camera Ip not set - please check instance!');
return;
}
if (!this.config.cameraUser || !this.config.cameraPassword) {
this.log.error('Username and/or password not set properly - please check instance!');
return;
}
// Check if this is a battery-powered camera
if (this.config.isBatteryCam) {
this.log.info('Battery-powered camera detected - using neolink');
await this.startBatteryCam();
return; // Don't continue with HTTP API
}
// HTTP API camera mode:
// 1. Remove any leftover battery cam states (in case user switched camera type)
// 2. Create HTTP cam states dynamically (analog to createBatteryCamStates for battery cams)
await this.cleanupBatteryCamStates();
await this.createHttpCamStates();
if (!this.config.cameraProtocol) {
this.log.error('no protocol (http/https) set!');
return;
}
// check Checkbox of ssl validation is set
this.sslValidation = this.config.sslvalid ?? false;
this.reolinkApiClient = axios_1.default.create({
baseURL: this.config.cameraIp.startsWith('http://') || this.config.cameraIp.startsWith('https://')
? this.config.cameraIp
: `${this.config.cameraProtocol}://${this.config.cameraIp}`,
timeout: 4000,
responseType: 'json',
responseEncoding: 'binary',
httpsAgent: new node_https_1.default.Agent({
rejectUnauthorized: this.sslValidation,
}),
});
this.log.info(`Current IP: ${this.config.cameraIp}`);
await this.setState('network.ip', { val: this.config.cameraIp, ack: true });
await this.setState('network.channel', {
val: Number(this.config.cameraChannel),
ack: true,
});
// first API Call...if something isn't working, stop Adapter
try {
await this.getDevInfo();
}
catch (error) {
this.log.error(`${error}: ${error.code}`);
}
if (!this.apiConnected) {
return;
}
await this.getLocalLink();
await this.refreshState('onReady');
await this.getDriveInfo();
await this.getPtzGuardInfo();
await this.getAutoFocus();
await this.getZoomAndFocus();
await this.getIrLights();
await this.getWhiteLed();
await this.getRecording();
this.log.debug('getStateAsync start Email notification');
//create state dynamically
const state = await this.getStateAsync('device.name');
if (state) {
await this.setState('Device.Name', state.val);
}
await this.getMailNotification();
this.subscribeStates('settings.EmailNotification');
this.log.debug('Email notification subscribed');
this.log.debug('start subscriptions');
// Subscribe on states
this.subscribeStates('settings.ir');
this.subscribeStates('settings.switchLed');
this.subscribeStates('settings.ledBrightness');
this.subscribeStates('settings.ledMode');
this.subscribeStates('settings.ptzPreset');
this.subscribeStates('settings.ptzPatrol');
this.subscribeStates('settings.autoFocus');
this.subscribeStates('settings.setZoomFocus');
this.subscribeStates('settings.push');
this.subscribeStates('settings.ftp');
this.subscribeStates('settings.scheduledRecording');
this.subscribeStates('settings.playAlarm');
this.subscribeStates('settings.getDiscData');
this.subscribeStates('settings.ptzEnableGuard');
this.subscribeStates('settings.ptzCheck');
this.subscribeStates('settings.ptzGuardTimeout');
this.subscribeStates('command.reboot');
this.subscribeStates('ai_config.*');
}
// function for getting motion detection
async getMdState() {
if (this.reolinkApiClient) {
try {
// cmd, channel, user, password
const MdInfoValues = await this.reolinkApiClient.get(this.genUrl('GetMdState', false, true));
this.log.debug(`camMdStateInfo ${JSON.stringify(MdInfoValues.status)}: ${JSON.stringify(MdInfoValues.data)}`);
if (MdInfoValues.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const MdValues = MdInfoValues.data[0];
this.log.debug(`Motion Detection value: ${MdValues.value.state}`);
await this.setState('sensor.motion', {
val: !!MdValues.value.state,
ack: true,
});
}
}
catch (error) {
const errorMessage = error.message.toString();
if (errorMessage.includes('timeout of')) {
this.log.debug(`get md state: ${error}`);
}
else {
this.log.error(`get md state: ${error}`);
}
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
}
}
}
async getAiState() {
if (this.reolinkApiClient) {
try {
// cmd, channel, user, password
const AiInfoValues = await this.reolinkApiClient.get(this.genUrl('GetAiState', false, true));
this.log.debug(`camAiStateInfo ${JSON.stringify(AiInfoValues.status)}: ${JSON.stringify(AiInfoValues.data)}`);
if (AiInfoValues.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const AiValues = AiInfoValues.data[0];
try {
await this.setState('sensor.dog_cat.state', {
val: !!AiValues.value.dog_cat.alarm_state,
ack: true,
});
await this.setState('sensor.dog_cat.support', {
val: !!AiValues.value.dog_cat.support,
ack: true,
});
this.log.debug(`dog_cat_state detection:${AiValues.value.dog_cat.alarm_state}`);
}
catch (error) {
this.log.debug(`get ai state animal: ${error}`);
this.log.debug('dog cat state not found.');
}
try {
await this.setState('sensor.face.state', {
val: !!AiValues.value.face.alarm_state,
ack: true,
});
await this.setState('sensor.face.support', {
val: !!AiValues.value.face.support,
ack: true,
});
this.log.debug(`face_state detection:${AiValues.value.face.alarm_state}`);
}
catch (error) {
this.log.debug(`get ai state face: ${error}`);
this.log.debug('face state not found.');
}
try {
await this.setState('sensor.people.state', {
val: !!AiValues.value.people.alarm_state,
ack: true,
});
await this.setState('sensor.people.support', {
val: !!AiValues.value.people.support,
ack: true,
});
this.log.debug(`people_state detection:${AiValues.value.people.alarm_state}`);
}
catch (error) {
this.log.debug(`get ai state people: ${error}`);
this.log.debug('people state not found.');
}
try {
await this.setState('sensor.vehicle.state', {
val: !!AiValues.value.vehicle.alarm_state,
ack: true,
});
await this.setState('sensor.vehicle.support', {
val: !!AiValues.value.vehicle.support,
ack: true,
});
this.log.debug(`vehicle_state detection:${AiValues.value.vehicle.alarm_state}`);
}
catch (error) {
this.log.debug(`get ai state vehicle: ${error}`);
this.log.debug('vehicle state not found.');
}
}
}
catch (error) {
const errorMessage = error.message.toString();
if (errorMessage.includes('timeout of')) {
this.log.debug(`get ai state general: ${error}`);
}
else {
this.log.error(`get ai state general: ${error}`);
}
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
}
}
}
async getAiCfg() {
if (!this.reolinkApiClient) {
return;
}
try {
// cmd, channel, user, password
const cfg = await this.reolinkApiClient.get(this.genUrl('GetAiCfg', false, true));
this.log.debug(`GetAiCfg ${JSON.stringify(cfg.status)}: ${JSON.stringify(cfg.data)}`);
if (cfg.status !== 200) {
return;
}
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const val = cfg.data[0].value;
try {
await this.setState('ai_config.raw', {
val: JSON.stringify(val),
ack: true,
});
this.log.debug(`ai_config.raw = ${JSON.stringify(val)}`);
}
catch (error) {
this.log.debug(`ai_config.raw: ${error}`);
}
}
catch (error) {
const errorMessage = error.message.toString();
if (errorMessage.includes('timeout of')) {
this.log.debug(`get ai config general: ${error}`);
}
else {
this.log.error(`get ai config general: ${error}`);
}
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
}
}
async setAiCfg(jsonString) {
try {
const command = [
{
cmd: 'SetAiCfg',
param: JSON.parse(jsonString),
},
];
await this.sendCmd(command, 'SetAiCfg');
}
catch (error) {
this.log.error(`setAiCfg: ${error}`);
}
// Immediately after patching the settings, get the new settings.
await this.getAiCfg();
}
// function for getting general information of camera device
async getDevInfo() {
if (this.reolinkApiClient) {
try {
this.log.debug('getDevinfo');
// cmd, channel, user, password
const devInfoValues = await this.reolinkApiClient.get(this.genUrl('GetDevInfo', false, true));
this.log.debug(`camMdStateInfo ${JSON.stringify(devInfoValues.status)}: ${JSON.stringify(devInfoValues.data)}`);
if (devInfoValues.status === 200) {
await this.setState('info.connection', true, true);
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const DevValues = devInfoValues.data[0];
await this.setState('device.buildDay', {
val: DevValues.value.DevInfo.buildDay,
ack: true,
});
await this.setState('device.cfgVer', {
val: DevValues.value.DevInfo.cfgVer,
ack: true,
});
await this.setState('device.detail', {
val: DevValues.value.DevInfo.detail,
ack: true,
});
await this.setState('device.diskNum', {
val: DevValues.value.DevInfo.diskNum,
ack: true,
});
await this.setState('device.firmVer', {
val: DevValues.value.DevInfo.firmVer,
ack: true,
});
await this.setState('device.model', {
val: DevValues.value.DevInfo.model,
ack: true,
});
await this.setState('device.name', {
val: DevValues.value.DevInfo.name,
ack: true,
});
await this.setState('device.serial', {
val: DevValues.value.DevInfo.serial,
ack: true,
});
await this.setState('device.wifi', {
val: DevValues.value.DevInfo.wifi,
ack: true,
});
}
}
catch (error) {
await this.setState('info.connection', false, true);
if (error.response?.error?.rspCode) {
const response = error.response?.data;
this.log.error(`Cannot get local link: ${getReolinkErrorMessage(response.error.rspCode)}`);
}
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
// this.log.error(error + ": " + error.code);
throw error;
}
}
}
async getPtzGuardInfo() {
if (this.reolinkApiClient) {
try {
// cmd, user, password
const ptzGuardInfoData = await this.reolinkApiClient.get(this.genUrl('GetPtzGuard', false, false));
this.log.debug(`ptz guard info ${JSON.stringify(ptzGuardInfoData.status)}: ${JSON.stringify(ptzGuardInfoData.data)}`);
}
catch (error) {
this.log.error(`ptz guard info: ${error}`);
}
}
}
async getDriveInfo() {
if (this.reolinkApiClient) {
try {
// cmd, user, password
const driveInfoData = await this.reolinkApiClient.get(this.genUrl('GetHddInfo', false, false));
this.log.debug(`getDriveInfo ${JSON.stringify(driveInfoData.status)}: ${JSON.stringify(driveInfoData.data)}`);
if (driveInfoData.status === 200) {
const driveInfoValues = driveInfoData.data[0];
const numberOfDiscs = Object.keys(driveInfoValues.value.HddInfo).length;
if (numberOfDiscs > 0) {
if (numberOfDiscs > 1) {
this.log.warn(`Only the first disc is read. You have ${numberOfDiscs.toString()} Discs!`);
}
await this.setState('disc.capacity', {
val: driveInfoValues.value.HddInfo[0].capacity,
ack: true,
});
let discFormatted = false;
if (driveInfoValues.value.HddInfo[0].format === 1) {
discFormatted = true;
}
await this.setState('disc.formatted', {
val: discFormatted,
ack: true,
});
await this.setState('disc.free', {
val: driveInfoValues.value.HddInfo[0].size,
ack: true,
});
let discMounted = false;
if (driveInfoValues.value.HddInfo[0].mount === 1) {
discMounted = true;
}
await this.setState('disc.mounted', {
val: discMounted,
ack: true,
});
}
else {
// no sd card inserted
await this.setState('disc.capacity', { val: 0, ack: true });
await this.setState('disc.formatted', { val: false, ack: true });
await this.setState('disc.free', { val: 0, ack: true });
await this.setState('disc.mounted', { val: false, ack: true });
}
}
}
catch (error) {
const errorMessage = error.message.toString();
if (errorMessage.includes('timeout of')) {
this.log.debug(`drive info ${error}`);
}
else {
this.log.error(`drive info ${error}`);
}
}
}
}
async getLocalLink() {
if (this.reolinkApiClient) {
try {
// cmd, channel, user, password
const LinkInfoValues = await this.reolinkApiClient.get(this.genUrl('GetLocalLink', false, true));
this.log.debug(`LinkInfoValues ${JSON.stringify(LinkInfoValues.status)}: ${JSON.stringify(LinkInfoValues.data)}`);
if (LinkInfoValues.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const LinkValues = LinkInfoValues.data[0];
await this.setState('network.activeLink', {
val: LinkValues.value.LocalLink.activeLink,
ack: true,
});
await this.setState('network.mac', {
val: LinkValues.value.LocalLink.mac,
ack: true,
});
await this.setState('network.dns', {
val: LinkValues.value.LocalLink.dns.dns1,
ack: true,
});
await this.setState('network.gateway', {
val: LinkValues.value.LocalLink.static.gateway,
ack: true,
});
await this.setState('network.mask', {
val: LinkValues.value.LocalLink.static.mask,
ack: true,
});
await this.setState('network.networkType', {
val: LinkValues.value.LocalLink.type,
ack: true,
});
}
}
catch (error) {
this.apiConnected = false;
if (error.response) {
const response = error.response?.data;
this.log.error(`Cannot get local link: ${getReolinkErrorMessage(response.error.rspCode)}`);
}
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
this.log.error(`get local link: ${error}`);
}
}
}
async getSnapshot() {
if (this.reolinkApiClient) {
try {
// cmd, channel, rs, user, password
const snapShot = await this.reolinkApiClient.get(this.genUrl('Snap', true, true));
const contentType = snapShot.headers['content-type'];
const base64data = Buffer.from(snapShot.data, 'binary').toString('base64');
return { type: contentType, base64: base64data };
}
catch (error) {
this.log.error(`get snapshot: ${error}`);
return null;
}
}
return null;
}
async sendCmd(cmdObject, cmdName) {
this.log.debug(`sendCmd: ${cmdName}`);
this.log.debug(`sendCmdObj: ${JSON.stringify(cmdObject)}`);
try {
if (this.reolinkApiClient) {
// cmd, user, password
const result = await this.reolinkApiClient.post(this.genUrl(cmdName, false, false), cmdObject);
this.log.debug(JSON.stringify(result.status));
this.log.debug(JSON.stringify(result.data));
if ('error' in result.data[0]) {
this.log.error(`sendCmd ${cmdName}: ${JSON.stringify(result.data[0].error.detail)}`);
switch (cmdName) {
case 'SetAutoFocus':
await this.setState('settings.autoFocus', {
val: 'Error or not supported',
ack: true,
});
break;
default:
this.log.error('not defined');
}
}
}
}
catch (error) {
this.log.error(`send cmd: ${error}`);
this.log.error(`sendCmd ${cmdName}connection error`);
}
}
async ptzCtrl(ptzPreset) {
const ptzPresetCmd = [
{
cmd: 'PtzCtrl',
action: 0,
param: {
channel: Number(this.config.cameraChannel),
id: ptzPreset,
op: 'ToPos',
speed: 32,
},
},
];
await this.sendCmd(ptzPresetCmd, 'PtzCtrl');
}
async ptzCtrl2(ptzPatrolPos) {
if (ptzPatrolPos === 0) {
const ptzPresetCmd = [
{
cmd: 'PtzCtrl',
param: {
channel: Number(this.config.cameraChannel),
op: 'StopPatrol',
},
},
];
await this.sendCmd(ptzPresetCmd, 'PtzCtrl');
}
else {
const ptzPresetCmd = [
{
cmd: 'PtzCtrl',
param: {
channel: Number(this.config.cameraChannel),
op: 'StartPatrol',
id: ptzPatrolPos,
},
},
];
await this.sendCmd(ptzPresetCmd, 'PtzCtrl');
}
}
async setPush(state) {
const pushOnCmd = [
{
cmd: 'SetPushV20',
param: {
Push: {
enable: state ? 1 : 0,
},
},
},
];
await this.sendCmd(pushOnCmd, 'SetPush');
}
async setFtp(ftpOn) {
const ftpOnCmd = [
{
cmd: 'SetFtpV20',
param: {
Ftp: {
enable: ftpOn ? 1 : 0,
},
},
},
];
await this.sendCmd(ftpOnCmd, 'SetFtp');
}
async setAutoFocus(autoFocusStr) {
if (autoFocusStr === 'Error or not supported') {
return;
}
const autoFocusVal = parseInt(autoFocusStr, 10);
if (autoFocusVal === 0 || autoFocusVal === 1) {
const autoFocusCmd = [
{
cmd: 'SetAutoFocus',
action: 0,
param: {
AutoFocus: {
channel: Number(this.config.cameraChannel),
disable: autoFocusVal,
},
},
},
];
await this.sendCmd(autoFocusCmd, 'SetAutoFocus');
}
else {
this.log.error('Auto focus: Value not supported!');
await this.getAutoFocus();
}
}
async getAutoFocus() {
if (this.reolinkApiClient) {
try {
const getAutoFocusCmd = [
{
cmd: 'GetAutoFocus',
action: 0,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
// cmd, user, password
const autoFocusValue = await this.reolinkApiClient.post(this.genUrl('GetAutoFocus', false, false), getAutoFocusCmd);
this.log.debug(`AutoFocusValue ${JSON.stringify(autoFocusValue.status)}: ${JSON.stringify(autoFocusValue.data)}`);
if (autoFocusValue.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const autoFocus = autoFocusValue.data[0];
if ('error' in autoFocus) {
this.log.debug(`Error or not supported ${this.getAutoFocus.name}`);
await this.setState('settings.autoFocus', {
val: 'Error or not supported',
ack: true,
});
}
else {
// The datatype of the object is string.
// 1 - means forbid (but is there any effect?)
// 0 - means not disabled
const intState = autoFocus.value.AutoFocus.disable;
if (intState === 0) {
await this.setState('settings.autoFocus', {
val: '0',
ack: true,
});
}
else if (intState === 1) {
await this.setState('settings.autoFocus', {
val: '1',
ack: true,
});
}
else {
await this.setState('settings.autoFocus', {
val: 'Unknown',
ack: true,
});
}
}
}
}
catch (error) {
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
this.log.error(`get auto focus: ${error}`);
}
}
}
async getZoomAndFocus() {
if (this.reolinkApiClient) {
try {
const getZoomFocusCmd = [
{
cmd: 'GetZoomFocus',
action: 0,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
// cmd, user, password
const ZoomFocusValue = await this.reolinkApiClient.post(this.genUrl('GetZoomFocus', false, false), getZoomFocusCmd);
this.log.debug(`ZoomFocusValue ${JSON.stringify(ZoomFocusValue.status)}: ${JSON.stringify(ZoomFocusValue.data)}`);
if (ZoomFocusValue.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const ZoomFocus = ZoomFocusValue.data[0];
if ('error' in ZoomFocus) {
this.log.debug(`Error or not supported ${this.getZoomAndFocus.name}`);
return;
}
// zoom is the zoom position. See setZoomFocus()
const zoom = ZoomFocus.value.ZoomFocus.zoom.pos;
// the lens focus is adjusted during autofocus procedure.
const focus = ZoomFocus.value.ZoomFocus.focus.pos;
await this.setState('settings.setZoomFocus', {
val: zoom,
ack: true,
});
await this.setState('settings.focus', { val: focus, ack: true });
}
}
catch (error) {
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
this.log.error(`get zoom and focus: ${error}`);
}
}
}
async startZoomFocus(pos) {
const startZoomCmd = [
{
cmd: 'StartZoomFocus',
action: 0,
param: {
ZoomFocus: {
channel: Number(this.config.cameraChannel),
pos,
op: 'ZoomPos',
},
},
},
];
await this.sendCmd(startZoomCmd, 'StartZoomFocus');
}
async setPtzCheck() {
const ptzCheckCmd = [
{
cmd: 'PtzCheck',
action: 0,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
await this.sendCmd(ptzCheckCmd, 'PtzCheck');
}
async setScheduledRecording(state) {
if (state !== true && state !== false) {
this.log.error('Set scheduled recording: Value not supported!');
await this.getRecording();
return;
}
const scheduledRecordingCmd = [
{
cmd: 'SetRecV20',
param: {
Rec: {
enable: state ? 1 : 0, // The description in API Guide v8 had this key inside `schedule`, which does not work.
schedule: {
channel: Number(this.config.cameraChannel),
},
},
},
},
];
await this.sendCmd(scheduledRecordingCmd, 'SetRecV20');
}
async getRecording() {
if (!this.reolinkApiClient) {
return;
}
try {
const recordingCmd = [
{
cmd: 'GetRecV20',
action: 1,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
// cmd, user, password
const recordingSettingsResponse = await this.reolinkApiClient.post(this.genUrl('GetRecV20', false, false), recordingCmd);
this.log.debug(`recordingSettings ${JSON.stringify(recordingSettingsResponse.status)}: ${JSON.stringify(recordingSettingsResponse.data)}`);
if (recordingSettingsResponse.status !== 200) {
return;
}
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const recordingSettingValues = recordingSettingsResponse.data[0];
this.log.debug(`rec set val: ${JSON.stringify(recordingSettingValues)}`);
// This response object contains much more than `enable`.
// There would be as well `overwrite`, `postRec`, `preRec`, `saveDay` and the 4 schedule tables as "1010.."-string
if (recordingSettingValues.error != null) {
this.log.debug(`get record settings error ${recordingSettingValues.error.detail}`);
}
else {
const scheduledRecordingState = recordingSettingValues.value.Rec.enable;
if (scheduledRecordingState === 0) {
await this.setState('settings.scheduledRecording', {
val: false,
ack: true,
});
}
else if (scheduledRecordingState === 1) {
await this.setState('settings.scheduledRecording', {
val: true,
ack: true,
});
}
else {
this.log.error(`An unknown scheduled recording state was detected: ${scheduledRecordingState}`);
}
}
}
catch (error) {
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const errorMessage = error.message.toString();
if (errorMessage.includes('timeout of')) {
this.log.debug(`get recording: ${error}`);
}
else {
this.log.error(`get recording: ${error}`);
}
}
}
async audioAlarmPlay(count) {
const audioAlarmPlayCmd = [
{
cmd: 'AudioAlarmPlay',
action: 0,
param: {
alarm_mode: 'times',
manual_switch: 0,
times: count,
channel: Number(this.config.cameraChannel),
},
},
];
await this.sendCmd(audioAlarmPlayCmd, 'AudioAlarmPlay');
}
async setIrLights(irValue) {
if (irValue === 'Error or not supported') {
return;
}
if (irValue === 'Auto' || irValue === 'Off' || irValue === 'On') {
const irCmd = [
{
cmd: 'SetIrLights',
action: 0,
param: {
IrLights: {
channel: Number(this.config.cameraChannel),
state: irValue,
},
},
},
];
this.log.debug(JSON.stringify(irCmd));
await this.sendCmd(irCmd, 'SetIrLights');
}
else {
this.log.error('Set ir lights: Value not supported!');
await this.getIrLights();
}
}
async getIrLights() {
if (this.reolinkApiClient) {
try {
// cmd, channel, user, password
const IrLightValue = await this.reolinkApiClient.get(this.genUrl('GetIrLights', false, true));
this.log.debug(`IrLightValue ${JSON.stringify(IrLightValue.status)}: ${JSON.stringify(IrLightValue.data)}`);
if (IrLightValue.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const IrLights = IrLightValue.data[0];
// Check answer
if ('error' in IrLights) {
this.log.debug(`Error or not supported ${this.getIrLights.name}`);
await this.setState('settings.autoFocus', {
val: 'Error or not supported',
ack: true,
});
}
else {
await this.setState('settings.ir', {
val: IrLights.value.IrLights.state,
ack: true,
});
}
}
}
catch (error) {
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
this.log.error(`get ir lights: ${error}`);
}
}
}
async switchWhiteLed(ledState) {
const switchWhiteLedCmd = [
{
cmd: 'SetWhiteLed',
param: {
WhiteLed: {
channel: Number(this.config.cameraChannel),
state: ledState ? 1 : 0,
},
},
},
];
await this.sendCmd(switchWhiteLedCmd, 'SetWhiteLed');
}
async setWhiteLed(bright) {
const setBrightnessCmd = [
{
cmd: 'SetWhiteLed',
param: {
WhiteLed: {
channel: Number(this.config.cameraChannel),
bright,
},
},
},
];
await this.sendCmd(setBrightnessCmd, 'SetWhiteLed');
}
async setWhiteLedMode(mode) {
// mode 0 = off -> Manual switching. See https://github.com/aendue/ioBroker.reolink/issues/25 @johndoetheanimal for possible restrictions
// mode 1 = night mode -> Night Smart Mode
// mode 2 = unknown -> Maybe `Always on at night` if supported.
// mode 3 = Timer mode -> Optional: [ { "cmd":"SetWhiteLed", "action":0, "param":{ "WhiteLed":{ "LightingSchedule":{ "EndHour":23, "EndMin":50, "StartHour":23, "StartMin":29 }, "mode":3, "channel":0 } } } ]
if (mode !== 0 && mode !== 1 && mode !== 2 && mode !== 3) {
this.log.error(`White Led mode ${mode} not supported!`);
return;
}
const setModeCmd = [
{
cmd: 'SetWhiteLed',
param: {
WhiteLed: {
channel: Number(this.config.cameraChannel),
mode,
},
},
},
];
await this.sendCmd(setModeCmd, 'SetWhiteLed');
}
async getWhiteLed() {
if (this.reolinkApiClient) {
try {
const getLedCmd = [
{
cmd: 'GetWhiteLed',
action: 0,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
// cmd, channel, user, password
const whiteLedValue = await this.reolinkApiClient.post(this.genUrl('GetWhiteLed', false, true), getLedCmd);
this.log.debug(`whiteLedValue ${JSON.stringify(whiteLedValue.status)}: ${JSON.stringify(whiteLedValue.data)}`);
if (whiteLedValue.status === 200) {
this.apiConnected = true;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
const whiteLed = whiteLedValue.data[0];
const brightness = whiteLed.value.WhiteLed.bright;
const mode = whiteLed.value.WhiteLed.mode;
const switchLed = !!whiteLed.value.WhiteLed.state;
await this.setState('settings.ledBrightness', {
val: brightness,
ack: true,
});
await this.setState('settings.ledMode', { val: mode, ack: true });
await this.setState('settings.switchLed', {
val: switchLed,
ack: true,
});
}
}
catch (error) {
this.apiConnected = false;
await this.setState('network.connected', {
val: this.apiConnected,
ack: true,
});
this.log.error(`get white led: ${error}`);
}
}
}
async setPtzGuard(enable) {
const setPtzGuardCmd = [
{
cmd: 'SetPtzGuard',
action: 0,
param: {
PtzGuard: {
channel: Number(this.config.cameraChannel),
cmdStr: 'setPos',
benable: enable ? 1 : 0,
bSaveCurrentPos: 0,
},
},
},
];
await this.sendCmd(setPtzGuardCmd, 'setPtzGuard');
await this.getPtzGuardInfo();
}
async setPtzGuardTimeout(timeout) {
const setPtzGuardCmd = [
{
cmd: 'SetPtzGuard',
action: 0,
param: {
PtzGuard: {
channel: Number(this.config.cameraChannel),
cmdStr: 'setPos',
timeout,
bSaveCurrentPos: 0,
},
},