UNPKG

node-red-contrib-hikvision-ultimate

Version:

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

438 lines (396 loc) 17.2 kB
module.exports = function (RED) { function hikvisionUltimateAlarm(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(`hikvisionUltimateAlarm: ${text}`); }; node.reactto = (config.reactto === null || config.reactto === undefined) ? "vmd" : config.reactto.toLowerCase();// Rect to alarm coming from... node.filterzone = config.filterzone || "0";// Rect to alarm coming from zone... node.channelID = config.channelID || "0";// Rect to alarm coming from channelID... node.currentAlarmMSG = {}; // Stores the current alarm object node.total_alarmfilterduration = 0; // stores the total time an alarm has been true in the alarmfilterperiod time. node.isNodeInAlarm = false; // Stores the current state of the filtered alarm. node.isRunningTimerFilterPeriod = false; // Indicates wether the period timer is running; node.alarmfilterduration = config.alarmfilterduration !== undefined ? Number(config.alarmfilterduration) : 0; node.alarmfilterperiod = config.alarmfilterperiod !== undefined ? Number(config.alarmfilterperiod) : 0; node.devicetype = config.devicetype !== undefined ? config.devicetype : 0; if (node.devicetype == 0) node.alarmfilterduration = 0; // Normal events, set it to zero node.setNodeStatus = ({ fill, shape, text }) => { var dDate = new Date(); node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" }) } // 29/01/2021 start the timer that counts the time the alarm has been true // ################################### startTimerAlarmFilterDuration = () => { node.timer_alarmfilterduration = setInterval(() => { if (node.currentAlarmMSG.hasOwnProperty("payload")) { if (node.currentAlarmMSG.payload === true) { node.total_alarmfilterduration += 1; if (node.isRunningTimerFilterPeriod) node.setNodeStatus({ fill: "red", shape: "ring", text: "Zone " + node.currentAlarmMSG.zone + " pre alert count " + node.total_alarmfilterduration }); if (node.total_alarmfilterduration >= node.alarmfilterduration) { if (!node.isNodeInAlarm) { // Emit alarm if (node.timer_alarmfilterperiod !== null) clearTimeout(node.timer_alarmfilterperiod); //node.setNodeStatus({ fill: "yellow", shape: "ring", text: "STOP TimerFilterPeriod" }); node.isRunningTimerFilterPeriod = false; node.isNodeInAlarm = true; node.total_alarmfilterduration = 0; node.setNodeStatus({ fill: "red", shape: "dot", text: "Zone " + node.currentAlarmMSG.zone + " alarm" }); node.send([node.currentAlarmMSG, null, null]); } else { node.total_alarmfilterduration = 0; } } } } }, 1000); } // 29/01/2021 This timer resets the node.total_alarmfilterduration startTimerAlarmFilterPeriod = () => { //node.setNodeStatus({ fill: "yellow", shape: "ring", text: "START TimerFilterPeriod" }); node.isRunningTimerFilterPeriod = true; node.total_alarmfilterduration = 0; node.timer_alarmfilterperiod = setTimeout(() => { node.total_alarmfilterduration = 0; //node.setNodeStatus({ fill: "yellow", shape: "ring", text: "ELAPSED TimerFilterPeriod" }); node.isRunningTimerFilterPeriod = false; }, node.alarmfilterperiod * 1000); } if (node.alarmfilterduration !== 0) startTimerAlarmFilterDuration(); // If filter is enabled, start timer // ################################### // Called from config node, to send output to the flow node.sendPayload = (_msg, extension = '') => { if (_msg === null || _msg === undefined) return; _msg.topic = node.topic; if (_msg.hasOwnProperty("errorDescription")) { logDebug(`Connection status message: ${_msg.errorDescription || ""}`); node.send([null, _msg, null]); return; }; // It's a connection error/restore comunication. if (!_msg.hasOwnProperty("payload") || (_msg.hasOwnProperty("payload") && _msg.payload === undefined)) { logDebug("Discarded incoming message without payload"); return; } if (_msg.type === 'img') { _msg.extension = extension; logDebug("Forwarding image payload"); node.send([null, null, _msg]); return; } var oRetMsg = {}; // Return message // Check what alarm type must i search for. // Security devices issue a CID alarm //#region "CID" // ################################# if ((node.devicetype == 1 || node.devicetype == 2) && _msg.payload.hasOwnProperty("CIDEvent") && _msg.payload.CIDEvent.type.toString().toLowerCase() === "zonealarm" && _msg.payload.CIDEvent.hasOwnProperty("zone")) { oRetMsg.topic = _msg.topic; oRetMsg.alarm = _msg.payload; // Put the full alarm description here. oRetMsg.zone = _msg.payload.CIDEvent.zone + 1; // The zone on device's ISAPI is base 0, while the zone on UI is base 1. // CID Alarm (node.reactto is CID:startAlarmCode-endAlarmCode) let sAlarmCodeStart = node.reactto.split(":")[1].split("-")[0]; let sAlarmCodeEnd = node.reactto.split(":")[1].split("-")[1]; if (Number(node.filterzone) === 0 || Number(node.filterzone) === Number(oRetMsg.zone)) { // Filter only selcted zones // Get the Hikvision alarm codes, that differs from standard SIA codes. switch (_msg.payload.CIDEvent.code.toString().toLowerCase()) { // Standard SIA Code is _msg.payload.CIDEvent.standardCIDcode case sAlarmCodeStart: // Starts alarm oRetMsg.payload = true; node.setNodeStatus({ fill: "red", shape: "ring", text: "Zone " + oRetMsg.zone + " pre alert" }); logDebug(`CID alarm start for zone ${oRetMsg.zone} (code ${_msg.payload.CIDEvent.code})`); break; case sAlarmCodeEnd: // End alarm. oRetMsg.payload = false; node.setNodeStatus({ fill: "green", shape: "dot", text: "Zone " + oRetMsg.zone + " normal" }); logDebug(`CID alarm end for zone ${oRetMsg.zone} (code ${_msg.payload.CIDEvent.code})`); break; default: // Unknown CID code. node.setNodeStatus({ fill: "grey", shape: "ring", text: "Zone " + oRetMsg.zone + " unknowk CID code " + _msg.payload.CIDEvent.code }); logDebug(`Unknown CID code ${_msg.payload.CIDEvent.code} for zone ${oRetMsg.zone}`); return; // Unknown state } } } // ################################# //#endregion // Camera/NVR, no CID codes, just standard hikvision strings //#region "STANDARD" // ################################# // STANDARD MOTION EVENT VERSION 2.0 // { // "topic": "", // "payload": { // "$": { // "version": "2.0", // "xmlns": "http://www.isapi.org/ver20/XMLSchema" // }, // "ipAddress": "192.168.1.32", // "portNo": "80", // "protocolType": "HTTP", // "macAddress": "xxx", // "dynChannelID": "13", // "channelID": "13", // "dateTime": "2021-01-29T09:58:05+01:00", // "activePostCount": "38", // "eventType": "VMD", // "eventState": "active", // "eventDescription": "Motion alarm", // "channelName": "Viessmann" // }, // "_msgid": "913ac479.52e768" // } // STANDARD MOTION EVENT VERSION 1.0 // { // "payload":{ // "$":{ // "version":"1.0", // "xmlns":"http://www.hikvision.com/ver20/XMLSchema" // }, // "ipAddress":"192.168.60.25", // "portNo":"80", // "protocol":"HTTP", // "macAddress":"44:47:cc:cf:82:17", // "dynChannelID":"4", // "dateTime":"2021-07-01T09:11:21+02:00", // "activePostCount":"1", // "eventType":"VMD", // "eventState":"active", // "eventDescription":"Motion alarm" // } // } // SMART EVENT VERSION 2.0 // { // "topic": "", // "payload": { // "$": { // "version": "2.0", // "xmlns": "http://www.isapi.org/ver20/XMLSchema" // }, // "ipAddress": "192.168.1.32", // "portNo": "80", // "protocolType": "HTTP", // "macAddress": "xxx", // "dynChannelID": "13", // "channelID": "13", // "dateTime": "2021-01-29T10:26:44+01:00", // "activePostCount": "1", // "eventType": "fielddetection", // "eventState": "active", // "eventDescription": "fielddetection alarm", // "channelName": "Viessmann", // "DetectionRegionList": { // "DetectionRegionEntry": { // "regionID": "0", // "RegionCoordinatesList": "\n", // "TargetRect": { // "X": "0.000000", // "Y": "0.000000", // "width": "0.000000", // "height": "0.000000" // } // } // } // }, // "_msgid": "853ba286.3a708" // } // DURATION EVENT VERSION 2.0 (this is a uration event of some alarm not trapped) https://github.com/Supergiovane/node-red-contrib-hikvision-ultimate/issues/16 // { // "$": { // "version": "2.0", // "xmlns": "http://www.hikvision.com/ver20/XMLSchema" // }, // "ipAddress": "10.0.0.2", // "ipv6Address": "::ffff:10.0.0.2", // "portNo": "80", // "protocol": "HTTP", // "macAddress": "08:a1:89:6a:3d:59", // "channelID": "1", // "dateTime": "2021-04-24T16:59:47+08:00", // "activePostCount": "1", // "eventType": "duration", // "eventState": "active", // "eventDescription": "duration alarm", // "channelName": "Hik", // "DurationList": { // "Duration": { // "relationEvent": "fielddetection" // } // }, // "isDataRetransmission": "false" // } if (node.devicetype == 0) { var sEventType = ""; var bAlarmStatus = false; let sEventDesc = ""; sEventDesc = (_msg.payload.hasOwnProperty("eventDescription") ? _msg.payload.eventDescription : ""); const isMatchingEventType = (eventType, reactTo) => { // Some firmwares use different names for the same event if (eventType === reactTo) return true; // Temperature Measurement Alarm (TMA) is commonly used for thermometry/temperature measurement events if ((reactTo === "thermometry" && eventType === "tma") || (reactTo === "tma" && eventType === "thermometry")) return true; return false; }; if (_msg.payload.hasOwnProperty("eventType")) { // Check if it's only a hearbeat alarm sEventType = _msg.payload.eventType.toString().toLowerCase(); if (sEventType === "videoloss" && _msg.payload.hasOwnProperty("activePostCount") && _msg.payload.activePostCount == "0") { node.setNodeStatus({ fill: "green", shape: "ring", text: "Received HeartBeat (the device is online)" }); logDebug("Heartbeat received, ignoring"); return; // It's a Heartbeat } if (sEventType === "duration" && !node.isNodeInAlarm) { // This is a duration event of an alarm, so i must get the real alarm event from the relationEvent prop if (_msg.payload.hasOwnProperty("DurationList") && _msg.payload.DurationList.hasOwnProperty("Duration") && _msg.payload.DurationList.Duration.hasOwnProperty("relationEvent")) { sEventType = _msg.payload.DurationList.Duration.relationEvent.toString().toLowerCase(); sEventDesc = sEventDesc + " of " + sEventType + ". Zone number will be ignored."; } } } // Filter channel let sChannelID = _msg.payload.channelID; // Filter regionID (Zone) let iRegionID = 0; let oDetectionRegionEntry = null; if (_msg.payload.hasOwnProperty("DetectionRegionList") && _msg.payload.DetectionRegionList.hasOwnProperty("DetectionRegionEntry")) { oDetectionRegionEntry = _msg.payload.DetectionRegionList.DetectionRegionEntry; if (Array.isArray(oDetectionRegionEntry)) oDetectionRegionEntry = oDetectionRegionEntry[0]; } if (oDetectionRegionEntry && oDetectionRegionEntry.hasOwnProperty("regionID")) iRegionID = Number(oDetectionRegionEntry.regionID);// Era + 1; if (Number(node.channelID) === 0 || Number(node.channelID) === Number(sChannelID)) { // Filter only selcted channel if (Number(node.filterzone) === 0 || Number(node.filterzone) === iRegionID || iRegionID === 0) { // Filter only selcted regionID (zone). iRegionID is 0 when the eventtype is "duration" if (_msg.payload.hasOwnProperty("eventState")) { bAlarmStatus = (_msg.payload.eventState.toString().toLowerCase() === "active" ? true : false); } else { // Mmmm.... no event state? node.setNodeStatus({ fill: "red", shape: "ring", text: "Received alarm but no state!" }); return; } // check alarm filter var aReactTo = node.reactto.split(","); // node.reactto can contain multiple names for the same event, depending on firmware for (let index = 0; index < aReactTo.length; index++) { const element = aReactTo[index]; if (element === null || element === undefined) continue; const reactToEvent = element.trim(); if (reactToEvent !== "" && isMatchingEventType(sEventType, reactToEvent)) { oRetMsg.payload = bAlarmStatus; oRetMsg.topic = _msg.topic; oRetMsg.channelid = sChannelID; // Channel ID (in case of NVR) oRetMsg.zone = iRegionID; // Zone oRetMsg.description = sEventDesc; // Attach thermometry/TMA details if present (useful for thermal cameras) if (oDetectionRegionEntry && oDetectionRegionEntry.hasOwnProperty("TMA")) oRetMsg.tma = oDetectionRegionEntry.TMA; node.isNodeInAlarm = bAlarmStatus; logDebug(`Event ${sEventType} state ${bAlarmStatus ? "active" : "inactive"} channel ${sChannelID} zone ${iRegionID}`); break; // Find first occurrence, exit. } } } } } // ################################# //#endregion // 29/01/2020 check wether the filter is enabled or not if (oRetMsg.hasOwnProperty("payload")) { if (node.alarmfilterduration == 0) { node.send([oRetMsg, null, null]); logDebug(`Forwarded alarm immediately (${oRetMsg.description || "no description"})`); } else { // Sends the false only in case the isNodeInAlarm is true. node.currentAlarmMSG = oRetMsg; if (oRetMsg.payload === false && node.isNodeInAlarm) { node.send([oRetMsg, null, null]); node.currentAlarmMSG = {}; node.isNodeInAlarm = false; logDebug("Alarm reset forwarded after filter duration"); } else if (oRetMsg.payload === true && !node.isNodeInAlarm) { if (!node.isRunningTimerFilterPeriod) { startTimerAlarmFilterPeriod(); logDebug("Alarm filter timer started"); } } } } } // On each deploy, unsubscribe+resubscribe if (node.server) { node.server.removeClient(node); node.server.addClient(node); } this.on('input', function (msg) { // TEST VERSION 2.0 msg.payload = `{ "$": { "version": "2.0", "xmlns": "http://www.hikvision.com/ver20/XMLSchema" }, "ipAddress": "10.0.0.2", "ipv6Address": "::ffff:10.0.0.2", "portNo": "80", "protocol": "HTTP", "macAddress": "08:a1:89:6a:3d:59", "channelID": "1", "dateTime": "2021-04-24T16:59:47+08:00", "activePostCount": "1", "eventType": "duration", "eventState": "active", "eventDescription": "duration alarm", "channelName": "Hik", "DurationList": { "Duration": { "relationEvent": "fielddetection" } }, "isDataRetransmission": "false" }`; // TEST VERSION 1.0 msg.payload = `{ "$":{ "version":"1.0", "xmlns":"http://www.hikvision.com/ver20/XMLSchema" }, "ipAddress":"192.168.60.25", "portNo":"80", "protocol":"HTTP", "macAddress":"44:47:cc:cf:82:17", "dynChannelID":"4", "dateTime":"2021-07-01T09:11:21+02:00", "activePostCount":"1", "eventType":"VMD", "eventState":"active", "eventDescription":"Motion alarm" }`; try { msg.payload = JSON.parse(msg.payload); node.sendPayload(msg); setTimeout(() => { msg.payload = `{"$":{"version":"2.0","xmlns":"http://www.hikvision.com/ver20/XMLSchema"},"ipAddress":"10.0.0.2","ipv6Address":"::ffff:10.0.0.2","portNo":"80","protocol":"HTTP","macAddress":"08:a1:89:6a:3d:59","channelID":"1","dateTime":"2021-04-24T16:59:56+08:00","activePostCount":"1","eventType":"fielddetection","eventState":"inactive","eventDescription":"fielddetection alarm","channelName":"Hik"}`; try { msg.payload = JSON.parse(msg.payload); node.sendPayload(msg); } catch (error) { } }, 5000); } catch (error) { } }); node.on("close", function (done) { if (node.server) { node.server.removeClient(node); } if (node.timer_alarmfilterduration !== null) clearInterval(node.timer_alarmfilterduration); if (node.timer_alarmfilterperiod !== null) clearTimeout(node.timer_alarmfilterperiod); if (node.server) { node.server.removeClient(node); } done(); }); } RED.nodes.registerType("hikvisionUltimateAlarm", hikvisionUltimateAlarm); }