node-dahua-api-zhoukai
Version:
大华api http
386 lines (357 loc) • 10.7 kB
JavaScript
// Dahua HTTP API Module
var net = require("net");
var events = require("events");
var util = require("util");
var request = require("request");
var NetKeepAlive = require("net-keepalive");
const md5 = require("md5");
const { v4 } = require("uuid");
var TRACE = true;
var BASEURI = false;
// 重试次数 (发送两次请求)
var RetryCount = 0;
var dahua = function (options) {
this.options = options;
(this.auth = {
user: options.user,
pass: options.pass,
sendImmediately: false,
}),
events.EventEmitter.call(this);
this.client = this.connect(options);
TRACE = options.log;
BASEURI = "http://" + options.host + ":" + options.port;
};
util.inherits(dahua, events.EventEmitter);
dahua.prototype.connect = function (options, headerParams) {
var self = this;
// var authHeader =
// "Basic " + new Buffer(options.user + ":" + options.pass).toString("base64");
// Connect
var client = net.connect(options, function () {
RetryCount += 1;
var header =
"GET /cgi-bin/eventManager.cgi?action=attach&codes=[AlarmLocal,VideoMotion,VideoLoss,VideoBlind] HTTP/1.0\r\n" +
"Host: " +
options.host +
":" +
options.port +
"\r\n" +
headerParams +
"\r\n" +
"Accept: multipart/x-mixed-replace\r\n\r\n";
client.write(header);
console.log("dahua.prototype.connect -> client", header);
client.setKeepAlive(true, 1000);
NetKeepAlive.setKeepAliveInterval(client, 5000); // sets TCP_KEEPINTVL to 5s
NetKeepAlive.setKeepAliveProbes(client, 12); // 60s and kill the connection.
handleConnection(self, options);
});
client.on("timeout", function () {
console.log("10 mins of inactivity");
//self.abort()
//self.connect(options)
});
client.on("data", function (data) {
handleData(self, data, options);
});
client.on("close", function () {
// Try to reconnect after 30s
// setTimeout(function () {
// self.connect(options);
// }, 30000);
handleEnd(self);
});
client.on("error", function (err) {
handleError(self, err);
});
};
dahua.prototype.ptzCommand = function (cmd, arg1, arg2, arg3, arg4) {
var self = this;
if (!cmd || isNaN(arg1) || isNaN(arg2) || isNaN(arg3) || isNaN(arg4)) {
handleError(self, "INVALID PTZ COMMAND");
return 0;
}
request(
BASEURI +
"/cgi-bin/ptz.cgi?action=start&channel=0&code=" +
ptzcommand +
"&arg1=" +
arg1 +
"&arg2=" +
arg2 +
"&arg3=" +
arg3 +
"&arg4=" +
arg4,
function (error, response, body) {
if (error || response.statusCode !== 200 || body.trim() !== "OK") {
self.emit("error", "FAILED TO ISSUE PTZ COMMAND");
}
}
);
};
dahua.prototype.ptzPreset = function (preset) {
var self = this;
if (isNaN(preset)) handleError(self, "INVALID PTZ PRESET");
request(
BASEURI +
"/cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&arg1=0&arg2=" +
preset +
"&arg3=0",
function (error, response, body) {
if (error || response.statusCode !== 200 || body.trim() !== "OK") {
self.emit("error", "FAILED TO ISSUE PTZ PRESET");
}
}
);
};
dahua.prototype.ptzZoom = function (multiple) {
var self = this;
if (isNaN(multiple)) handleError(self, "INVALID PTZ ZOOM");
if (multiple > 0) cmd = "ZoomTele";
if (multiple < 0) cmd = "ZoomWide";
if (multiple === 0) return 0;
request(
BASEURI +
"/cgi-bin/ptz.cgi?action=start&channel=0&code=" +
cmd +
"&arg1=0&arg2=" +
multiple +
"&arg3=0",
function (error, response, body) {
if (error || response.statusCode !== 200 || body.trim() !== "OK") {
self.emit("error", "FAILED TO ISSUE PTZ ZOOM");
}
}
);
};
dahua.prototype.ptzMove = function (err, success, direction, action, speed) {
console.log("dahua.prototype.ptzMove -> action", action);
var self = this;
if (isNaN(speed)) {
err("INVALID PTZ SPEED");
// handleError(self, "INVALID PTZ SPEED")
}
if (action !== "start" && action !== "stop") {
err("INVALID PTZ COMMAND");
// handleError(self, "INVALID PTZ COMMAND");
return 0;
}
if (
direction !== "Up" &&
direction !== "Down" &&
direction !== "Left" &&
direction !== "Right" &&
direction !== "LeftUp" &&
direction !== "RightUp" &&
direction !== "LeftDown" &&
direction !== "RightDown"
) {
err("INVALID PTZ DIRECTION");
// handleError(self, "INVALID PTZ DIRECTION");
return 0;
}
const options = {
url:
BASEURI +
"/cgi-bin/ptz.cgi?action=" +
action +
"&channel=0&code=" +
direction +
"&arg1=" +
speed +
"&arg2=" +
speed +
"&arg3=0",
auth: this.auth,
};
RequestFun(options.url, options.auth)
.then(function (res) {
success(res);
})
.catch(function (error) {
err(error);
});
};
dahua.prototype.ptzStatus = function () {
var self = this;
request(BASEURI + "/cgi-bin/ptz.cgi?action=getStatus", function (
error,
response,
body
) {
if (!error && response.statusCode === 200) {
body = body.toString().split("\r\n");
self.emit("ptzStatus", body);
} else {
self.emit("error", "FAILED TO QUERY STATUS");
}
});
};
dahua.prototype.dayProfile = function () {
var self = this;
request(
BASEURI +
"/cgi-bin/configManager.cgi?action=setConfig&VideoInMode[0].Config[0]=1",
function (error, response, body) {
if (!error && response.statusCode === 200) {
if (body === "Error") {
// Didnt work, lets try another method for older cameras
request(
BASEURI +
"/cgi-bin/configManager.cgi?action=setConfig&VideoInOptions[0].NightOptions.SwitchMode=0",
function (error, response, body) {
if (error || response.statusCode !== 200) {
self.emit("error", "FAILED TO CHANGE TO DAY PROFILE");
}
}
);
}
} else {
self.emit("error", "FAILED TO CHANGE TO DAY PROFILE");
}
}
);
};
dahua.prototype.nightProfile = function () {
var self = this;
request(
BASEURI +
"/cgi-bin/configManager.cgi?action=setConfig&VideoInMode[0].Config[0]=2",
function (error, response, body) {
if (!error && response.statusCode === 200) {
if (body === "Error") {
// Didnt work, lets try another method for older cameras
request(
BASEURI +
"/cgi-bin/configManager.cgi?action=setConfig&VideoInOptions[0].NightOptions.SwitchMode=3",
function (error, response, body) {
if (error || response.statusCode !== 200) {
self.emit("error", "FAILED TO CHANGE TO NIGHT PROFILE");
}
}
);
}
} else {
self.emit("error", "FAILED TO CHANGE TO NIGHT PROFILE");
}
}
);
};
function handleData(self, data, options) {
if (TRACE) console.log("Data: " + data);
data = data.toString().split("\r\n");
var i = Object.keys(data);
i.forEach(function (id) {
if (data[id] === "HTTP/1.1 401 Unauthorized") {
if (RetryCount <= 3) {
console.log("handleData -> data[1]", data[1]);
generateAuthorization(data[1], options.user, options.pass, function (
params
) {
console.log("handleData -> headerParmas", params);
self.connect(options, params);
});
}
}
if (data[id].startsWith("Code=")) {
var alarm = data[id].split(";");
var code = alarm[0].substr(5);
var action = alarm[1].substr(7);
var index = alarm[2].substr(6);
self.emit("alarm", code, action, index);
}
});
}
function handleConnection(self, options) {
if (TRACE) console.log("Connected to " + options.host + ":" + options.port);
//self.socket = socket;
self.emit("connect");
}
function handleEnd(self) {
if (TRACE) console.log("Connection closed!");
self.emit("end");
}
function handleError(self, err) {
if (TRACE) console.log("Connection error: " + err);
RetryCount = 0;
self.emit("error", err);
}
/**
* 根据返回的头信息 生成验证信息头
* @param {*} data 数据
* @param {*} username 用户名
* @param {*} password 密码
* @param {*} callback 回调
*/
function generateAuthorization(data, username, password, callback) {
// 最终结果值
const endParams = {
realm: "",
qop: "",
nonce: "",
opaque: "",
cnonce: v4().replace(/-/gi, ""),
nc: "00000001",
};
console.log("data", data);
console.log("\n");
const rr = data.replace("WWW-Authenticate", "Authenticate");
rr.split(",").forEach((item) => {
let r = item.trim().replace(/=/gi, ":");
if (foramtParams(r, "realm:")) endParams.realm = foramtParams(r, "realm:");
if (foramtParams(r, "qop:")) endParams.qop = foramtParams(r, "qop:");
if (foramtParams(r, "nonce:")) endParams.nonce = foramtParams(r, "nonce:");
if (foramtParams(r, "opaque:"))
endParams.opaque = foramtParams(r, "opaque:");
});
/**
* 获取参数时的处理
* @param {*} data 数据
* @param {*} val 选择搜索的值
*/
function foramtParams(data, val) {
if (data.indexOf(val) !== -1 || data.indexOf(val) === 0) {
return data.substring(data.indexOf(val) + val.length).replace(/"/gi, "");
}
return false;
}
// HA1部分
const HA1 = md5(`${username}:${endParams.realm}:${password}`);
// HA2
const HA2 = md5("GET:192.168.33.241:80");
const response = md5(
`${HA1}:${endParams.nonce}:${endParams.nc}:${endParams.cnonce}:${endParams.qop}:${HA2}`
);
const returnValidationHeader = `Authorization: Digest username="${username}",realm="${endParams.realm}",nonce="${endParams.nonce}",uri="192.168.33.241:80",algorithm="MD5",cnonce="${endParams.cnonce}",nc="${endParams.nc}",qop="${endParams.qop}",response="${response}"`;
callback(returnValidationHeader);
}
/**
* 通用request请求
* @param {*} url 地址
* @param {*} auth 验证
*/
function RequestFun(url, auth) {
return new Promise(function (resolve, reject) {
const options = {
url,
auth,
};
request(options, function (error, response, body) {
if (error || response.statusCode !== 200 || body.trim() !== "OK") {
reject({
msg: response.statusMessage,
code: response.statusCode,
});
} else {
resolve(response.statusMessage);
}
});
});
}
String.prototype.startsWith = function (str) {
return this.slice(0, str.length) == str;
};
exports.dahua = dahua;