node-red-contrib-hik-media-buffer 1.1.115 → 1.1.116

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.
@@ -35,27 +35,28 @@ module.exports = function(RED) {
35
35
 
36
36
  function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
37
37
 
38
- // --- PRENDE IL NOME DELLA TELECAMERA DALL'NVR ---
38
+ // --- PRENDE IL NOME REALE DELLA TELECAMERA DALL'NVR ---
39
39
  async function getCameraNameFromNVR(channelID) {
40
40
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
41
41
  try {
42
42
  const res = await nvrAuth.request({
43
43
  method: 'GET',
44
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}/overlays/channelNameOverlay`,
44
+ url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}`,
45
45
  timeout: 5000,
46
46
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
47
47
  });
48
48
 
49
49
  const data = res.data.toString();
50
- const match = data.match(/<name>([^<]+)<\/name>/i);
50
+ // Cerchiamo il tag channelName che contiene il nome impostato sull'NVR
51
+ const match = data.match(/<channelName>([^<]+)<\/channelName>/i);
51
52
  if (match && match[1]) return match[1].trim();
52
- return `Canale_${channelID}`;
53
+ return `Canale ${channelID}`;
53
54
  } catch (e) {
54
- return `Camera_${channelID}`;
55
+ return `Canale ${channelID}`;
55
56
  }
56
57
  }
57
58
 
58
- // --- CONTROLLO STATUS CAMERE INTERROGANDO DIRETTAMENTE L'NVR ---
59
+ // --- CONTROLLO STATUS CAMERE (ONLINE/OFFLINE) INTERROGANDO L'NVR ---
59
60
  async function checkCameras() {
60
61
  if (isClosing || !nvrOnline) return;
61
62
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
@@ -72,25 +73,29 @@ module.exports = function(RED) {
72
73
 
73
74
  for (let block of channelBlocks) {
74
75
  const idMatch = block.match(/<id>(\d+)<\/id>/i);
75
- const statusMatch = block.match(/<resVerType>([^<]+)<\/resVerType>/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);
76
78
 
77
79
  if (idMatch) {
78
80
  const ch = idMatch[1];
79
- const isOnline = statusMatch ? statusMatch[1].toLowerCase() === "online" : true;
81
+ // Se il tag dice true è online, altrimenti se dice false (o manca) è offline
82
+ const isOnline = onlineMatch ? onlineMatch[1].trim().toLowerCase() === "true" : true;
80
83
 
81
84
  if (isOnline && statoCamera[ch] === false) {
82
85
  const nomeOnline = await getCameraNameFromNVR(ch);
83
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" } });
84
87
  statoCamera[ch] = true;
85
88
  } else if (!isOnline && statoCamera[ch] !== false) {
86
- 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" } });
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" } });
87
90
  statoCamera[ch] = false;
88
91
  } else if (statoCamera[ch] === undefined) {
89
92
  statoCamera[ch] = isOnline;
90
93
  }
91
94
  }
92
95
  }
93
- } catch (e) {}
96
+ } catch (e) {
97
+ node.error(`Errore durante il check delle camere: ${e.message}`);
98
+ }
94
99
  updateNodeStatus();
95
100
  }
96
101
 
@@ -118,13 +123,12 @@ module.exports = function(RED) {
118
123
  const referenceTime = new Date();
119
124
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
120
125
 
121
- // Calcolo dinamico del Track ID per il Video (canale * 100 + 1)
122
126
  const videoTrackID = (parseInt(channelID) * 100) + 1;
123
127
 
128
+ // Recupera il nome reale prima di inviare l'allarme
124
129
  const nomeCamera = await getCameraNameFromNVR(channelID);
125
130
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
126
131
 
127
- // Finestra temporale per la ricerca foto JSON
128
132
  const startSearchStr = referenceTime.toISOString().split('T')[0] + "T00:00:00 01:00";
129
133
  const endSearchStr = referenceTime.toISOString().split('T')[0] + "T23:59:59 01:00";
130
134
 
@@ -144,7 +148,7 @@ module.exports = function(RED) {
144
148
  let fileDaCancellare = [];
145
149
 
146
150
  try {
147
- // 1. SCARICAMENTO IMMAGINE (POST in JSON a eventRecordSearch)
151
+ // 1. SCARICAMENTO IMMAGINE
148
152
  const payloadFoto = {
149
153
  "EventSearchDescription": {
150
154
  "searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
@@ -169,7 +173,6 @@ module.exports = function(RED) {
169
173
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
170
174
  });
171
175
 
172
- // CORRETTO: Adesso cerca dentro Targets e prende il pictureUrl grezzo come su Postman
173
176
  if (resFotoSearch.data?.EventSearchResult?.Targets?.[0]?.pictureUrl) {
174
177
  const urlFotoGrezzo = resFotoSearch.data.EventSearchResult.Targets[0].pictureUrl;
175
178
 
@@ -186,7 +189,7 @@ module.exports = function(RED) {
186
189
  fileDaCancellare.push(fullImgPath);
187
190
  }
188
191
 
189
- // 2. SCARICAMENTO VIDEO (POST XML a search -> GET XML a download)
192
+ // 2. SCARICAMENTO VIDEO
190
193
  const startVideoSearch = referenceTime.toISOString().split('T')[0] + "T00:00:00Z";
191
194
  const endVideoSearch = referenceTime.toISOString().split('T')[0] + "T23:59:59Z";
192
195
 
@@ -254,7 +257,6 @@ module.exports = function(RED) {
254
257
  });
255
258
  }
256
259
 
257
- // Invio dei media elaborati
258
260
  if (output.foto_base64 || output.video_base64) {
259
261
  node.send({ payload: output });
260
262
 
@@ -262,9 +264,7 @@ module.exports = function(RED) {
262
264
  for (let file of fileDaCancellare) {
263
265
  try {
264
266
  if (fs.existsSync(file)) fs.unlinkSync(file);
265
- } catch (err) {
266
- node.error(`Errore pulizia file temporaneo ${file}: ${err.message}`);
267
- }
267
+ } catch (err) {}
268
268
  }
269
269
  }, 120000);
270
270
  }
package/hik-snapshot.js CHANGED
@@ -1,320 +1,106 @@
1
1
  const axios = require('axios');
2
- const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
3
2
  const https = require('https');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
- const { exec } = require('child_process');
3
+ const mhocDigestSnapshot = require('@mhoc/axios-digest-auth');
4
+ const DigestAuthClass = mhocDigestSnapshot.default;
8
5
 
9
6
  module.exports = function(RED) {
10
- function HikMediaBufferNode(config) {
7
+ function HikSnapshotNode(config) {
11
8
  RED.nodes.createNode(this, config);
12
9
  const node = this;
13
-
14
- node.name = config.name || "TEST";
10
+
11
+ // Recuperiamo il nome del nodo dalla configurazione (se vuoto usa l'host)
12
+ node.nodeName = config.name || `NVR_${config.host}`;
13
+
14
+ node.protocol = config.protocol || "http";
15
15
  node.host = config.host;
16
16
  node.port = config.port || "80";
17
- node.protocol = config.protocol || "http";
18
17
  node.user = config.user;
19
18
  node.pass = config.pass;
20
-
21
- let streamRequest = null;
22
- let isClosing = false;
23
- let lastTriggerTime = {};
24
- let nvrOnline = true;
25
- let statoCamera = {};
19
+ node.maxChannels = parseInt(config.channel) || 1;
26
20
 
27
21
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
28
- const EventList = ["FieldDetection", "LineDetection"];
29
-
30
- // CARTELLA TEMPORANEA
31
- const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
32
- if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
33
-
34
- node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
35
-
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
- });
48
-
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
- }
58
-
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
- });
70
-
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
22
 
