node-red-contrib-hik-media-buffer 1.1.44 → 1.1.46

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 +25 -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,18 +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>NVR_CATCH</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/${output.tipo_evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
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}</metadataDescriptor></metadataList></CMSearchDescription>`;
172
168
 
173
- const resSearch = await nvrAuth.request({
169
+ const resSearch = await camAuth.request({
174
170
  method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
175
171
  });
176
172
 
@@ -179,7 +175,7 @@ module.exports = function(RED) {
179
175
 
180
176
  if (uriMatch) {
181
177
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
182
- const resDown = await nvrAuth.request({
178
+ const resDown = await camAuth.request({
183
179
  method: 'GET',
184
180
  url: `${baseUrl}/download`,
185
181
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
@@ -187,12 +183,14 @@ module.exports = function(RED) {
187
183
  });
188
184
 
189
185
  let buffer = Buffer.from(resDown.data);
190
- if (t.type === "foto") {
186
+ if (t.id === trackFoto.toString()) {
187
+ // SALVA FOTO IN LOCALE
191
188
  const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
192
189
  fs.writeFileSync(fullImgPath, buffer);
193
190
  output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
194
191
  fileDaCancellare.push(fullImgPath);
195
192
  } else {
193
+ // SALVA VIDEO IN LOCALE
196
194
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
197
195
  const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
198
196
  const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
@@ -225,9 +223,9 @@ module.exports = function(RED) {
225
223
  }
226
224
 
227
225
  } catch (e) {
228
- node.error(`Errore Download NVR per Canale ${channelID}: ${e.message}`);
226
+ node.error(`Errore Download Cam ${channelID}: ${e.message}`);
229
227
  }
230
- node.status({fill:"green", shape:"ring", text:"In ascolto"});
228
+ updateNodeStatus();
231
229
  }
232
230
 
233
231
  // --- ALERT STREAM ---
@@ -235,33 +233,22 @@ module.exports = function(RED) {
235
233
  if (isClosing) return;
236
234
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
237
235
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
238
-
239
236
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
240
237
  .then(response => {
241
238
  streamRequest = response;
242
239
  if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
243
240
  updateNodeStatus();
244
-
245
241
  response.data.on('data', (chunk) => {
246
242
  const data = chunk.toString().toLowerCase();
247
-
248
243
  if (data.includes("active")) {
249
244
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
250
245
  if (chMatch) {
251
- let channelID = chMatch[1];
252
246
  let ev = "Unknown";
247
+ for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
253
248
 
254
- for (let e of EventList) {
255
- if (data.includes(e.toLowerCase())) {
256
- ev = e;
257
- break;
258
- }
259
- }
249
+ if (ev === "Unknown") return; // Ignora heartbeat o altri eventi non mappati
260
250
 
261
- // Se non è un allarme presente in lista o è un heartbeat, fermati qui
262
- if (ev === "Unknown") return;
263
-
264
- downloadMedia(ev, channelID);
251
+ downloadMedia(ev, chMatch[1]);
265
252
  }
266
253
  }
267
254
  });
@@ -278,7 +265,6 @@ module.exports = function(RED) {
278
265
 
279
266
  startAlertStream();
280
267
  setTimeout(checkCameras, 2000);
281
-
282
268
  node.on('close', (done) => {
283
269
  isClosing = true;
284
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.44",
3
+ "version": "1.1.46",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",