Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

node-red-contrib-hikvision-ultimate

Package Overview
Dependencies
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-red-contrib-hikvision-ultimate - npm Package Compare versions

Comparing version 0.0.8 to 0.0.9

5

CHANGELOG.md

@@ -5,2 +5,7 @@ # node-red-contrib-hikvision-ultimate

<p>
<b>Version 0.0.9 BETA</b> November 2020<br/>
- Replaced the node urllib with node-fetch.
- Alert node now filters heartbeath without sending unuseful payloads.
</p>
<p>
<b>Version 0.0.8 BETA</b> November 2020<br/>

@@ -7,0 +12,0 @@ - ANPR node now emits connect and disconnect messages.

263

nodes/ANPR-config.js

@@ -5,7 +5,6 @@

const urllib = require('urllib');
var Agent = require('agentkeepalive');
const xml2js = require('xml2js').parseString;
const DigestFetch = require('digest-fetch')
const AbortController = require('abort-controller');
const xml2js = require('xml2js').Parser({explicitArray: false}).parseString;
function ANPRconfig(config) {

@@ -32,90 +31,122 @@ RED.nodes.createNode(this, config)

node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Connecting..." });
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client.
var controller = new AbortController(); // For aborting the stream request
var options = {
"digestAuth": node.credentials.user + ":" + node.credentials.password,
"streaming": false,
"timeout": 5000,
"method": "POST",
'followRedirect': true,
'followAllRedirects': true,
"data": "<AfterTime><picTime>202001010101010000</picTime></AfterTime>",
"headers": {
'content-type': 'application/xml',
}
};
// These properties are part of the Fetch Standard
method: 'POST',
headers: { 'content-type': 'application/xml' }, // request headers. format is the identical to that accepted by the Headers constructor (see below)
body: "<AfterTime><picTime>202001010101010000</picTime></AfterTime>", // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options, function (err, data, res) {
if (err) {
//console.log("MAIN ERROR: " + JSON.stringify(err));
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
})
// The following properties are node-fetch extensions
follow: 20, // maximum redirect count. 0 to not follow redirect
timeout: 5000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: true, // support gzip/deflate content encoding. false to disable
size: 0, // maximum response body size in bytes. 0 to disable
agent: null // http(s).Agent instance or function that returns an instance (see below)
}
client.fetch("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options)
.then(response => {
//RED.log.error(response.status + " " + response.statusText);
if (response.statusText === "Unauthorized") {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText });
}
node.isConnected = false;
setTimeout(node.initPlateReader, 10000); // Reconnect
return;
}
if (data) {
node.lastPicName = "";
var oPlates = null;
try {
var sRet = data.toString();
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"
if (i > -1) {
sRet = sRet.substring(i);
// By xml2js
xml2js(sRet, function (err, result) {
oPlates = result;
});
} else {
i = sRet.indexOf("{") // It's a Json
if (response.statusText === "Ok") {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." });
}
return response.body;
}).then(body => {
body.on('readable', () => {
let chunk;
var sRet = "";
node.lastPicName = "";
while (null !== (chunk = body.read())) {
sRet = chunk.toString();
}
//console.log ("BANANA " + sRet)
var oPlates = null;
try {
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"
if (i > -1) {
sRet = sRet.substring(i);
oPlates = JSON.parse(result);
// By xml2js
xml2js(sRet, function (err, result) {
oPlates = result;
});
} else {
i = sRet.indexOf("{") // It's a Json
if (i > -1) {
sRet = sRet.substring(i);
oPlates = JSON.parse(result);
} else {
// Invalid body
RED.log.error("ANPR-config: DecodingBody:" + error);
node.lastPicName = ""; // This raises an error, below.
oPlates = null; // Set null
}
}
} catch (error) {
RED.log.error("ANPR-config: ERRORE CATCHATO initPlateReader:" + error);
node.lastPicName = ""; // This raises an error, below.
}
} catch (error) { console.log("ERRORE CATCHATO initPlateReader" + error); }
// Working the plates. Must be sure, that no error occurs, before acknolwedging the plate last picName
try {
if (oPlates !== null) {
if (oPlates.Plates !== null) {
if (oPlates.Plates.hasOwnProperty("Plate") && oPlates.Plates.Plate.length > 0) {
node.lastPicName = oPlates.Plates.Plate[oPlates.Plates.Plate.length - 1].picName;
//node.lastPicName = "202001010101010000"; // BANANA SIMULAZIONE CON LETTURA DI TUTTE LE TARGHE
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Found " + oPlates.Plates.Plate.length + " ignored plates. Last was " + node.lastPicName });
} else {
// No previously plates found, set a default datetime
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "No previously plates found." });
node.lastPicName = "202001010101010000";
}
}
}
} catch (error) {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Ahi lasso Error: " + error });
node.lastPicName = ""; // This raises an error, below.
}
};
// Must be sure, that no error occurs, before acknolwedging the plate last picName
try {
if (oPlates !== null) {
if (oPlates.Plates !== null && oPlates.Plates.hasOwnProperty("Plate")) {
if (oPlates.Plates.Plate.length > 0) {
node.lastPicName = oPlates.Plates.Plate[oPlates.Plates.Plate.length - 1].picName;
//node.lastPicName = "202001010101010000"; // BANANA
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Found " + oPlates.Plates.Plate.length + " ignored plates. Last was " + node.lastPicName });
} else {
// No previously plates found, set a default datetime
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "No previously plates found." });
node.lastPicName = "202001010101010000";
if (node.lastPicName === "") {
// Some errors occurred
// Abort request
try {
if (controller !== null) controller.abort();
} catch (error) { }
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Error occurred in init plates list." });
node.isConnected = false;
setTimeout(node.initPlateReader, 5000); // Reconnect
} else {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for vehicle..." });
if (!node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: true });
})
}
node.isConnected = true;
setTimeout(node.queryForPlates, 2000); // Start main polling thread
}
}
} catch (error) {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Ahi lasso: " + error });
}
if (node.lastPicName === "") {
// Some errors occurred
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Error occurred in init plates list." });
node.isConnected = false;
setTimeout(node.initPlateReader, 10000); // Reconnect
} else {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for vehicle..." });
if (!node.isConnected) {
});
})
.catch(err => {
// Abort request
try {
if (controller !== null) controller.abort();
} catch (error) { }
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable: " + err + " Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: true });
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
})
}
node.isConnected = true;
setTimeout(node.queryForPlates, 2000); // Start main polling thread
}
node.isConnected = false;
setTimeout(node.initPlateReader, 5000); // Reconnect
return;
});
});
};

