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
0
Versions
119
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 1.1.22 to 1.2.0-beta.1

nodes/Hikvision-config copy.js

6

CHANGELOG.md

@@ -6,4 +6,8 @@ <p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-hikvision-ultimate/master/img/logo.png' width="40%"></p>

<p>
<b>Version 1.2.0-beta.1</b> September 2024<br/>
- RAW Event Node (BETA): on the third pin, outputs the IMAGE related to the event (if any). The node will now filter the heartbeat signals.<br/>
- Event Node (BETA): on the third pin, outputs the IMAGE related to the event (if any).<br/>
</p>
<p>
<b>Version 1.1.22</b> August 2024<br/>

@@ -10,0 +14,0 @@ - IP Speaker node: added help.<br/>

228

nodes/Hikvision-config.js

@@ -10,3 +10,5 @@

const https = require('https');
const Dicer = require('dicer');
function Hikvisionconfig(config) {

@@ -71,3 +73,3 @@ RED.nodes.createNode(this, config)

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.
timeout: 15000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: false, // support gzip/deflate content encoding. false to disable

@@ -174,2 +176,7 @@ size: 0, // maximum response body size in bytes. 0 to disable

//#region ALARMSTREAM
// Funzione per estrarre il boundary dal Content-Type
function extractBoundary(contentType) {
const match = contentType.match(/boundary=(.*)$/);
return match ? match[1] : null;
}
var clientAlarmStream;

@@ -195,3 +202,3 @@ async function startAlarmStream() {

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.
timeout: 15000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: false, // support gzip/deflate content encoding. false to disable

@@ -205,13 +212,13 @@ size: 0, // maximum response body size in bytes. 0 to disable

const response = await clientAlarmStream.fetch(node.protocol + "://" + node.host + "/ISAPI/Event/notification/alertStream", optionsAlarmStream);
const res = await clientAlarmStream.fetch(node.protocol + "://" + node.host + "/ISAPI/Event/notification/alertStream", optionsAlarmStream);
if (response.status >= 200 && response.status <= 300) {
if (res.status >= 200 && res.status <= 300) {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for event." });
} else {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText || " unknown response code" });
node.setAllClientsStatus({ fill: "red", shape: "ring", text: res.statusText || " unknown response code" });
// if (node.debug) RED.log.error("BANANA Error response " + response.statusText);
node.errorDescription = "StatusResponse problem " + (response.statusText || " unknown status response code");
throw new Error("StatusResponse " + (response.statusText || " unknown response code"));
node.errorDescription = "StatusResponse problem " + (res.statusText || " unknown status response code");
throw new Error("StatusResponse " + (res.statusText || " unknown response code"));
}
if (response.ok) {
if (res.ok) {
if (!node.isConnected) {

@@ -225,42 +232,81 @@ node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." });

node.isConnected = true;
node.resetHeartBeatTimer();
try {
if (node.debug) RED.log.info("Hikvision-config: before Pipelining...");
if (oReadable !== null) oReadable.removeAllListeners() // 09/01/2023
oReadable = readableStr.from(response.body, { encoding: 'utf8' });
let result = "";
oReadable.on('data', async (chunk) => {
result += chunk;
if (result.indexOf("--boundary") > -1) {
const contentType = res.headers.get('content-type');
if (!contentType) {
if (node.debug) RED.log.error("Hikvision-config: No Content-Type in response");
// 11/01/2022 let's do some other checks on the event stream text
let bMessageCanBeHandled = false;
if (result.includes("</EventNotificationAlert>")) {
// Is the XML
bMessageCanBeHandled = true;
} else if (result.includes("}")) {
// Should be the JSON
bMessageCanBeHandled = true;
}
}
if (bMessageCanBeHandled) {
try {
await handleChunk(result);
} catch (error) {
if (node.debug) RED.log.error("Hikvision-config: Error handleChunk " + (error.message || "") + node.protocol + "://" + node.host);
}
result = "";
}
if (contentType.includes('multipart')) {
const boundary = extractBoundary(contentType);
if (!boundary) {
if (node.debug) RED.log.error("Hikvision-config: Failed to extract boundary from multipart stream");
}
});
oReadable.on('end', function () {
// For some reason, some NVRs do end the stream. I must restart it.
console.log(result)
if (node.debug) RED.log.info("Hikvision-config: streamPipeline: STREAMING HAS ENDED.");
startAlarmStream();
});
oReadable.on('error', function (error) {
RED.log.error("Hikvision-config: streamPipeline: " + (error.message || " unknown error") + node.protocol + "://" + node.host);
});
//console.log(`Receiving multipart stream with boundary: ${boundary}`);
// Inizializza Dicer per il parsing del multipart
const dicer = new Dicer({ boundary });
dicer.on('part', (part) => {
let partData = [];
let extension = 'bin'; // Default estensione per parti non riconosciute
part.on('header', (header) => {
//console.log('Part headers:', header);
node.resetHeartBeatTimer();
// Verifica il tipo di parte
if (header['content-type'] && (header['content-type'][0].includes('image/jpeg') || header['content-type'][0].includes('image/jpg'))) {
extension = 'jpg'; // Estensione corretta per immagini JPEG
} else if (header['content-type'] && header['content-type'][0].includes('image/png')) {
extension = 'png'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/xml')) {
extension = 'xml'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/json')) {
extension = 'json'; // Estensione corretta per immagini PNG
}
});
part.on('data', (data) => {
node.resetHeartBeatTimer();
partData.push(data); // Aggiungi i chunk di dati alla parte
});
part.on('end', () => {
node.resetHeartBeatTimer();
const fullData = Buffer.concat(partData); // Unisci i chunk di dati
switch (extension) {
case 'xml':
handleXML(fullData);
break;
case 'json':
handleJSON(fullData);
break;
case 'jpg' || 'png':
//const filename = generateFilename(extension);
//saveFile(fullData, filename); // Salva l'immagine su disco
handleIMG(fullData, extension);
break;
default:
break;
}
});
});
dicer.on('finish', () => {
console.log('Finished parsing multipart stream.');
});
dicer.on('error', (err) => {
console.error('Error in Dicer:', err);
});
// Pipa lo stream multipart in Dicer
res.body.pipe(dicer);
} else {
//throw new Error('Unsupported Content-Type');
}
} catch (error) {

@@ -282,74 +328,38 @@ if (node.debug) RED.log.error("Hikvision-config: streamPipeline: Please be sure to have the latest Node.JS version installed: " + (error.message || " unknown error"));

//#region "HANDLE STREAM MESSAGE"
// Handle the complete stream message, enclosed into the --boundary stream string
// If there is more boundary, process each one separately
// Handle the complete stream message
// ###################################
async function handleChunk(result) {
async function handleIMG(result, extension) {
try {
// 05/12/2020 process the data
var aResults = result.split("--boundary");
if (node.debug) RED.log.info("SPLITTATO RESULT COUNT: ####### " + aResults.length + " ###################### FINE SPLITTATO RESULT");
aResults.forEach(async sRet => {
if (sRet.trim() !== "") {
if (node.debug) RED.log.error("BANANA PROCESSING" + sRet);
try {
//sRet = sRet.replace(/--boundary/g, '');
var i = sRet.includes("Content-Type: application/xml");
if (i > -1) {
sRet = sRet.substring(i);
// 11/01/2023 new parser XML to Json
try {
const parser = new XMLParser();
let result = parser.parse(sRet);
if (node.debug) RED.log.error("BANANA SBANANATO XML -> JSON " + JSON.stringify(result));
if (result !== null && result !== undefined && result.hasOwnProperty("EventNotificationAlert")) {
node.nodeClients.forEach(oClient => {
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result.EventNotificationAlert });
});
}
} catch (error) {
sRet = "";
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
} else if (sRet.includes("Content-Type: application/json")) {
i = sRet.indexOf("{") // It's a Json
if (node.debug) RED.log.error("BANANA SBANANATO JSON " + sRet);
sRet = sRet.substring(i);
try {
sRet = JSON.parse(sRet);
// if (node.debug) RED.log.error("BANANA JSONATO: " + sRet);
if (sRet !== null && sRet !== undefined) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: sRet });
})
}
} catch (error) {
sRet = "";
}
} else {
// Invalid body
if (node.debug) RED.log.info("Hikvision-config: DecodingBody Info only: Invalid Json " + sRet);
}
// 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.resetHeartBeatTimer();
} catch (error) {
// if (node.debug) RED.log.error("BANANA startAlarmStream decodifica body: " + error);
if (node.debug) RED.log.error("Hikvision-config: DecodingBody error: " + (error.message || " unknown error"));
throw (error);
}
} else {
if (node.debug) RED.log.info("SPLITTATO RESULT EMPTY: ####### " + sRet + " ###################### FINE SPLITTATO RESULT");
}
if (node.debug) RED.log.error("BANANA SBANANATO IMG -> JSON " + JSON.stringify(oXML));
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: result, type: 'img', extension: extension });
});
} catch (error) {
if (node.debug) RED.log.info("Hikvision-config: readStream error: " + (error.message || " unknown error"));
node.errorDescription = "readStream error " + (error.message || " unknown error");
throw (error);
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
async function handleXML(result) {
try {
const parser = new XMLParser();
const oXML = parser.parse(result);
if (node.debug) RED.log.error("BANANA SBANANATO XML -> JSON " + JSON.stringify(oXML));
node.nodeClients.forEach(oClient => {
if (oXML !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: oXML.EventNotificationAlert, type: 'event' });
});
} catch (error) {
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
async function handleJSON(result) {
try {
const oJSON = JSON.parse(result);
if (oJSON !== null && oJSON !== undefined) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: oJSON, type: 'event' });
})
}
} catch (error) {
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
// ###################################

@@ -380,3 +390,3 @@ //#endregion

follow: 20, // maximum redirect count. 0 to not follow redirect
timeout: 8000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
timeout: 15000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: false, // support gzip/deflate content encoding. false to disable

@@ -383,0 +393,0 @@ size: 0, // maximum response body size in bytes. 0 to disable

@@ -43,3 +43,3 @@

node.setNodeStatus({ fill: "red", shape: "dot", text: "Zone " + node.currentAlarmMSG.zone + " alarm" });
node.send([node.currentAlarmMSG, null]);
node.send([node.currentAlarmMSG, null, null]);
} else { node.total_alarmfilterduration = 0; }

