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

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 +24 -27
  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,30 +57,31 @@ 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 (Senza 403) ---
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 di tutti i canali digitali (Pag. 58 della documentazione)
64
66
  const res = await nvrAuth.request({
65
67
  method: 'GET',
66
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels`,
68
+ url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels`,
67
69
  timeout: 8000,
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 (InputProxyChannel)
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
+ // Standard Hikvision: estraiamo il tag <online> (true/false)
80
+ const onlineMatch = block.match(/<online>([^<]+)<\/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].trim().toLowerCase() === "true" : false;
83
85
 
84
86
  if (isOnline && statoCamera[ch] === false) {
85
87
  const nomeOnline = await getCameraNameFromNVR(ch);
@@ -94,7 +96,7 @@ module.exports = function(RED) {
94
96
  }
95
97
  }
96
98
  } catch (e) {
97
- node.error(`Errore durante il check delle camere: ${e.message}`);
99
+ node.error(`Errore nel check diagnostico: ${e.message}`);
98
100
  }
99
101
  updateNodeStatus();
100
102
  }
@@ -112,18 +114,16 @@ module.exports = function(RED) {
112
114
 
113
115
  const heartbeatInterval = setInterval(checkCameras, 30000);
114
116
 
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 ---
117
+ // --- 3. DOWNLOAD MEDIA CON DELAY E FORMATO STRINGA CORRETTO ---
121
118
  async function downloadMedia(evento, channelID) {
122
119
  const nowTime = Date.now();
123
120
  if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
124
121
  if (nowTime - lastTriggerTime[channelID] < 5000) return;
125
122
  lastTriggerTime[channelID] = nowTime;
126
123
 
124
+ node.status({fill:"yellow", shape:"dot", text:`Attesa scrittura NVR (6s)...`});
125
+ await new Promise(resolve => setTimeout(resolve, 6000));
126
+
127
127
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
128
128
  const referenceTime = new Date();
129
129
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
@@ -132,7 +132,6 @@ module.exports = function(RED) {
132
132
  const nomeCamera = await getCameraNameFromNVR(channelID);
133
133
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
134
134
 
135
- // FINESTRA TEMPORALE REALE: Cerchiamo l'evento da 2 minuti fa a 1 minuto nel futuro
136
135
  const inizioFinestra = new Date(referenceTime.getTime() - (2 * 60 * 1000));
137
136
  const fineFinestra = new Date(referenceTime.getTime() + (1 * 60 * 1000));
138
137
 
@@ -155,12 +154,12 @@ module.exports = function(RED) {
155
154
  let fileDaCancellare = [];
156
155
 
157
156
  try {
158
- // 1. 📸 RICERCA E SCARICAMENTO IMMAGINE (Formato con i trattini nel body)
157
+ // 1. 📸 SCARICAMENTO IMMAGINE
159
158
  const payloadFoto = {
160
159
  "EventSearchDescription": {
161
160
  "searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
162
161
  "searchResultPosition": 0,
163
- "maxResults": 1, // Prendiamo solo il più vicino all'evento attuale
162
+ "maxResults": 1,
164
163
  "timeSpanList": [{ "startTime": startSearchStr, "endTime": endSearchStr }],
165
164
  "type": "all",
166
165
  "channels": [parseInt(channelID)],
@@ -193,7 +192,7 @@ module.exports = function(RED) {
193
192
  fileDaCancellare.push(fullImgPath);
194
193
  }
195
194
 
196
- // 2. 🎥 RICERCA E SCARICAMENTO VIDEO (Formato con i trattini nel body)
195
+ // 2. 🎥 SCARICAMENTO VIDEO
197
196
  const payloadVideoSearch = `<?xml version="1.0" encoding="utf-8"?>
198
197
  <CMSearchDescription>
199
198
  <searchID>CEC13F0F-1AEA-4474-A2DA-CFFF332C7C5B</searchID>
@@ -221,10 +220,8 @@ module.exports = function(RED) {
221
220
  const uriMatch = xmlString.match(/<playbackURI>([\s\S]*?)<\/playbackURI>/i);
222
221
 
223
222
  if (uriMatch && uriMatch[1]) {
224
- // Estrae il playbackURI grezzo che contiene GIÀ la data compressa senza trattini generata dall'NVR
225
223
  const playbackURIGrezzo = uriMatch[1].trim();
226
224
 
227
- // Inviamo il downloadRequest passando l'URI nativo fornito dall'NVR (che rispetta il formato compresso)
228
225
  const payloadDownload = `<?xml version="1.0" encoding="UTF-8"?>
229
226
  <downloadRequest>
230
227
  <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.118",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",