UNPKG

node-red-contrib-hikvision-ultimate

Version:

A native set of nodes for Hikvision (and compatible) Cameras, Alarms, Radars, NVR, Doorbells, etc.

245 lines (212 loc) 9.31 kB
module.exports = function (RED) { function hikvisionUltimateDoorbell(config) { RED.nodes.createNode(this, config); var node = this; node.topic = config.topic || config.name; node.server = RED.nodes.getNode(config.server) const isDebug = node.server && node.server.debug; const logDebug = (text) => { if (isDebug) RED.log.info(`hikvisionUltimateDoorbell: ${text}`); }; node.ringStatus = (config.ringStatus === null || config.ringStatus === undefined) ? "all" : config.ringStatus.toLowerCase(); node.floorNo = (config.floorNo === null || config.floorNo === undefined) ? "all" : config.floorNo; node.unitNo = (config.unitNo === null || config.unitNo === undefined) ? "all" : config.unitNo; node.zoneNo = (config.zoneNo === null || config.zoneNo === undefined) ? "all" : config.zoneNo; node.buildingNo = (config.buildingNo === null || config.buildingNo === undefined) ? "all" : config.buildingNo; node.currentEmittedMSG = {}; // To keep the current status and avoid emitting msg if already emitted. const extractPressedButton = (callerInfo) => { if (!callerInfo || typeof callerInfo !== "object") return null; const candidates = [ callerInfo.buttonNo, callerInfo.devNo, callerInfo.keyNo, callerInfo.callButtonNo ]; for (const candidate of candidates) { if (candidate !== undefined && candidate !== null && candidate !== "") return candidate; } return null; }; node.setNodeStatus = ({ fill, shape, text }) => { var dDate = new Date(); node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" }) } // Called from config node, to send output to the flow // Expected return JSON from ISAPI/VideoIntercom/callerInfo // { // "CallerInfo": { // "buildingNo": 1, // "floorNo": 1, // "zoneNo": 1, // "unitNo": 1, // "devNo": 88, // "devType": 1, // "lockNum": 1, // "status": "idle" ("idle,ring,onCall") // } // } // "devType":{ 1-door station, 2-master station, // 3-indoor station, 4-outer door station, 5-villa door station, 6-doorphone, // 7-Infosight Client Software, 8-iVMS-4200 Client Software, 9-APP, // 10-doorbell, 11-VOIP Client Software, 12-network camera, 13-access control device*/ node.sendPayload = (_msg) => { if (_msg === null || _msg === undefined) return; if (_msg.type !== undefined && _msg.type === 'img') { _msg.extension = extension; node.send([null, null, _msg]); return; } _msg.topic = node.topic; _msg.payload = true; if (_msg.hasOwnProperty("errorDescription")) { logDebug(`Connection status message: ${_msg.errorDescription || ""}`); node.send([null, _msg]); return; }; // It's a connection error/restore comunication. //node.send([_msg, null]); if (_msg.hasOwnProperty("CallerInfo") && _msg.CallerInfo.hasOwnProperty("status")) { const callerInfo = _msg.CallerInfo; const normalizedStatus = callerInfo.status.toString().toLowerCase(); const pressedButton = extractPressedButton(callerInfo); // Backward compatible output aliases. _msg.callerInfo = callerInfo; _msg.ringStatus = normalizedStatus; _msg.buildingNo = callerInfo.buildingNo; _msg.floorNo = callerInfo.floorNo; _msg.zoneNo = callerInfo.zoneNo; _msg.unitNo = callerInfo.unitNo; _msg.devNo = callerInfo.devNo; _msg.devType = callerInfo.devType; _msg.lockNum = callerInfo.lockNum; if (pressedButton !== null) _msg.pressedButton = pressedButton; if ( ((node.ringStatus === "all" || node.ringStatus === normalizedStatus)) && (node.floorNo === "all" || node.floorNo === callerInfo.floorNo.toString()) && (node.unitNo === "all" || node.unitNo === callerInfo.unitNo.toString()) && (node.zoneNo === "all" || node.zoneNo === callerInfo.zoneNo.toString()) && (node.buildingNo === "all" || node.buildingNo === callerInfo.buildingNo.toString()) ) { delete _msg._msgid; // To allow objects compare delete node.currentEmittedMSG._msgid; // To allow objects compare if (RED.util.compareObjects(node.currentEmittedMSG, _msg)) { logDebug("Skipping duplicate CallerInfo notification"); return; // Omit sending the same notification more than once } node.currentEmittedMSG = _msg; // Oputputs only msg that are no "idle" if (normalizedStatus !== "idle") { logDebug(`Forwarding CallerInfo status ${normalizedStatus}`); node.send([_msg, null]); } else { logDebug("CallerInfo status is idle, not forwarding"); } } } } // On each deploy, unsubscribe+resubscribe if (node.server) { node.server.removeClient(node); node.server.addClient(node); } this.on('input', function (msg) { // node.request = async function (_callerNode, _method, _URL, _body) { if (msg.hasOwnProperty("openDoor")) { // Open the door latch let iDoor = msg.openDoor || 1; logDebug(`Open door request for gateway ${iDoor}`); // <RemoteOpenDoor version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema"> // <gateWayIndex> // <!--required, xs:integer, access control point No., currently, the value can only be equal to 1--> // </gateWayIndex> // <command> // <!--required, xs:string, unlocking command, currently, only the "unlock" is supported--> // </command> // <controlSrc> // <!--required, xs:string, control command source, the format is "web site+IP address"--> // </controlSrc> // </RemoteOpenDoor> let sBody = `<RemoteOpenDoor version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema"> <gateWayIndex>`+ iDoor + `</gateWayIndex> <command>unlock</command> <controlSrc>HikvisionUltimate</controlSrc> </RemoteOpenDoor> `; // Try with API 2.0 node.server.request(node, "PUT", "/ISAPI/VideoIntercom/remoteOpenDoor", sBody).then(success => { node.setNodeStatus({ fill: "green", shape: "ring", text: "Door unlocked" }); msg.payload = true; node.send([msg, null]); logDebug("Door unlocked via API 2.0"); }).catch(error => { // Try with API 1.0 node.server.request(node, "PUT", "/ISAPI/AccessControl/RemoteControl/door/" + iDoor, "<RemoteControlDoor><cmd>open</cmd></RemoteControlDoor>").then(success => { node.setNodeStatus({ fill: "green", shape: "ring", text: "Door unlocked" }); msg.payload = true; node.send([msg, null]); logDebug("Door unlocked via API 1.0 fallback"); }).catch(error => { node.setNodeStatus({ fill: "red", shape: "ring", text: "Error unlocking door " + error.message }); msg.payload = false; node.send([msg, null]); logDebug(`Error unlocking door: ${error.message || error}`); }); }); } if (msg.hasOwnProperty("hangUp")) { logDebug("Hang-up requested from flow input"); try { // Both calls are needed to stop current ring and call // Stop ringing. This stops the APP ringing. // Try with API 2.0, but with some firmware it doesn't work node.server.request(node, "DELETE", "/ISAPI/VideoIntercom/ring", "").then(success => { node.setNodeStatus({ fill: "green", shape: "ring", text: "Stop ringing" }); logDebug("Stop ringing sent"); }).catch(error => { node.setNodeStatus({ fill: "red", shape: "ring", text: "Error stop ringing " + error.message }); RED.log.error("hikvisionUltimateDoorbell: Error stopping ring " + error.message); }); // Stop current call initiated by the intercom. This stops the intercom call. // Try with API 2.0 setTimeout(() => { //const jHangUp = JSON.stringify(JSON.parse(`{"CallSignal": { "cmdType": "hangUp" } }`)); try { const jHangUp = JSON.parse(JSON.stringify(JSON.parse(`{"CallSignal": {"cmdType": "hangUp"}}`))) node.server.request(node, "PUT", "/ISAPI/VideoIntercom/callSignal?format=json", jHangUp).then(success => { node.setNodeStatus({ fill: "green", shape: "ring", text: "Hang Up" }); logDebug("Hang up command sent"); }).catch(error => { RED.log.error("hikvisionUltimateDoorbell: Error hangUp " + error.message); logDebug(`Hang up command failed: ${error.message || error}`); }); } catch (error) { } setTimeout(() => { try { const jReject = JSON.parse(JSON.stringify(JSON.parse(`{"CallSignal": {"cmdType": "reject"}}`))) node.server.request(node, "PUT", "/ISAPI/VideoIntercom/callSignal?format=json", jReject).then(success => { node.setNodeStatus({ fill: "green", shape: "ring", text: "reject" }); logDebug("Reject command sent"); }).catch(error => { RED.log.error("hikvisionUltimateDoorbell: Error reject " + error.message); logDebug(`Reject command failed: ${error.message || error}`); }); } catch (error) { } }, 1000); }, 1000); } catch (error) { } } }); node.on("close", function (done) { if (node.server) { node.server.removeClient(node); } if (node.server) { node.server.removeClient(node); } done(); }); } RED.nodes.registerType("hikvisionUltimateDoorbell", hikvisionUltimateDoorbell); }