@@ -68,8 +68,13 @@ }

// Called from config node, to send output to the flow
node.sendPayload = (_msg) => {
node.sendPayload = (_msg, extension = '') => {
if (_msg === null || _msg === undefined) return;
_msg.topic = node.topic;
if (_msg.hasOwnProperty("errorDescription")) { node.send([null, _msg]); return; }; // It's a connection error/restore comunication.
if (_msg.hasOwnProperty("errorDescription")) { node.send([null, _msg, null]); return; }; // It's a connection error/restore comunication.
if (!_msg.hasOwnProperty("payload") || (_msg.hasOwnProperty("payload") && _msg.payload === undefined)) return;
if (_msg.type === 'img') {
_msg.extension = extension;
node.send([null, null, _msg]);
return;
}
var oRetMsg = {}; // Return message

@@ -302,3 +307,3 @@

if (node.alarmfilterduration == 0) {
node.send([oRetMsg, null]);
node.send([oRetMsg, null, null]);
} else {

@@ -308,3 +313,3 @@ // Sends the false only in case the isNodeInAlarm is true.

if (oRetMsg.payload === false && node.isNodeInAlarm) {
node.send([oRetMsg, null]);
node.send([oRetMsg, null, null]);
node.currentAlarmMSG = {};

@@ -311,0 +316,0 @@ node.isNodeInAlarm = false;

@@ -16,8 +16,19 @@

// Called from config node, to send output to the flow
node.sendPayload = (_msg) => {
node.sendPayload = (_msg, extension = '') => {
if (_msg === null || _msg === undefined) return;
_msg.topic = node.topic;
if (_msg.hasOwnProperty("errorDescription")) { node.send([null, _msg]); return; }; // It's a connection error/restore comunication.
if (_msg.hasOwnProperty("errorDescription")) { node.send([null, _msg, null]); return; }; // It's a connection error/restore comunication.
if (!_msg.hasOwnProperty("payload") || (_msg.hasOwnProperty("payload") && _msg.payload === undefined)) return;
if (_msg.type !== undefined && _msg.type === 'img') {
_msg.extension = extension;
node.send([null, null, _msg]);
return;
}
// Heartbeat discard
// <activePostCount>0</activePostCount>
// <eventType>videoloss</eventType>
// <eventState>inactive</eventState>
if (_msg.hasOwnProperty("payload")

@@ -27,3 +38,3 @@ && _msg.payload.hasOwnProperty("eventType")

&& _msg.payload.eventState.toString().toLowerCase() === "inactive"
&& _msg.payload.hasOwnProperty("activePostCount") && Number(_msg.payload.activePostCount) === 0) {
&& _msg.payload.hasOwnProperty("activePostCount") && (Number(_msg.payload.activePostCount) === 0 || Number(_msg.payload.activePostCount) === 1)) {
// It's a HertBeat, exit.

@@ -35,3 +46,3 @@ node.setNodeStatus({ fill: "green", shape: "ring", text: "Waiting for alert..." });

node.setNodeStatus({ fill: "green", shape: "dot", text: "Alert received" });
node.send([_msg, null]);
node.send([_msg, null, null]);
}

@@ -38,0 +49,0 @@

@@ -42,2 +42,10 @@

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;

@@ -44,0 +52,0 @@ _msg.payload = true;

@@ -219,2 +219,7 @@

if (_msg === null || _msg === undefined) return;
if (_msg.type !== undefined && _msg.type === 'img') {
// Coming from an event containing an image
return;
}
// 01/09/2022 Add the previous input message

@@ -221,0 +226,0 @@ _msg.previousInputMessage = node.previousInputMessage;

@@ -18,2 +18,8 @@ module.exports = function (RED) {

if (_msg === null || _msg === undefined) return;
if (_msg.type !== undefined && _msg.type === 'img') {
// The payload is an image, exit.
return;
}
_msg.topic = node.topic;

@@ -55,3 +61,3 @@ if (_msg.hasOwnProperty("errorDescription")) { node.send([null, _msg]); return; }; // It's a connection error/restore comunication.

node.setNodeStatus({ fill: "green", shape: "dot", text: "Preset passed by msg input." });
recallPTZ();
recallPTZ();
}

@@ -58,0 +64,0 @@ }

@@ -20,2 +20,6 @@

node.sendPayload = (_msg) => {
if (_msg.type !== undefined && _msg.type === 'img') {
// The payload is an image, exit.
return;
}
node.setNodeStatus({ fill: "green", shape: "ring", text: "Received response" });

@@ -44,3 +48,3 @@ if (_msg === null || _msg === undefined) return;

node.on("close", function (done) {

@@ -47,0 +51,0 @@ done();

{
"name": "node-red-contrib-hikvision-ultimate",
"version": "1.1.22",
"version": "1.2.0-beta.1",
"description": "A native set of nodes for Hikvision (and compatible) Cameras, Alarms, Radars, NVR, Doorbells, etc.",

@@ -12,3 +12,4 @@ "author": "Supergiovane (https://github.com/Supergiovane)",

"lodash": "4.17.21",
"fast-xml-parser": "4.0.13"
"fast-xml-parser": "4.0.13",
"dicer": "0.3.1"
},

@@ -15,0 +16,0 @@ "keywords": [

@@ -50,2 +50,3 @@

For RADAR device types, you can filter improper/false alams as well.<br/>
On the third pin, the node will output the event's picture (if any). You can save it directly to disk or user where you want.<br/>

@@ -132,2 +133,11 @@ <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-hikvision-ultimate/master/img/GenericAlarm.png' >

```
**Output PIN 3 (Image)**
```javascript
msg = {
"topic": "",
"payload": image,
"extension": "jpg" // Can be "jpg" or "png"
}
```
<br/>

@@ -520,2 +530,3 @@ <br/>

The RAW CAMERA Event node reacts to every message sent by the device. You can use this node when the other nodes doesn't fit your needs. It connects to ***NVR, Camera, Radars etc...*** and outputs the event received. <br/>
On the third pin, the node will output the event's picture (if any). You can save it directly to disk or user where you want.<br/>

@@ -535,2 +546,3 @@ <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-hikvision-ultimate/master/img/RawAlarm.png' >

"topic": "",
"type": "event",
"payload": {

@@ -563,4 +575,3 @@ "ipAddress": "192.168.1.25",

]
},
"_msgid": "dba1850a.2dc5e8"
}
}

@@ -574,6 +585,15 @@ ```

"errorDescription": "", // This will contain the error rescription, in case of errors.
"payload": false, // Or TRUE if error
"_msgid": "dd5b3622.884a78"
"payload": false // Or TRUE if error
}
```
**Output PIN 3 (Image)**
```javascript
msg = {
"topic": "",
"type": "img",
"payload": image,
"extension": "jpg" // Can be "jpg" or "png"
}
```
<br/>

@@ -580,0 +600,0 @@ <br/>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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