node-red-contrib-hik-media-buffer 1.1.116 → 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 +34 -37
  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,25 +114,29 @@ module.exports = function(RED) {
112
114
 
113
115
  const heartbeatInterval = setInterval(checkCameras, 30000);
114
116
 
115
- // --- DOWNLOAD CON URL E BODY SECONDO SPECIFICHE ---
117
+ // --- 3. DOWNLOAD MEDIA CON DELAY E FORMATO STRINGA CORRETTO ---
116
118
  async function downloadMedia(evento, channelID) {
117
119
  const nowTime = Date.now();
118
120
  if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
119
121
  if (nowTime - lastTriggerTime[channelID] < 5000) return;
120
122
  lastTriggerTime[channelID] = nowTime;
121
123
 
124
+ node.status({fill:"yellow", shape:"dot", text:`Attesa scrittura NVR (6s)...`});
125
+ await new Promise(resolve => setTimeout(resolve, 6000));
126
+
122
127
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
123
128
  const referenceTime = new Date();
124
129
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
125
130
 
126
131
  const videoTrackID = (parseInt(channelID) * 100) + 1;
127
-
128
- // Recupera il nome reale prima di inviare l'allarme
129
132
  const nomeCamera = await getCameraNameFromNVR(channelID);
130
133
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
131
134
 
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";
135
+ const inizioFinestra = new Date(referenceTime.getTime() - (2 * 60 * 1000));
136
+ const fineFinestra = new Date(referenceTime.getTime() + (1 * 60 * 1000));
137
+
138
+ const startSearchStr = toHikSearchDate(inizioFinestra);
139
+ const endSearchStr = toHikSearchDate(fineFinestra);
134
140
 
135
141
  let output = {
136
142
  tipo_messaggio: "evento",
@@ -148,16 +154,13 @@ module.exports = function(RED) {
148
154
  let fileDaCancellare = [];
149
155
 
150
156
  try {
151
- // 1. SCARICAMENTO IMMAGINE
157
+ // 1. 📸 SCARICAMENTO IMMAGINE
152
158
  const payloadFoto = {
153
159
  "EventSearchDescription": {
154
160
  "searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
155
161
  "searchResultPosition": 0,
156
- "maxResults": 30,
157
- "timeSpanList": [{
158
- "startTime": startSearchStr,
159
- "endTime": endSearchStr
160
- }],
162
+ "maxResults": 1,
163
+ "timeSpanList": [{ "startTime": startSearchStr, "endTime": endSearchStr }],
161
164
  "type": "all",
162
165
  "channels": [parseInt(channelID)],
163
166
  "eventType": "behavior",
@@ -189,21 +192,18 @@ module.exports = function(RED) {
189
192
  fileDaCancellare.push(fullImgPath);
190
193
  }
191
194
 
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
-
195
+ // 2. 🎥 SCARICAMENTO VIDEO
196
196
  const payloadVideoSearch = `<?xml version="1.0" encoding="utf-8"?>
197
197
  <CMSearchDescription>
198
198
  <searchID>CEC13F0F-1AEA-4474-A2DA-CFFF332C7C5B</searchID>
199
199
  <trackList><trackID>${videoTrackID}</trackID></trackList>
200
200
  <timeSpanList>
201
201
  <timeSpan>
202
- <startTime>${startVideoSearch}</startTime>
203
- <endTime>${endVideoSearch}</endTime>
202
+ <startTime>${startSearchStr}</startTime>
203
+ <endTime>${endSearchStr}</endTime>
204
204
  </timeSpan>
205
205
  </timeSpanList>
206
- <maxResults>100</maxResults>
206
+ <maxResults>1</maxResults>
207
207
  <searchResultPostion>0</searchResultPostion>
208
208
  <metadataList><metadataDescriptor>//recordType.meta.std-cgi.com</metadataDescriptor></metadataList>
209
209
  </CMSearchDescription>`;
@@ -259,12 +259,9 @@ module.exports = function(RED) {
259
259
 
260
260
  if (output.foto_base64 || output.video_base64) {
261
261
  node.send({ payload: output });
262
-
263
262
  setTimeout(() => {
264
263
  for (let file of fileDaCancellare) {
265
- try {
266
- if (fs.existsSync(file)) fs.unlinkSync(file);
267
- } catch (err) {}
264
+ try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
268
265
  }
269
266
  }, 120000);
270
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.116",
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",