node-red-contrib-hik-media-buffer 1.1.45 → 1.1.47

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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +26 -39
  2. package/package.json +1 -1
@@ -23,7 +23,6 @@ module.exports = function(RED) {
23
23
  let lastTriggerTime = {};
24
24
  let nvrOnline = true;
25
25
 
26
- // Strutture dinamiche per monitorare canali e stati senza liste fisse
27
26
  let statoCanale = {};
28
27
  let ultimoAllarmeTempo = {};
29
28
 
@@ -65,7 +64,6 @@ module.exports = function(RED) {
65
64
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
66
65
 
67
66
  try {
68
- // Interroghiamo l'NVR sullo stato globale dei suoi canali proxy
69
67
  const res = await nvrAuth.request({
70
68
  method: 'GET',
71
69
  url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/status`,
@@ -76,22 +74,19 @@ module.exports = function(RED) {
76
74
  nvrOnline = true;
77
75
  const xmlData = res.data.toString();
78
76
 
79
- // Regex globale per estrarre TUTTI i blocchi <InputProxyChannelStatus> presenti nell'XML dell'NVR
80
77
  const bloccoRegex = /<InputProxyChannelStatus[\s\\S]*?>([\s\S]*?)<\/InputProxyChannelStatus>/gi;
81
78
  let matchBlocco;
82
79
 
83
80
  while ((matchBlocco = bloccoRegex.exec(xmlData)) !== null) {
84
81
  const bloccoContenuto = matchBlocco[1];
85
82
 
86
- // Estraiamo l'ID del canale e il suo stato dal blocco attuale
87
83
  const idMatch = bloccoContenuto.match(/<id>(\d+)<\/id>/i);
88
- const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i); // Corretta regex di chiusura tag
84
+ const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i);
89
85
 
90
86
  if (idMatch && statusMatch) {
91
87
  const ch = idMatch[1];
92
88
  const statusStr = statusMatch[1].toLowerCase();
93
89
 
94
- // Determiniamo se per l'NVR la telecamera su questo canale è attiva
95
90
  const isOnline = statusStr.includes("online") || statusStr.includes("recording");
96
91
 
97
92
  if (isOnline && statoCanale[ch] === false) {
@@ -102,13 +97,12 @@ module.exports = function(RED) {
102
97
  node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Canale_${ch}`, ip_nvr: node.host, channel: ch.toString(), msg: "Camera scollegata o offline su NVR" } });
103
98
  statoCanale[ch] = false;
104
99
  } else if (statoCanale[ch] === undefined) {
105
- // Primo avvio: memorizziamo lo stato iniziale
106
100
  statoCanale[ch] = isOnline;
107
101
  }
108
102
  }
109
103
  }
110
104
  } catch (e) {
111
- nvrOnline = false; // Se fallisce la GET, è l'NVR ad essere irraggiungibile
105
+ nvrOnline = false;
112
106
  }
113
107
  updateNodeStatus();
114
108
  }
