iobroker.reolink
Version:
1,296 lines (1,232 loc) • 52.3 kB
JavaScript
"use strict";
/*
* Created with @iobroker/create-adapter v2.1.1
*/
const utils = require("@iobroker/adapter-core");
const axios = require("axios").default;
const https = require("https");
let sslvalidation = false;
const refreshIntervalRecording = 10;
let refreshIntervalRecordingTimer = 0;
class ReoLinkCam extends utils.Adapter {
/**
* @param [options] options tunnel
*/
constructor(options) {
super({
...options,
name: "reolink",
});
this.apiConnected = false;
this.reolinkApiClient = null;
this.cameraModel = null;
this.refreshStateTimeout = null;
this.on("ready", this.onReady.bind(this));
this.on("stateChange", this.onStateChange.bind(this));
// this.on("objectChange", this.onObjectChange.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 == false) {
password = this.config.cameraPassword;
}
}
if (withChannel === true) {
urlString += `channel=${this.config.cameraChannel}&`;
}
if (genRndSeed === true) {
const randomseed = Math.round(Math.random() * 10000000000000000000).toString(16);
urlString += `rs=${randomseed}&`;
}
urlString += `user=${this.config.cameraUser}&password=${password}`;
return urlString;
}
async onReady() {
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;
}
if (!this.config.cameraProtocol) {
this.log.error("no protocol (http/https) set!");
return;
}
//check Checkbox of ssl validation is set
if (this.config.sslvalid == undefined) {
sslvalidation = false;
} else {
sslvalidation = this.config.sslvalid;
}
this.reolinkApiClient = axios.create({
baseURL: `${this.config.cameraProtocol}://${this.config.cameraIp}`,
timeout: 4000,
responseType: "json",
responseEncoding: "binary",
httpsAgent: new https.Agent({
rejectUnauthorized: 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 isnt working stop Adapter
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
this.getStateAsync("device.name", (err, state) => {
this.setState("settings.EmailNotification", state.val);
this.getMailNotification();
this.subscribeStates("settings.EmailNotification");
this.log.debug("Email notification subscribed");
});
this.log.debug("start subscribtions");
//State abbonieren
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");
}
//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) {
var 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) {
var 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,
});
}
}
}
//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) {
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) {
this.setState("info.connection", false, true);
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) {
var 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;
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,
},
},
];
this.sendCmd(ptzPresetCmd, "PtzCtrl");
}
async ptzCtrl2(ptzPatrolPos) {
if (ptzPatrolPos === 0) {
const ptzPresetCmd = [
{
cmd: "PtzCtrl",
param: {
channel: Number(this.config.cameraChannel),
op: "StopPatrol",
},
},
];
this.sendCmd(ptzPresetCmd, "PtzCtrl");
} else {
const ptzPresetCmd = [
{
cmd: "PtzCtrl",
param: {
channel: Number(this.config.cameraChannel),
op: "StartPatrol",
id: ptzPatrolPos,
},
},
];
this.sendCmd(ptzPresetCmd, "PtzCtrl");
}
}
async setPush(state) {
let pushOn = 1;
if (state == false) {
pushOn = 0;
}
const pushOnCmd = [
{
cmd: "SetPushV20",
param: {
Push: {
enable: pushOn,
},
},
},
];
this.sendCmd(pushOnCmd, "SetPush");
}
async setFtp(state) {
let ftpOn = 1;
if (state == false) {
ftpOn = 0;
}
const ftpOnCmd = [
{
cmd: "SetFtpV20",
param: {
Ftp: {
enable: ftpOn,
},
},
},
];
this.sendCmd(ftpOnCmd, "setFtp");
}
async setAutoFocus(state) {
if (state == "Error or not supported") {
return;
}
if (state == "0" || state == "1") {
const AutoFocusval = parseInt(state);
const autoFocusCmd = [
{
cmd: "SetAutoFocus",
action: 0,
param: {
AutoFocus: {
channel: Number(this.config.cameraChannel),
disable: AutoFocusval,
},
},
},
];
this.sendCmd(autoFocusCmd, "SetAutoFocus");
} else {
this.log.error("Auto focus: Value not supported!");
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 auto focus 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: pos,
op: "ZoomPos",
},
},
},
];
this.sendCmd(startZoomCmd, "StartZoomFocus");
}
async setPtzCheck() {
const ptzCheckCmd = [
{
cmd: "PtzCheck",
action: 0,
param: {
channel: Number(this.config.cameraChannel),
},
},
];
this.sendCmd(ptzCheckCmd, "PtzCallibration");
}
async setScheduledRecording(state) {
if (state !== true && state !== false) {
this.log.error("Set scheduled recording: Value not supported!");
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),
},
},
},
},
];
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,
});
var 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),
},
},
];
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));
this.sendCmd(irCmd, "SetIrLights");
} else {
this.log.error("Set ir lights: Value not supported!");
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];
//Antwort pruefen
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(state) {
let ledState = 0;
if (state === true) {
ledState = 1;
}
const switchWhiteLedCmd = [
{
cmd: "SetWhiteLed",
param: {
WhiteLed: {
channel: Number(this.config.cameraChannel),
state: ledState,
},
},
},
];
this.sendCmd(switchWhiteLedCmd, "SetWhiteLed");
}
async setWhiteLed(state) {
const setBrightnessCmd = [
{
cmd: "SetWhiteLed",
param: {
WhiteLed: {
channel: Number(this.config.cameraChannel),
bright: state,
},
},
},
];
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: mode,
},
},
},
];
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 ? true : false;
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(state) {
let enable = 0;
if (state === true) {
enable = 1;
}
const setPtzGuardCmd = [
{
cmd: "SetPtzGuard",
action: 0,
param: {
PtzGuard: {
channel: Number(this.config.cameraChannel),
cmdStr: "setPos",
benable: enable,
bSaveCurrentPos: 0,
},
},
},
];
await this.sendCmd(setPtzGuardCmd, "setPtzGuard");
this.getPtzGuardInfo();
}
async setPtzGuardTimeout(timeout) {
const setPtzGuardCmd = [
{
cmd: "SetPtzGuard",
action: 0,
param: {
PtzGuard: {
channel: Number(this.config.cameraChannel),
cmdStr: "setPos",
timeout: timeout,
bSaveCurrentPos: 0,
},
},
},
];
await this.sendCmd(setPtzGuardCmd, "setPtzGuardTimeout");
this.getPtzGuardInfo();
}
async refreshState(source) {
this.log.debug(`refreshState': started from "${source}"`);
this.getMdState();
this.getAiState();
refreshIntervalRecordingTimer++;
if (refreshIntervalRecordingTimer > refreshIntervalRecording) {
this.getRecording();
this.getDriveInfo();
refreshIntervalRecordingTimer = 0;
}
//Delete Timer
if (this.refreshStateTimeout) {
this.log.debug(`refreshStateTimeout: CLEARED by ${source}`);
this.clearTimeout(this.refreshStateTimeout);
}
//Create new Timer (to re-run actions)
if (!this.apiConnected) {
const notConnectedTimeout = 10;
this.refreshStateTimeout = this.setTimeout(() => {
this.refreshStateTimeout = null;
this.refreshState("timeout (API not connected)");
}, notConnectedTimeout * 1000);
//this.log.debug(`refreshStateTimeout: re-created refresh timeout (API not connected): id ${this.refreshStateTimeout}- secounds: ${notConnectedTimeout}`);
} else {
let refreshInterval = parseInt(this.config.apiRefreshInterval);
if (refreshInterval > 10000) {
refreshInterval = 10000;
}
if (refreshInterval < 1) {
refreshInterval = 1;
}
this.refreshStateTimeout = this.setTimeout(() => {
this.refreshStateTimeout = null;
this.refreshState("timeout(default");
}, refreshInterval * 1000);
//this.log.debug(`refreshStateTimeout: re-created refresh timeout (default): id ${this.refreshStateTimeout}- secounds: ${this.config.apiRefreshInterval}`);
}
}
async getMailNotification() {
if (this.reolinkApiClient) {
try {
// cmd, user, password
const mailValue = await this.reolinkApiClient.get(this.genUrl("GetEmailV20", false, false));
this.log.debug(`mailValue ${JSON.stringify(mailValue.status)}: ${JSON.stringify(mailValue.data)}`);
if (mailValue.status === 200) {
this.apiConnected = true;
await this.setState("network.connected", {
val: this.apiConnected,
ack: true,
});
const mail = mailValue.data[0];
//Antwort pruefen
if ("error" in mail) {
this.log.debug(`Error or not supported ${this.getMailNotification.name}`);
await this.setState("settings.EmailNotification", {
val: "Error or not supported",
ack: true,
});
} else {
await this.setState("RAW.Email", {
val: JSON.stringify(mail),
ack: true,
});
await this.setState("settings.EmailNotification", {
val: mail.value.Email.enable,
ack: true,
});
}
}
} catch (error) {
this.apiConnected = false;
await this.setState("network.connected", {
val: this.apiConnected,
ack: true,
});
this.log.error(`get mail notification: ${error}`);
}
}
}
async setMailNotification(state) {
if (state == 0 || state == 1) {
const mail = await this.getStateAsync("RAW.Email");
const val = JSON.parse(mail.val).value.Email;
const mailCmd = [
{
cmd: "SetEmailV20",
param: {
Email: {
ssl: val.ssl,
enable: state,
smtpPort: val.smtpPort,
smtpServer: val.smtpServer,
userName: val.userName,
nickName: val.nickName,
addr1: val.addr1,
addr2: val.addr2,
addr3: val.addr3,
interval: val.interval,
},
},
},
];
//this.log.debug(JSON.stringify(mailCmd));
this.sendCmd(mailCmd, "SetEmailV20");
} else {
this.log.error("Set mail notification: Value not supported!");
this.getMailNotification();
}
}
async rebootCam() {
if (this.reolinkApiClient) {
try {
// cmd, user, password
const mailValue = await this.reolinkApiClient.get(this.genUrl("Reboot", false, false));
this.log.debug(`mailValue ${JSON.stringify(mailValue.status)}: ${JSON.stringify(mailValue.data)}`);
if (mailValue.status === 200) {
this.apiConnected = true;
await this.setState("network.connected", {
val: this.apiConnected,
ack: true,
});
this.log.info(`${this.config.cameraIp} reboot triggered!`);
}
} catch (error) {
this.apiConnected = false;
await this.setState("network.connected", {
val: this.apiConnected,
ack: true,
});
this.log.error(`reboot cam: ${error}`);
}
}
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback last execution
*/
onUnload(callback) {
try {
// Here you must clear all timeouts or intervals that may still be active
// clearTimeout(timeout1);
// clearTimeout(timeout2);
// ...
// clearInterval(interval1);
if (this.refreshStateTimeout) {
this.log.debug("refreshStateTimeout: UNLOAD");
this.clearTimeout(this.refreshStateTimeout);
}
callback();
} catch (error) {
this.log.error(`onUnlod: ${error}`);
callback();
}
}
/**
* Is called if a subscribed state changes
*
* @param id contain the chaged property
* @param state contain the new state
*/
onStateChange(id, state) {
if (state) {
if (state.ack === false) {
// The state was changed
this.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
const idValues = id.split(".");
const propName = idValues[idValues.length - 1];
this.log.info(`Changed state: ${propName}`);
if (propName == "ir") {
this.setIrLights(state.val);
}
if (propName === "ptzPreset") {
this.ptzCtrl(state.val);
}
if (propName === "ptzPatrol") {
this.ptzCtrl2(state.val);
}
if (propName === "autoFocus") {
this.setAutoFocus(state.val);
}
if (propName === "setZoomFocus") {
this.startZoomFocus(state.val);
}
if (propName === "push") {
this.setPush(state.val);
}
if (propName === "ftp") {
this.setFtp(state.val);
}
if (propName === "scheduledRecording") {
this.setScheduledRecording(state.val);
}