UNPKG

node-dahua-api-zhoukai

Version:
386 lines (357 loc) 10.7 kB
#!/usr/bin/nodejs // 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;