node-red-contrib-hik-media-buffer 1.0.18 → 1.1.0

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 +85 -42
  2. package/package.json +1 -2
@@ -1,33 +1,34 @@
1
1
  const axios = require('axios');
2
- const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
3
2
  const https = require('https');
4
3
  const fs = require('fs');
5
4
  const path = require('path');
6
5
  const os = require('os');
7
6
  const { exec } = require('child_process');
8
7
 
8
+ // --- STRATEGIA SWITCH: DEFINIZIONE MODALITÀ ---
9
+ // Valori possibili: 'digest', 'basic', 'bearer'
10
+ const AUTH_MODE = 'digest';
11
+
9
12
  module.exports = function(RED) {
10
13
  function HikMediaBufferNode(config) {
11
14
  RED.nodes.createNode(this, config);
12
15
  const node = this;
13
16
 
14
- // --- ASSEGNAZIONE PROPRIETÀ AL NODO ---
17
+ // --- ASSEGNAZIONE PROPRIETÀ ---
18
+ node.name = config.name;
15
19
  node.host = config.host;
16
20
  node.port = config.port || "80";
17
21
  node.protocol = config.protocol || "http";
18
22
  node.user = config.user;
19
23
  node.pass = config.pass;
20
- node.camPass = config.camPass || config.pass; // Usa pass dell'NVR se quella cam specifica non c'è
24
+ node.camPass = config.camPass || config.pass;
21
25
  node.cameras = config.cameras || [];
22
- // --------------------------------------
23
26
 
24
27
  let streamRequest = null;
25
28
  let isClosing = false;
26
29
  let lastTriggerTime = 0;
27
-
28
- // Stati di connessione
29
30
  let nvrOnline = true;
30
- let statoCamera = {}; // Memorizza lo stato { "IP": true/false }
31
+ let statoCamera = {};
31
32
 
32
33
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
33
34
  const tempDir = os.tmpdir();
@@ -36,32 +37,72 @@ module.exports = function(RED) {
36
37
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
37
38
 
38
39
  function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
39
-
40
- // --- CONTROLLO SE LE TELECAMERE SONO ONLINE ---
40
+
41
+ // --- PRENDE IL NOME DELLA TELECAMERA ---
42
+ async function getCameraName(cam) {
43
+ try {
44
+ const res = await hikRequest({
45
+ method: 'GET',
46
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`,
47
+ user: node.user,
48
+ pass: node.camPass
49
+ });
50
+ const match = res.data.match(/<name>([^<]+)<\/name>/);
51
+ return match ? match[1] : `Cam_${cam.channel}`;
52
+ } catch (e) {
53
+ return `Camera_${cam.ip}`; // Fallback se la camera è già offline
54
+ }
55
+ }
56
+
57
+ // --- HELPER UNICO PER LE CHIAMATE (STRATEGIA SWITCH) ---
58
+ async function hikRequest(options) {
59
+ const { method, url, data, responseType, user, pass, headers = {} } = options;
60
+
61
+ const requestConfig = {
62
+ method,
63
+ url,
64
+ data,
65
+ responseType: responseType || 'data',
66
+ httpsAgent: node.protocol === "https" ? httpsAgent : undefined,
67
+ timeout: 10000,
68
+ headers: { ...headers }
69
+ };
70
+
71
+ // LOGICA DI AUTENTICAZIONE
72
+ if (AUTH_MODE === 'digest' || AUTH_MODE === 'basic') {
73
+ // Axios gestisce Basic/Digest internamente se configurato così:
74
+ requestConfig.auth = { username: user, password: pass };
75
+ } else if (AUTH_MODE === 'bearer') {
76
+ // Esempio per Token futuro
77
+ requestConfig.headers['Authorization'] = `Bearer ${node.context().get('myToken')}`;
78
+ }
79
+
80
+ return axios(requestConfig);
81
+ }
82
+
83
+ // --- CONTROLLO ONLINE TELECAMERE ---
41
84
  async function checkCameras() {
42
85
  if (isClosing) return;
43
86
  for (let cam of node.cameras) {
44
- const camAuth = new AxiosDigestAuth({
45
- username: node.user,
46
- password: node.camPass
47
- });
48
87
  try {
49
- await camAuth.request({
88
+ await hikRequest({
50
89
  method: 'GET',
51
90
  url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
52
- timeout: 5000,
53
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
91
+ user: node.user,
92
+ pass: node.camPass
54
93
  });
55
94
 
56
95
  if (statoCamera[cam.ip] === false) {
57
- node.send({ payload: { status: "online", ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
96
+ const nomeOnline = await getCameraName(cam);
97
+ node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
58
98
  statoCamera[cam.ip] = true;
59
99
  } else if (statoCamera[cam.ip] === undefined) {
60
100
  statoCamera[cam.ip] = true;
61
101
  }
62
102
  } catch (e) {
63
103
  if (statoCamera[cam.ip] !== false) {
64
- node.send({ payload: { status: "offline", ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
104
+ const nomeOffline = await getCameraName(cam);
105
+ node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
65
106
  statoCamera[cam.ip] = false;
66
107
  }
67
108
  }
@@ -82,19 +123,17 @@ module.exports = function(RED) {
82
123
 
83
124
  const heartbeatInterval = setInterval(checkCameras, 30000);
84
125
 
85
- // --- FUNZIONE DOWNLOAD ---
126
+ // --- FUNZIONE DOWNLOAD MEDIA ---
86
127
  async function downloadMedia(evento, channelID) {
87
128
  const camera = node.cameras.find(c => c.channel == channelID);
129
+ const nomeCamera = await getCameraName(camera);
130
+
88
131
  if (!camera) return;
89
132
 
90
133
  const nowTime = Date.now();
91
134
  if (nowTime - lastTriggerTime < 10000) return;
92
135
  lastTriggerTime = nowTime;
93
136
 
94
- const camAuth = new AxiosDigestAuth({
95
- username: node.user,
96
- password: node.camPass
97
- });
98
137
  const referenceTime = new Date();
99
138
  const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
100
139
  const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
@@ -103,7 +142,7 @@ module.exports = function(RED) {
103
142
  await new Promise(resolve => setTimeout(resolve, 6000));
104
143
 
105
144
  const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
106
- let output = { ip: camera.ip, channel: channelID, event: evento, videoPath: null, imageBuffer: null };
145
+ let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imageBuffer: null };
107
146
 
108
147
  try {
109
148
  const tracks = [{ name: "termicoV", id: "201" }, { name: "termico", id: "203" }];
@@ -114,15 +153,16 @@ module.exports = function(RED) {
114
153
  <trackIDList><trackID>${t.id}</trackID></trackIDList>
115
154
  <timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList>
116
155
  <maxResults>100</maxResults>
117
- <searchResultPostion>0</searchResultPostion>
118
156
  <metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList>
119
157
  </CMSearchDescription>`;
120
-
121
- const resSearch = await camAuth.request({
158
+
159
+ const resSearch = await hikRequest({
122
160
  method: 'POST',
123
- url: `${baseUrl}/search`,
124
- data: searchXml,
125
- headers: { "Content-Type": "application/xml" }
161
+ url: `${baseUrl}/search`,
162
+ data: searchXml,
163
+ headers: { "Content-Type": "application/xml" },
164
+ user: node.user,
165
+ pass: node.camPass
126
166
  });
127
167
 
128
168
  let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
@@ -130,10 +170,13 @@ module.exports = function(RED) {
130
170
 
131
171
  if (uriMatch) {
132
172
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
133
- const resDown = await camAuth.request({
134
- method: 'GET', url: `${baseUrl}/download`,
135
- data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
136
- responseType: 'arraybuffer'
173
+ const resDown = await hikRequest({
174
+ method: 'GET',
175
+ url: `${baseUrl}/download`,
176
+ data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
177
+ responseType: 'arraybuffer',
178
+ user: node.user,
179
+ pass: node.camPass
137
180
  });
138
181
 
139
182
  let buffer = Buffer.from(resDown.data);
@@ -144,6 +187,7 @@ module.exports = function(RED) {
144
187
  const rawPath = path.join(tempDir, `raw_${Date.now()}.mp4`);
145
188
  const fixedPath = path.join(tempDir, `hik_v_${channelID}_${Date.now()}.mp4`);
146
189
  fs.writeFileSync(rawPath, buffer);
190
+
147
191
  await new Promise((resolve) => {
148
192
  exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
149
193
  if (!err) {
@@ -167,15 +211,14 @@ module.exports = function(RED) {
167
211
  // --- GESTIONE NVR ALERT STREAM ---
168
212
  function startAlertStream() {
169
213
  if (isClosing) return;
170
- const nvrAuth = new AxiosDigestAuth({
171
- username: node.user,
172
- password: node.pass
173
- });
174
214
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
175
215
 
176
- nvrAuth.request({
177
- method: 'GET', url: url, responseType: 'stream',
178
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
216
+ hikRequest({
217
+ method: 'GET',
218
+ url: url,
219
+ responseType: 'stream',
220
+ user: node.user,
221
+ pass: node.pass
179
222
  }).then(response => {
180
223
  streamRequest = response;
181
224
  if (!nvrOnline) {
@@ -216,7 +259,7 @@ module.exports = function(RED) {
216
259
  node.on('close', (done) => {
217
260
  isClosing = true;
218
261
  clearInterval(heartbeatInterval);
219
- if (streamRequest) streamRequest.data.destroy();
262
+ if (streamRequest && streamRequest.data) streamRequest.data.destroy();
220
263
  done();
221
264
  });
222
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.0.18",
3
+ "version": "1.1.0",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",
@@ -22,7 +22,6 @@
22
22
  "dependencies": {
23
23
  "@mhoc/axios-digest-auth": "^0.8.0",
24
24
  "axios": "^1.6.0",
25
- "node-red-contrib-hik-media-buffer": "^1.0.10",
26
25
  "xml2js": "^0.6.2"
27
26
  }
28
27
  }