node-red-contrib-hik-media-buffer 1.1.113 → 1.1.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hik-media-buffer.js +6 -10
- package/hik-snapshot.js +286 -72
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -68,16 +68,14 @@ module.exports = function(RED) {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
const xml = res.data.toString();
|
|
71
|
-
// Regex per estrarre ogni blocco VideoInputChannel
|
|
72
71
|
const channelBlocks = xml.match(/<VideoInputChannel>[\s\S]*?<\/VideoInputChannel>/g) || [];
|
|
73
72
|
|
|
74
73
|
for (let block of channelBlocks) {
|
|
75
74
|
const idMatch = block.match(/<id>(\d+)<\/id>/i);
|
|
76
|
-
const statusMatch = block.match(/<resVerType>([^<]+)<\/resVerType>/i);
|
|
75
|
+
const statusMatch = block.match(/<resVerType>([^<]+)<\/resVerType>/i);
|
|
77
76
|
|
|
78
77
|
if (idMatch) {
|
|
79
78
|
const ch = idMatch[1];
|
|
80
|
-
// Se non trova il tag esplicito o dice "online", assumiamo sia attiva
|
|
81
79
|
const isOnline = statusMatch ? statusMatch[1].toLowerCase() === "online" : true;
|
|
82
80
|
|
|
83
81
|
if (isOnline && statoCamera[ch] === false) {
|
|
@@ -92,9 +90,7 @@ module.exports = function(RED) {
|
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
|
-
} catch (e) {
|
|
96
|
-
// Se l'NVR fallisce la chiamata generale, lo gestisce l'alert stream o lasciamo lo stato immutato
|
|
97
|
-
}
|
|
93
|
+
} catch (e) {}
|
|
98
94
|
updateNodeStatus();
|
|
99
95
|
}
|
|
100
96
|
|
|
@@ -128,7 +124,7 @@ module.exports = function(RED) {
|
|
|
128
124
|
const nomeCamera = await getCameraNameFromNVR(channelID);
|
|
129
125
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
130
126
|
|
|
131
|
-
// Finestra temporale
|
|
127
|
+
// Finestra temporale per la ricerca foto JSON
|
|
132
128
|
const startSearchStr = referenceTime.toISOString().split('T')[0] + "T00:00:00 01:00";
|
|
133
129
|
const endSearchStr = referenceTime.toISOString().split('T')[0] + "T23:59:59 01:00";
|
|
134
130
|
|
|
@@ -173,8 +169,9 @@ module.exports = function(RED) {
|
|
|
173
169
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
174
170
|
});
|
|
175
171
|
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
// CORRETTO: Adesso cerca dentro Targets e prende il pictureUrl grezzo come su Postman
|
|
173
|
+
if (resFotoSearch.data?.EventSearchResult?.Targets?.[0]?.pictureUrl) {
|
|
174
|
+
const urlFotoGrezzo = resFotoSearch.data.EventSearchResult.Targets[0].pictureUrl;
|
|
178
175
|
|
|
179
176
|
const resDownFoto = await nvrAuth.request({
|
|
180
177
|
method: 'GET',
|
|
@@ -190,7 +187,6 @@ module.exports = function(RED) {
|
|
|
190
187
|
}
|
|
191
188
|
|
|
192
189
|
// 2. SCARICAMENTO VIDEO (POST XML a search -> GET XML a download)
|
|
193
|
-
// Usiamo una finestra temporale estesa in formato Z come richiesto dall'NVR
|
|
194
190
|
const startVideoSearch = referenceTime.toISOString().split('T')[0] + "T00:00:00Z";
|
|
195
191
|
const endVideoSearch = referenceTime.toISOString().split('T')[0] + "T23:59:59Z";
|
|
196
192
|
|
package/hik-snapshot.js
CHANGED
|
@@ -1,106 +1,320 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
+
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
2
3
|
const https = require('https');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { exec } = require('child_process');
|
|
5
8
|
|
|
6
9
|
module.exports = function(RED) {
|
|
7
|
-
function
|
|
10
|
+
function HikMediaBufferNode(config) {
|
|
8
11
|
RED.nodes.createNode(this, config);
|
|
9
12
|
const node = this;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
node.nodeName = config.name || `NVR_${config.host}`;
|
|
13
|
-
|
|
14
|
-
node.protocol = config.protocol || "http";
|
|
13
|
+
|
|
14
|
+
node.name = config.name || "TEST";
|
|
15
15
|
node.host = config.host;
|
|
16
16
|
node.port = config.port || "80";
|
|
17
|
+
node.protocol = config.protocol || "http";
|
|
17
18
|
node.user = config.user;
|
|
18
19
|
node.pass = config.pass;
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
let streamRequest = null;
|
|
22
|
+
let isClosing = false;
|
|
23
|
+
let lastTriggerTime = {};
|
|
24
|
+
let nvrOnline = true;
|
|
25
|
+
let statoCamera = {};
|
|
20
26
|
|
|
21
27
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
28
|
+
const EventList = ["FieldDetection", "LineDetection"];
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// CARTELLA TEMPORANEA
|
|
31
|
+
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
32
|
+
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
37
|
+
|
|
38
|
+
// --- PRENDE IL NOME REALE DELLA TELECAMERA DALL'NVR ---
|
|
39
|
+
async function getCameraNameFromNVR(channelID) {
|
|
40
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
41
|
+
try {
|
|
42
|
+
const res = await nvrAuth.request({
|
|
43
|
+
method: 'GET',
|
|
44
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}`,
|
|
45
|
+
timeout: 5000,
|
|
46
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
47
|
+
});
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
const data = res.data.toString();
|
|
50
|
+
// Cerchiamo il tag channelName che contiene il nome impostato sull'NVR
|
|
51
|
+
const match = data.match(/<channelName>([^<]+)<\/channelName>/i);
|
|
52
|
+
if (match && match[1]) return match[1].trim();
|
|
53
|
+
return `Canale ${channelID}`;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return `Canale ${channelID}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
// --- CONTROLLO STATUS CAMERE (ONLINE/OFFLINE) INTERROGANDO L'NVR ---
|
|
60
|
+
async function checkCameras() {
|
|
61
|
+
if (isClosing || !nvrOnline) return;
|
|
62
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
63
|
+
try {
|
|
64
|
+
const res = await nvrAuth.request({
|
|
65
|
+
method: 'GET',
|
|
66
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels`,
|
|
67
|
+
timeout: 8000,
|
|
68
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
69
|
+
});
|
|
44
70
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
const xml = res.data.toString();
|
|
72
|
+
const channelBlocks = xml.match(/<VideoInputChannel>[\s\S]*?<\/VideoInputChannel>/g) || [];
|
|
73
|
+
|
|
74
|
+
for (let block of channelBlocks) {
|
|
75
|
+
const idMatch = block.match(/<id>(\d+)<\/id>/i);
|
|
76
|
+
// Hikvision usa il tag <online> true/false per lo stato della telecamera IP accoppiata
|
|
77
|
+
const onlineMatch = block.match(/<online>([^<]+)<\/online>/i) || block.match(/<onLine>([^<]+)<\/onLine>/i);
|
|
78
|
+
|
|
79
|
+
if (idMatch) {
|
|
80
|
+
const ch = idMatch[1];
|
|
81
|
+
// Se il tag dice true è online, altrimenti se dice false (o manca) è offline
|
|
82
|
+
const isOnline = onlineMatch ? onlineMatch[1].trim().toLowerCase() === "true" : true;
|
|
83
|
+
|
|
84
|
+
if (isOnline && statoCamera[ch] === false) {
|
|
85
|
+
const nomeOnline = await getCameraNameFromNVR(ch);
|
|
86
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: node.host, channel: ch, msg: "Camera ripristinata" } });
|
|
87
|
+
statoCamera[ch] = true;
|
|
88
|
+
} else if (!isOnline && statoCamera[ch] !== false) {
|
|
89
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera ${ch}`, ip_telecamera: node.host, channel: ch, msg: "Camera non raggiungibile" } });
|
|
90
|
+
statoCamera[ch] = false;
|
|
91
|
+
} else if (statoCamera[ch] === undefined) {
|
|
92
|
+
statoCamera[ch] = isOnline;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
node.error(`Errore durante il check delle camere: ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
updateNodeStatus();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function updateNodeStatus() {
|
|
103
|
+
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
104
|
+
if (!nvrOnline) {
|
|
105
|
+
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
106
|
+
} else if (offlineCams > 0) {
|
|
107
|
+
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
108
|
+
} else {
|
|
109
|
+
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
114
|
+
|
|
115
|
+
// --- DOWNLOAD CON URL E BODY SECONDO SPECIFICHE ---
|
|
116
|
+
async function downloadMedia(evento, channelID) {
|
|
117
|
+
const nowTime = Date.now();
|
|
118
|
+
if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
|
|
119
|
+
if (nowTime - lastTriggerTime[channelID] < 5000) return;
|
|
120
|
+
lastTriggerTime[channelID] = nowTime;
|
|
121
|
+
|
|
122
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
123
|
+
const referenceTime = new Date();
|
|
124
|
+
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
125
|
+
|
|
126
|
+
const videoTrackID = (parseInt(channelID) * 100) + 1;
|
|
127
|
+
|
|
128
|
+
// Recupera il nome reale prima di inviare l'allarme
|
|
129
|
+
const nomeCamera = await getCameraNameFromNVR(channelID);
|
|
130
|
+
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
131
|
+
|
|
132
|
+
const startSearchStr = referenceTime.toISOString().split('T')[0] + "T00:00:00 01:00";
|
|
133
|
+
const endSearchStr = referenceTime.toISOString().split('T')[0] + "T23:59:59 01:00";
|
|
134
|
+
|
|
135
|
+
let output = {
|
|
136
|
+
tipo_messaggio: "evento",
|
|
137
|
+
nome_cliente: node.name,
|
|
138
|
+
nome_telecamera: nomeCamera,
|
|
139
|
+
ip_telecamera: node.host,
|
|
140
|
+
tipo_evento: evento,
|
|
141
|
+
timestamp_epoch: timestamp,
|
|
142
|
+
stato_telecamera: "ONLINE",
|
|
143
|
+
channel: channelID.toString(),
|
|
144
|
+
foto_base64: null,
|
|
145
|
+
video_base64: null
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let fileDaCancellare = [];
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// 1. SCARICAMENTO IMMAGINE
|
|
152
|
+
const payloadFoto = {
|
|
153
|
+
"EventSearchDescription": {
|
|
154
|
+
"searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
|
|
155
|
+
"searchResultPosition": 0,
|
|
156
|
+
"maxResults": 30,
|
|
157
|
+
"timeSpanList": [{
|
|
158
|
+
"startTime": startSearchStr,
|
|
159
|
+
"endTime": endSearchStr
|
|
160
|
+
}],
|
|
161
|
+
"type": "all",
|
|
162
|
+
"channels": [parseInt(channelID)],
|
|
163
|
+
"eventType": "behavior",
|
|
164
|
+
"behavior": { "behaviorEventType": "linedetection" }
|
|
165
|
+
}
|
|
53
166
|
};
|
|
54
167
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
168
|
+
const resFotoSearch = await nvrAuth.request({
|
|
169
|
+
method: 'POST',
|
|
170
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/eventRecordSearch?format=json`,
|
|
171
|
+
data: payloadFoto,
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (resFotoSearch.data?.EventSearchResult?.Targets?.[0]?.pictureUrl) {
|
|
177
|
+
const urlFotoGrezzo = resFotoSearch.data.EventSearchResult.Targets[0].pictureUrl;
|
|
178
|
+
|
|
179
|
+
const resDownFoto = await nvrAuth.request({
|
|
58
180
|
method: 'GET',
|
|
59
|
-
url:
|
|
181
|
+
url: urlFotoGrezzo,
|
|
60
182
|
responseType: 'arraybuffer',
|
|
61
|
-
httpsAgent: node.protocol ===
|
|
62
|
-
timeout: 5000
|
|
183
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
63
184
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
185
|
+
|
|
186
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}_ch${channelID}.jpg`);
|
|
187
|
+
fs.writeFileSync(fullImgPath, Buffer.from(resDownFoto.data));
|
|
188
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
189
|
+
fileDaCancellare.push(fullImgPath);
|
|
68
190
|
}
|
|
69
191
|
|
|
70
|
-
// 2.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
192
|
+
// 2. SCARICAMENTO VIDEO
|
|
193
|
+
const startVideoSearch = referenceTime.toISOString().split('T')[0] + "T00:00:00Z";
|
|
194
|
+
const endVideoSearch = referenceTime.toISOString().split('T')[0] + "T23:59:59Z";
|
|
195
|
+
|
|
196
|
+
const payloadVideoSearch = `<?xml version="1.0" encoding="utf-8"?>
|
|
197
|
+
<CMSearchDescription>
|
|
198
|
+
<searchID>CEC13F0F-1AEA-4474-A2DA-CFFF332C7C5B</searchID>
|
|
199
|
+
<trackList><trackID>${videoTrackID}</trackID></trackList>
|
|
200
|
+
<timeSpanList>
|
|
201
|
+
<timeSpan>
|
|
202
|
+
<startTime>${startVideoSearch}</startTime>
|
|
203
|
+
<endTime>${endVideoSearch}</endTime>
|
|
204
|
+
</timeSpan>
|
|
205
|
+
</timeSpanList>
|
|
206
|
+
<maxResults>100</maxResults>
|
|
207
|
+
<searchResultPostion>0</searchResultPostion>
|
|
208
|
+
<metadataList><metadataDescriptor>//recordType.meta.std-cgi.com</metadataDescriptor></metadataList>
|
|
209
|
+
</CMSearchDescription>`;
|
|
210
|
+
|
|
211
|
+
const resVideoSearch = await nvrAuth.request({
|
|
212
|
+
method: 'POST',
|
|
213
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`,
|
|
214
|
+
data: payloadVideoSearch,
|
|
215
|
+
headers: { "Content-Type": "application/xml" },
|
|
216
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const xmlString = resVideoSearch.data.toString();
|
|
220
|
+
const uriMatch = xmlString.match(/<playbackURI>([\s\S]*?)<\/playbackURI>/i);
|
|
221
|
+
|
|
222
|
+
if (uriMatch && uriMatch[1]) {
|
|
223
|
+
const playbackURIGrezzo = uriMatch[1].trim();
|
|
224
|
+
|
|
225
|
+
const payloadDownload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
226
|
+
<downloadRequest>
|
|
227
|
+
<playbackURI>${playbackURIGrezzo}</playbackURI>
|
|
228
|
+
</downloadRequest>`;
|
|
229
|
+
|
|
230
|
+
const resDownVideo = await nvrAuth.request({
|
|
231
|
+
method: 'GET',
|
|
232
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/download`,
|
|
233
|
+
data: payloadDownload,
|
|
234
|
+
headers: { "Content-Type": "application/xml" },
|
|
235
|
+
responseType: 'arraybuffer',
|
|
236
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
let bufferVideo = Buffer.from(resDownVideo.data);
|
|
240
|
+
if (bufferVideo.slice(0, 4).toString() === 'IMKH') bufferVideo = bufferVideo.slice(40);
|
|
241
|
+
|
|
242
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}_ch${channelID}.mp4`);
|
|
243
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
244
|
+
fs.writeFileSync(rawPath, bufferVideo);
|
|
245
|
+
|
|
246
|
+
await new Promise((resolve) => {
|
|
247
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
248
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
249
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
250
|
+
fileDaCancellare.push(fixedPath);
|
|
251
|
+
} else {
|
|
252
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
253
|
+
}
|
|
254
|
+
fileDaCancellare.push(rawPath);
|
|
255
|
+
resolve();
|
|
256
|
+
});
|
|
79
257
|
});
|
|
80
|
-
const xmlOutput = responseRec.data.toString();
|
|
81
|
-
const regex = new RegExp(`<id>${day}</id>[^]*?<record>true</record>`);
|
|
82
|
-
resCanale.isRecording = regex.test(xmlOutput);
|
|
83
|
-
} catch (err) {
|
|
84
|
-
resCanale.recError = err.message;
|
|
85
258
|
}
|
|
86
259
|
|
|
87
|
-
|
|
88
|
-
|
|
260
|
+
if (output.foto_base64 || output.video_base64) {
|
|
261
|
+
node.send({ payload: output });
|
|
262
|
+
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
for (let file of fileDaCancellare) {
|
|
265
|
+
try {
|
|
266
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
267
|
+
} catch (err) {}
|
|
268
|
+
}
|
|
269
|
+
}, 120000);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} catch (e) {
|
|
273
|
+
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
89
274
|
}
|
|
275
|
+
updateNodeStatus();
|
|
276
|
+
}
|
|
90
277
|
|
|
91
|
-
|
|
92
|
-
|
|
278
|
+
// --- ALERT STREAM ---
|
|
279
|
+
function startAlertStream() {
|
|
280
|
+
if (isClosing) return;
|
|
281
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
282
|
+
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
283
|
+
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
284
|
+
.then(response => {
|
|
285
|
+
streamRequest = response;
|
|
286
|
+
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
287
|
+
updateNodeStatus();
|
|
288
|
+
response.data.on('data', (chunk) => {
|
|
289
|
+
const data = chunk.toString().toLowerCase();
|
|
290
|
+
if (data.includes("active")) {
|
|
291
|
+
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
292
|
+
if (chMatch) {
|
|
293
|
+
let ev = "Unknown";
|
|
294
|
+
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
295
|
+
downloadMedia(ev, chMatch[1]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
response.data.on('error', () => handleNvrError());
|
|
300
|
+
response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
301
|
+
}).catch(() => handleNvrError());
|
|
302
|
+
}
|
|
93
303
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
304
|
+
function handleNvrError() {
|
|
305
|
+
if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", ip: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
|
|
306
|
+
updateNodeStatus();
|
|
307
|
+
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
startAlertStream();
|
|
311
|
+
setTimeout(checkCameras, 2000);
|
|
312
|
+
node.on('close', (done) => {
|
|
313
|
+
isClosing = true;
|
|
314
|
+
clearInterval(heartbeatInterval);
|
|
315
|
+
if (streamRequest) streamRequest.data.destroy();
|
|
316
|
+
done();
|
|
103
317
|
});
|
|
104
318
|
}
|
|
105
|
-
RED.nodes.registerType("hik-
|
|
319
|
+
RED.nodes.registerType("hik-media-buffer", HikMediaBufferNode);
|
|
106
320
|
};
|