@@ -138,37 +169,38 @@

}
var controller = new AbortController(); // For aborting the stream request
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client.
var options = {
"digestAuth": node.credentials.user + ":" + node.credentials.password,
"streaming": false,
"timeout": 5000,
"method": "POST",
'followRedirect': true,
'followAllRedirects': true,
"data": "<AfterTime><picTime>" + node.lastPicName + "</picTime></AfterTime>",
"headers": {
'content-type': 'application/xml',
}
};
// These properties are part of the Fetch Standard
method: 'POST',
headers: { 'content-type': 'application/xml' }, // request headers. format is the identical to that accepted by the Headers constructor (see below)
body: "<AfterTime><picTime>" + node.lastPicName + "</picTime></AfterTime>", // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options, function (err, data, res) {
// The following properties are node-fetch extensions
follow: 20, // maximum redirect count. 0 to not follow redirect
timeout: 5000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: true, // support gzip/deflate content encoding. false to disable
size: 0, // maximum response body size in bytes. 0 to disable
agent: null // http(s).Agent instance or function that returns an instance (see below)
}
if (err) {
//console.log("MAIN ERROR: " + JSON.stringify(err));
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
})
client.fetch("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options)
.then(response => {
//RED.log.error(response.status + " " + response.statusText);
if (response.statusText === "Unauthorized") {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText });
}
node.isConnected = false;
setTimeout(node.initPlateReader, 10000); // Restart whole process.
return;
}
if (data) {
if (response.statusText === "Ok") {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." });
}
return response.body;
}).then(body => {
let chunk;
var oRet = "";
while (null !== (chunk = body.read())) {
sRet = chunk.toString();
}
node.isConnected = true;
var oRet = null;
try {
var sRet = data.toString();
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"

@@ -190,3 +222,3 @@ if (i > -1) {

} catch (error) { console.log("ERRORE CATCHATO " + error); }
} catch (error) { RED.log.error("ANPR-config: ERRORE CATCHATO vehicleDetect/plates:" + error); }

@@ -203,7 +235,16 @@ // Send the message to the child nodes

}
};
setTimeout(node.queryForPlates, 1000); // Call the cunction again.
});
setTimeout(node.queryForPlates, 1000); // Call the cunction again.
})
.catch(err => {
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
})
}
node.isConnected = false;
setTimeout(node.initPlateReader, 10000); // Restart whole process.
return;
});
};

