node-red-contrib-hik-media-buffer 1.1.117 → 1.1.119

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 +28 -30
  2. package/package.json +1 -1
@@ -27,28 +27,29 @@ module.exports = function(RED) {
27
27
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
28
28
  const EventList = ["FieldDetection", "LineDetection"];
29
29
 
30
- // CARTELLA TEMPORANEA
31
30
  const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
32
31
  if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
33
32
 
34
33
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
35
34
 
36
- function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
35
+ function toHikSearchDate(d) {
36
+ return d.toISOString().split('.')[0] + "Z";
37
+ }
37
38
 
38
- // --- PRENDE IL NOME REALE DELLA TELECAMERA DALL'NVR ---
39
+ // --- 1. PRENDE IL NOME REALE DELLA TELECAMERA DAL DIGITAL PROXY ---
39
40
  async function getCameraNameFromNVR(channelID) {
40
41
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
41
42
  try {
43
+ // In accordo con la pag. 58 della documentazione, interroghiamo l'InputProxy del canale digitale
42
44
  const res = await nvrAuth.request({
43
45
  method: 'GET',
44
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}`,
46
+ url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/${channelID}`,
45
47
  timeout: 5000,
46
48
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
47
49
  });
48
-
49
50
  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);
51
+ // Estraiamo il tag <name> nativo del canale digitale
52
+ const match = data.match(/<name>([^<]+)<\/name>/i);
52
53
  if (match && match[1]) return match[1].trim();
53
54
  return `Canale ${channelID}`;
54
55
  } catch (e) {
@@ -56,45 +57,47 @@ module.exports = function(RED) {
56
57
  }
57
58
  }
58
59
 
59
- // --- CONTROLLO STATUS CAMERE (ONLINE/OFFLINE) INTERROGANDO L'NVR ---
60
+ // --- 2. CONTROLLO STATUS CAMERE AUTOMATICO ---
60
61
  async function checkCameras() {
61
62
  if (isClosing || !nvrOnline) return;
62
63
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
63
64
  try {
65
+ // Interroghiamo l'elenco proxy centralizzato dell'NVR
64
66
  const res = await nvrAuth.request({
65
67
  method: 'GET',
66
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels`,
67
- timeout: 8000,
68
+ url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels`,
69
+ timeout: 6000,
68
70
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
69
71
  });
70
72
 
71
73
  const xml = res.data.toString();
72
- const channelBlocks = xml.match(/<VideoInputChannel>[\s\S]*?<\/VideoInputChannel>/g) || [];
74
+ // Estraiamo i singoli blocchi di canali digitali
75
+ const channelBlocks = xml.match(/<InputProxyChannel>[\s\S]*?<\/InputProxyChannel>/g) || [];
73
76
 
74
77
  for (let block of channelBlocks) {
75
78
  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);
79
+ // Questa regex cerca la parola "true" o "false" dentro il tag online, sia esso <online>, <onLine> o <ONLINE>
80
+ const onlineMatch = block.match(/<onLine[^>]*>(true|false)<\/onLine>/i) || block.match(/<online[^>]*>(true|false)<\/online>/i);
78
81
 
79
82
  if (idMatch) {
80
83
  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;
84
+ const isOnline = onlineMatch ? onlineMatch[1].toLowerCase() === "true" : false;
83
85
 
84
86
  if (isOnline && statoCamera[ch] === false) {
85
87
  const nomeOnline = await getCameraNameFromNVR(ch);
86
88
  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
89
  statoCamera[ch] = true;
88
- } else if (!isOnline && statoCamera[ch] !== false) {
90
+ } else if (!isOnline && statoCamera[ch] !== false && statoCamera[ch] !== undefined) {
89
91
  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
92
  statoCamera[ch] = false;
91
93
  } else if (statoCamera[ch] === undefined) {
94
+ // Configurazione iniziale dello stato al boot
92
95
  statoCamera[ch] = isOnline;
93
96
  }
94
97
  }
95
98
  }
96
99
  } catch (e) {
97
- node.error(`Errore durante il check delle camere: ${e.message}`);
100
+ node.error(`Errore nel check diagnostico: ${e.message}`);
98
101
  }
99
102
  updateNodeStatus();