@@ -126,23 +120,24 @@ module.exports = function(RED) {
126
120
 
127
121
  const heartbeatInterval = setInterval(checkCameras, 30000);
128
122
 
129
- // --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DALL'NVR ---
123
+ // --- DOWNLOAD MULTIMEDIALE DA NVR CON LOGICA STRUTTURALE ORIGINALE ---
130
124
  async function downloadMedia(evento, channelID) {
131
125
  const nowTime = Date.now();
132
126
  if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
133
127
  if (nowTime - lastTriggerTime[channelID] < 5000) return;
134
128
  lastTriggerTime[channelID] = nowTime;
135
129
 
136
- const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
130
+ // ISTANZIAMO camAuth SULL'NVR: Risolve il problema delle credenziali vuote nel vecchio blocco
131
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
132
+
137
133
  const referenceTime = new Date();
138
134
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
139
- const startTime = toHikDate(new Date(referenceTime.getTime() - (15 * 1000))); // Allargato a 15s per sicurezza
140
- const endTime = toHikDate(new Date(referenceTime.getTime() + (15 * 1000)));
135
+ const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
136
+ const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
141
137
 
142
138
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
143
139
  const nomeCamera = await getCameraName(channelID);
144
140
 
145
- // Ritardo tecnico per dare tempo all'NVR di chiudere e indicizzare il file su HDD
146
141
  await new Promise(resolve => setTimeout(resolve, 6000));
147
142
  const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
148
143
 
@@ -150,7 +145,7 @@ module.exports = function(RED) {
150
145
  tipo_messaggio: "evento",
151
146
  nome_cliente: node.name,
152
147
  nome_telecamera: nomeCamera,
153
- ip_nvr: node.host,
148
+ ip_telecamera: node.host,
154
149
  tipo_evento: evento,
155
150
  timestamp_epoch: timestamp,
156
151
  stato_telecamera: "ONLINE",
@@ -159,17 +154,19 @@ module.exports = function(RED) {
159
154
  video_base64: null
160
155
  };
161
156
 
162
- let fileDaCancellare = [];
163
157
  const parsedChannel = parseInt(channelID, 10);
164
- const trackVideo = (parsedChannel * 100) + 1; // Formula dinamica NVR (*100+1) e.g. 201
165
- const trackFoto = (parsedChannel * 100) + 3; // Formula dinamica NVR (*100+3) e.g. 203
158
+ const trackVideo = (parsedChannel * 100) + 1; // Formula NVR (*100+1)
159
+ const trackFoto = (parsedChannel * 100) + 3; // Formula NVR (*100+3)
166
160
 
167
- const tracks = [{ id: trackVideo.toString(), type: "video" }, { id: trackFoto.toString(), type: "foto" }];
161
+ const tracks = [{ id: trackVideo.toString() }, { id: trackFoto.toString() }];
162
+ let fileDaCancellare = [];
168
163
 
169
164
  try {
170
165
  for (let t of tracks) {
171
- const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
172
- const resSearch = await nvrAuth.request({
166
+ // RIPRISTINATO: Il tuo XML originale esatto con <trackIDList> e ${evento} dinamico
167
+ const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento.toLowerCase()}</metadataDescriptor></metadataList></CMSearchDescription>`;
168
+
169
+ const resSearch = await camAuth.request({
173
170
  method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
174
171
  });
175
172
 
@@ -178,7 +175,7 @@ module.exports = function(RED) {
178
175
 
179
176
  if (uriMatch) {
180
177
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
181
- const resDown = await nvrAuth.request({
178
+ const resDown = await camAuth.request({
182
179
  method: 'GET',
183
180
  url: `${baseUrl}/download`,
184
181
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
@@ -186,12 +183,14 @@ module.exports = function(RED) {
186
183
  });
187
184
 
188
185
  let buffer = Buffer.from(resDown.data);
189
- if (t.type === "foto") {
186
+ if (t.id === trackFoto.toString()) {
187
+ // SALVA FOTO IN LOCALE
190
188
  const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
191
189
  fs.writeFileSync(fullImgPath, buffer);
192
190
  output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
193
191
  fileDaCancellare.push(fullImgPath);
194
192
  } else {
193
+ // SALVA VIDEO IN LOCALE
195
194
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
196
195
  const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
197
196
  const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
@@ -224,9 +223,9 @@ module.exports = function(RED) {
224
223
  }
225
224
 
226
225
  } catch (e) {
227
- node.error(`Errore Download NVR per Canale ${channelID}: ${e.message}`);
226
+ node.error(`Errore Download Cam ${channelID}: ${e.message}`);
228
227
  }
229
- node.status({fill:"green", shape:"ring", text:"In ascolto"});
228
+ updateNodeStatus();
230
229
  }
231
230
 
232
231
  // --- ALERT STREAM ---
@@ -234,33 +233,22 @@ module.exports = function(RED) {
234
233
  if (isClosing) return;
235
234
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
236
235
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
237
-
238
236
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
239
237
  .then(response => {
240
238
  streamRequest = response;
241
239
  if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
242
240
  updateNodeStatus();
243
-
244
241
  response.data.on('data', (chunk) => {
245
242
  const data = chunk.toString().toLowerCase();
246
-
247
243
  if (data.includes("active")) {
248
244
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
249
245
  if (chMatch) {
250
- let channelID = chMatch[1];
251
246
  let ev = "Unknown";
247
+ for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
252
248
 
253
- for (let e of EventList) {
254
- if (data.includes(e.toLowerCase())) {
255
- ev = e;
256
- break;
257
- }
258
- }
249
+ if (ev === "Unknown") return; // Ignora heartbeat o altri eventi non mappati
259
250
 
260
- // Se non è un allarme presente in lista o è un heartbeat, fermati qui
261
- if (ev === "Unknown") return;
262
-
263
- downloadMedia(ev, channelID);
251
+ downloadMedia(ev, chMatch[1]);
264
252
  }
265
253
  }
266
254
  });
@@ -277,7 +265,6 @@ module.exports = function(RED) {
277
265
 
278
266
  startAlertStream();
279
267
  setTimeout(checkCameras, 2000);
280
-
281
268
  node.on('close', (done) => {
282
269
  isClosing = true;
283
270
  clearInterval(heartbeatInterval);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.45",
3
+ "version": "1.1.47",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",