113
- const heartbeatInterval = setInterval(checkCameras, 30000);
23
+ node.on('input', async function(msg) {
24
+ if (msg.payload !== true) return;
114
25
 
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;
26
+ node.status({fill: "blue", shape: "dot", text: "Verifica canali..."});
127
27
 
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}...`});
28
+ const digest = new DigestAuthClass({
29
+ username: node.user,
30
+ password: node.pass
31
+ });
131
32
 
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";
33
+ const data = new Date();
34
+ const year = data.getFullYear();
35
+ const month = data.getMonth() + 1;
36
+ const day = data.getDate();
134
37
 
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
- };
38
+ let snapshotResults = [];
147
39
 
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
- }
40
+ for (let i = 1; i <= node.maxChannels; i++) {
41
+ const chanId = i + "01";
42
+ const snapUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/Streaming/channels/${chanId}/picture`;
43
+ const recordUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/record/tracks/${chanId}/dailyDistribution`;
44
+
45
+ const recordXml = `<?xml version="1.0" encoding="utf-8"?><trackDailyParam><year>${year}</year><monthOfYear>${month}</monthOfYear><dayOfMonth>${day}</dayOfMonth></trackDailyParam>`;
46
+
47
+ let resCanale = {
48
+ name: node.nodeName, // <--- INIEZIONE RIGIDA DEL NOME DEL CONDOMINIO
49
+ channel: i,
50
+ photo: null,
51
+ snapOk: false,
52
+ isRecording: false
166
53
  };
167
54
 
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({
55
+ // 1. SNAPSHOT
56
+ try {
57
+ const responseSnap = await digest.request({
180
58
  method: 'GET',
181
- url: urlFotoGrezzo,
59
+ url: snapUrl,
182
60
  responseType: 'arraybuffer',
183
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
61
+ httpsAgent: node.protocol === 'https' ? httpsAgent : undefined,
62
+ timeout: 5000
184
63
  });
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);
64
+ resCanale.photo = responseSnap.data;
65
+ resCanale.snapOk = true;
66
+ } catch (err) {
67
+ resCanale.snapError = err.message;
190
68
  }
191
69
 
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
- });
70
+ // 2. RECORDING
71
+ try {
72
+ const responseRec = await digest.request({
73
+ method: 'POST',
74
+ url: recordUrl,
75
+ data: recordXml,
76
+ headers: { 'Content-Type': 'application/xml' },
77
+ httpsAgent: node.protocol === 'https' ? httpsAgent : undefined,
78
+ timeout: 5000
257
79
  });
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;
258
85
  }
259
86
 
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}`);
87
+ snapshotResults.push(resCanale);
88
+ await new Promise(resolve => setTimeout(resolve, 200));
274
89
  }
275
- updateNodeStatus();
276
- }
277
90
 
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
- }
91
+ msg.payload = snapshotResults;
92
+ node.send(msg);
303
93
 
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();
94
+ // Conteggi per lo stato del nodo
95
+ const snapCount = snapshotResults.filter(v => v.snapOk).length;
96
+ const recCount = snapshotResults.filter(v => v.isRecording).length;
97
+
98
+ node.status({
99
+ fill: (snapCount === node.maxChannels && recCount === node.maxChannels) ? "green" : "yellow",
100
+ shape: "dot",
101
+ text: `Snap: ${snapCount}/${node.maxChannels} | Rec: ${recCount}/${node.maxChannels}`
102
+ });
317
103
  });
318
104
  }
319
- RED.nodes.registerType("hik-media-buffer", HikMediaBufferNode);
105
+ RED.nodes.registerType("hik-snapshot", HikSnapshotNode);
320
106
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.115",
3
+ "version": "1.1.116",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",