@@ -210,0 +251,0 @@

@@ -5,7 +5,6 @@

const urllib = require('urllib');
var Agent = require('agentkeepalive');
const xml2js = require('xml2js').parseString;
const DigestFetch = require('digest-fetch')
const AbortController = require('abort-controller');
const xml2js = require('xml2js').Parser({explicitArray: false}).parseString;
function Hikvisionconfig(config) {

@@ -18,81 +17,116 @@ RED.nodes.createNode(this, config)

node.isConnected = false;
node.timerCheckHeartBeat = null;
var controller = null; // AbortController
node.setAllClientsStatus = ({ fill, shape, text }) => {
function nextStatus(oClient) {
oClient.setNodeStatus({ fill: fill, shape: shape, text: text })
}
node.nodeClients.map(nextStatus);
}
// This function starts the heartbeat timer, to detect the disconnection from the server
node.startHeartBeatTimer = () => {
// Reset node.timerCheckHeartBeat
if (node.timerCheckHeartBeat !== null) clearTimeout(node.timerCheckHeartBeat);
node.timerCheckHeartBeat = setTimeout(() => {
try {
if (controller !== null) controller.abort();
} catch (error) { }
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
});
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Lost connection...Retry..." });
}
node.isConnected = false;
setTimeout(node.startAlarmStream, 5000); // Reconnect
}, 25000);
}
node.startAlarmStream = () => {
var controller = new AbortController(); // For aborting the stream request
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client.
var options = {
// These properties are part of the Fetch Standard
method: 'GET',
headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below)
body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests
// The following properties are node-fetch extensions
follow: 20, // maximum redirect count. 0 to not follow redirect
timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: true, // support gzip/deflate content encoding. false to disable
size: 0, // maximum response body size in bytes. 0 to disable
agent: null // http(s).Agent instance or function that returns an instance (see below)
}
node.startHeartBeatTimer(); // First thing, start the heartbeat timer.
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Connecting..." });
var keepaliveAgent = new Agent({
keepAlive: true
});
var options = {
"digestAuth": node.credentials.user + ":" + node.credentials.password,
"streaming": true,
"agent": keepaliveAgent
};
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Event/notification/alertStream", options, function (err, data, res) {
if (err) {
//console.log("MAIN ERROR: " + err);
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
})
client.fetch("http://" + node.host + "/ISAPI/Event/notification/alertStream", options)
.then(response => {
//console.log(response.status + " " + response.statusText);
if (response.statusText === "Unauthorized") {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText });
}
node.isConnected = false;
setTimeout(node.startAlarmStream, 10000); // Reconnect
return;
}
res.on('data', function (chunk) {
node.isConnected = true;
try {
var sRet = chunk.toString();
sRet = sRet.replace(/--boundary/g, '');
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"
if (i > -1) {
sRet = sRet.substring(i);
// By xml2js
xml2js(sRet, function (err, result) {
node.nodeClients.forEach(oClient => {
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result, connected: true });
})
});
} else {
i = sRet.indexOf("{") // It's a Json
if (response.statusText === "Ok") {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected. Waiting for Alarm." });
}
return response.body;
})
.then(body => {
body.on('readable', () => {
node.isConnected = true;
try {
let chunk;
var sRet = ""
while (null !== (chunk = body.read())) {
sRet = chunk.toString();
}
sRet = sRet.replace(/--boundary/g, '');
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"
if (i > -1) {
sRet = sRet.substring(i);
//sRet = sRet.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); // Fix numbers and chars invalid in JSON
//console.log("BANANA : " + sRet);
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: JSON.parse(sRet), connected: true });
})
// By xml2js
xml2js(sRet, function (err, result) {
node.nodeClients.forEach(oClient => {
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result, connected: true });
})
});
} else {
i = sRet.indexOf("{") // It's a Json
if (i > -1) {
sRet = sRet.substring(i);
//sRet = sRet.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); // Fix numbers and chars invalid in JSON
//console.log("BANANA : " + sRet);
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: JSON.parse(sRet), connected: true });
})
}
}
}
} catch (error) { console.log("ERRORE CATCHATO " + error); }
} catch (error) { RED.log.error("Hikvision-config: ERRORE CATCHATO " + error); }
// All is fine. Reset and restart the hearbeat timer
// Hikvision sends an heartbeat alarm (videoloss), depending on firmware, every 300ms or more.
// If this HeartBeat isn't received, abort the stream request and restart.
node.startHeartBeatTimer();
});
});
res.on('end', function () {
//console.log("Hikvision-Config: END " + res.statusMessage);
if (res.statusMessage === "Unauthorized") {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: res.statusMessage });
}
});
res.on('close', function () {
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Disconnected. Retry..." });
})
.catch(err => {
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable: " + err + " Retry..." });
if (node.isConnected) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false });
});
})
}
node.isConnected = false;
setTimeout(node.startAlarmStream, 10000); // Reconnect
return;
});
res.on('error', function (err) {
//("ERROR: " + err);
});
});
};