100
103
  }
@@ -110,20 +113,18 @@ module.exports = function(RED) {
110
113
  }
111
114
  }
112
115
 
113
- const heartbeatInterval = setInterval(checkCameras, 30000);
116
+ const heartbeatInterval = setInterval(checkCameras, 15000);
114
117
 
115
- // --- FORMATTATORE PER IL BODY DI RICERCA (Con i trattini: 2026-07-07T17:24:00Z) ---
116
- function toHikSearchDate(d) {
117
- return d.toISOString().split('.')[0] + "Z";
118
- }
119
-
120
- // --- DOWNLOAD CON ORARIO DINAMICO STRETTO SULL'ULTIMO EVENTO ---
118
+ // --- 3. DOWNLOAD MEDIA CON DELAY E FORMATO STRINGA CORRETTO ---
121
119
  async function downloadMedia(evento, channelID) {
122
120
  const nowTime = Date.now();
123
121
  if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
124
122
  if (nowTime - lastTriggerTime[channelID] < 5000) return;
125
123
  lastTriggerTime[channelID] = nowTime;
126
124
 
125
+ node.status({fill:"yellow", shape:"dot", text:`Attesa scrittura NVR (6s)...`});
126
+ await new Promise(resolve => setTimeout(resolve, 6000));
127
+
127
128
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
128
129
  const referenceTime = new Date();
129
130
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
@@ -132,7 +133,6 @@ module.exports = function(RED) {
132
133
  const nomeCamera = await getCameraNameFromNVR(channelID);
133
134
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
134
135
 
135
- // FINESTRA TEMPORALE REALE: Cerchiamo l'evento da 2 minuti fa a 1 minuto nel futuro
136
136
  const inizioFinestra = new Date(referenceTime.getTime() - (2 * 60 * 1000));
137
137
  const fineFinestra = new Date(referenceTime.getTime() + (1 * 60 * 1000));
138
138
 
@@ -155,12 +155,12 @@ module.exports = function(RED) {
155
155
  let fileDaCancellare = [];
156
156
 
157
157
  try {
158
- // 1. 📸 RICERCA E SCARICAMENTO IMMAGINE (Formato con i trattini nel body)
158
+ // 1. 📸 SCARICAMENTO IMMAGINE
159
159
  const payloadFoto = {
160
160
  "EventSearchDescription": {
161
161
  "searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
162
162
  "searchResultPosition": 0,
163
- "maxResults": 1, // Prendiamo solo il più vicino all'evento attuale
163
+ "maxResults": 1,
164
164
  "timeSpanList": [{ "startTime": startSearchStr, "endTime": endSearchStr }],
165
165
  "type": "all",
166
166
  "channels": [parseInt(channelID)],
@@ -193,7 +193,7 @@ module.exports = function(RED) {
193
193
  fileDaCancellare.push(fullImgPath);
194
194
  }
195
195
 
196
- // 2. 🎥 RICERCA E SCARICAMENTO VIDEO (Formato con i trattini nel body)
196
+ // 2. 🎥 SCARICAMENTO VIDEO
197
197
  const payloadVideoSearch = `<?xml version="1.0" encoding="utf-8"?>
198
198
  <CMSearchDescription>
199
199
  <searchID>CEC13F0F-1AEA-4474-A2DA-CFFF332C7C5B</searchID>
@@ -221,10 +221,8 @@ module.exports = function(RED) {
221
221
  const uriMatch = xmlString.match(/<playbackURI>([\s\S]*?)<\/playbackURI>/i);
222
222
 
223
223
  if (uriMatch && uriMatch[1]) {
224
- // Estrae il playbackURI grezzo che contiene GIÀ la data compressa senza trattini generata dall'NVR
225
224
  const playbackURIGrezzo = uriMatch[1].trim();
226
225
 
227
- // Inviamo il downloadRequest passando l'URI nativo fornito dall'NVR (che rispetta il formato compresso)
228
226
  const payloadDownload = `<?xml version="1.0" encoding="UTF-8"?>
229
227
  <downloadRequest>
230
228
  <playbackURI>${playbackURIGrezzo}</playbackURI>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.117",
3
+ "version": "1.1.119",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",