@@ -102,14 +136,12 @@

//#region "FUNCTIONS"
node.on('close', function (removed, done) {
try {
controller.abort();
} catch (error) { }
if (node.timerCheckHeartBeat !== null) clearTimeout(node.timerCheckHeartBeat);
done();
});
node.setAllClientsStatus = ({ fill, shape, text }) => {
function nextStatus(oClient) {
oClient.setNodeStatus({ fill: fill, shape: shape, text: text })
}
node.nodeClients.map(nextStatus);
}

@@ -116,0 +148,0 @@ node.addClient = (_Node) => {

@@ -17,2 +17,10 @@

if (_msg.payload === null) { node.send(_msg); return; }; // If null, then it's disconnected. Avoid processing the event
if (_msg.payload.hasOwnProperty("EventNotificationAlert")
&& _msg.payload.EventNotificationAlert.eventType.toString().toLowerCase() === "videoloss"
&& _msg.payload.EventNotificationAlert.eventState.toString().toLowerCase() === "inactive") {
// It's a HertBeat, exit.
node.setNodeStatus({ fill: "green", shape: "ring", text: "Waiting for alert..." });
return ;
};
node.setNodeStatus({ fill: "green", shape: "dot", text: "Alert received" });

@@ -19,0 +27,0 @@ node.send(_msg);

{
"name": "node-red-contrib-hikvision-ultimate",
"version": "0.0.8",
"version": "0.0.9",
"description": "A native set of node for Hikvision Cameras, Alarms, Radars etc.",
"author": "Supergiovane (https://github.com/Supergiovane)",
"dependencies": {
"urllib": "2.36.1",
"xml2js": "0.4.23",
"agentkeepalive": "4.1.3"
"node-fetch": "2.6.1",
"digest-fetch": "1.1.5",
"abort-controller": ">=3.0.0"
},

@@ -15,3 +16,6 @@ "keywords": [

"alarm",
"camera"
"camera",
"radar",
"ANPR",
"license plate"
],

@@ -18,0 +22,0 @@ "license": "MIT",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc