node-red-contrib-hik-media-buffer 1.1.109 → 1.1.111
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.
- package/hik-media-buffer.js +159 -165
- package/package.json +2 -1
package/hik-media-buffer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const os = require('os');
|
|
@@ -16,6 +17,8 @@ module.exports = function(RED) {
|
|
|
16
17
|
node.protocol = config.protocol || "http";
|
|
17
18
|
node.user = config.user;
|
|
18
19
|
node.pass = config.pass;
|
|
20
|
+
node.camPass = config.camPass || config.pass;
|
|
21
|
+
node.cameras = config.cameras || [];
|
|
19
22
|
|
|
20
23
|
let streamRequest = null;
|
|
21
24
|
let isClosing = false;
|
|
@@ -23,232 +26,227 @@ module.exports = function(RED) {
|
|
|
23
26
|
let nvrOnline = true;
|
|
24
27
|
let statoCamera = {};
|
|
25
28
|
|
|
29
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
30
|
+
const EventList = ["FieldDetection", "LineDetection"];
|
|
31
|
+
|
|
32
|
+
// CARTELLA TEMPORANEA
|
|
26
33
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
27
34
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
28
35
|
|
|
29
36
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
const match = res.data.toString().match(/<name>([^<]+)<\/name>/i);
|
|
41
|
-
if (match && match[1]) return match[1].trim();
|
|
42
|
-
return `Canale_${channelID}`;
|
|
43
|
-
} catch (e) { return `Camera_${channelID}`; }
|
|
44
|
-
}
|
|
40
|
+
// --- PRENDE IL NOME DELLA TELECAMERA ---
|
|
41
|
+
async function getCameraName(cam) {
|
|
42
|
+
const camAuth = new AxiosDigestAuth({
|
|
43
|
+
username: node.user,
|
|
44
|
+
password: node.camPass
|
|
45
|
+
});
|
|
45
46
|
|
|
46
|
-
async function checkCameras() {
|
|
47
|
-
if (isClosing || !nvrOnline) return;
|
|
48
47
|
try {
|
|
49
|
-
const res = await
|
|
48
|
+
const res = await camAuth.request({
|
|
50
49
|
method: 'GET',
|
|
51
|
-
url: `${node.protocol}://${
|
|
52
|
-
timeout:
|
|
50
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
|
|
51
|
+
timeout: 5000,
|
|
52
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
53
53
|
});
|
|
54
|
-
const xml = res.data.toString();
|
|
55
|
-
const statusBlocks = xml.match(/<InputProxyChannelStatus>[\s\S]*?<\/InputProxyChannelStatus>/gi) || [];
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const data = res.data.toString();
|
|
56
|
+
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
57
|
+
|
|
58
|
+
if (match && match[1]) {
|
|
59
|
+
return match[1].trim();
|
|
60
|
+
} else {
|
|
61
|
+
return `Canale_${cam.channel}`;
|
|
62
|
+
}
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return `Camera_${cam.channel}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// --- CONTROLLO STATUS CAMERE ---
|
|
70
|
+
async function checkCameras() {
|
|
71
|
+
if (isClosing) return;
|
|
72
|
+
for (let cam of node.cameras) {
|
|
73
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
74
|
+
try {
|
|
75
|
+
await camAuth.request({
|
|
76
|
+
method: 'GET',
|
|
77
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
80
|
+
});
|
|
81
|
+
if (statoCamera[cam.ip] === false) {
|
|
82
|
+
const nomeOnline = await getCameraName(cam);
|
|
83
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
84
|
+
statoCamera[cam.ip] = true;
|
|
85
|
+
} else if (statoCamera[cam.ip] === undefined) {
|
|
86
|
+
statoCamera[cam.ip] = true;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (statoCamera[cam.ip] !== false) {
|
|
90
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera_${cam.channel}`, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
91
|
+
statoCamera[cam.ip] = false;
|
|
73
92
|
}
|
|
74
93
|
}
|
|
75
|
-
}
|
|
94
|
+
}
|
|
76
95
|
updateNodeStatus();
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
function updateNodeStatus() {
|
|
80
99
|
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
81
|
-
if (!nvrOnline) {
|
|
82
|
-
|
|
83
|
-
else
|
|
100
|
+
if (!nvrOnline) {
|
|
101
|
+
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
102
|
+
} else if (offlineCams > 0) {
|
|
103
|
+
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
104
|
+
} else {
|
|
105
|
+
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
106
|
+
}
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
87
110
|
|
|
88
|
-
// ---
|
|
111
|
+
// --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
|
|
89
112
|
async function downloadMedia(evento, channelID) {
|
|
90
|
-
const
|
|
113
|
+
const camera = node.cameras.find(c => c.channel == channelID);
|
|
114
|
+
if (!camera) return;
|
|
115
|
+
|
|
116
|
+
const nomeCamera = await getCameraName(camera);
|
|
117
|
+
|
|
91
118
|
const nowTime = Date.now();
|
|
92
|
-
if
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
if(!lastTriggerTime[camera.ip]){
|
|
120
|
+
lastTriggerTime[camera.ip] = 0;
|
|
121
|
+
}
|
|
122
|
+
if (nowTime - lastTriggerTime[camera.ip] < 5000) return;
|
|
123
|
+
lastTriggerTime[camera.ip] = nowTime;
|
|
95
124
|
|
|
96
|
-
const
|
|
125
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
97
126
|
const referenceTime = new Date();
|
|
98
127
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
128
|
+
const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
|
|
129
|
+
const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
|
|
99
130
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const year = referenceTime.getFullYear();
|
|
103
|
-
const month = pad(referenceTime.getMonth() + 1);
|
|
104
|
-
const day = pad(referenceTime.getDate());
|
|
131
|
+
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
105
133
|
|
|
106
|
-
const
|
|
107
|
-
const endTime = `${year}-${month}-${day}T23:59:59 01:00`;
|
|
108
|
-
|
|
109
|
-
node.status({fill:"yellow", shape:"dot", text:`Invio query protetta...`});
|
|
134
|
+
const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
|
|
110
135
|
|
|
136
|
+
// struttura del payload per Python
|
|
111
137
|
let output = {
|
|
112
|
-
tipo_messaggio: "evento",
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
tipo_messaggio: "evento",
|
|
139
|
+
nome_cliente: node.name,
|
|
140
|
+
nome_telecamera: nomeCamera,
|
|
141
|
+
ip_telecamera: camera.ip,
|
|
142
|
+
tipo_evento: evento,
|
|
143
|
+
timestamp_epoch: timestamp,
|
|
144
|
+
stato_telecamera: "ONLINE",
|
|
145
|
+
channel: channelID.toString(),
|
|
146
|
+
foto_base64: null,
|
|
147
|
+
video_base64: null
|
|
115
148
|
};
|
|
116
149
|
|
|
150
|
+
|
|
117
151
|
let fileDaCancellare = [];
|
|
118
152
|
|
|
119
153
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const cookieKeyName = `WebSession_${require('crypto').createHash('md5').update(node.host).digest('hex').substring(0, 10)}`;
|
|
124
|
-
|
|
125
|
-
node.warn(`[DEBUG SESSION] Simulazione Header -> ${cookieKeyName}=${fakeSessionToken}`);
|
|
154
|
+
const tracks = [{ id: "203" }, { id: "201" }];
|
|
155
|
+
for (let t of tracks) {
|
|
156
|
+
const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>2026-06-25T00:00:00Z</startTime><endTime>2026-06-26T23:59:59Z</endTime></timeSpan></timeSpanList><contentTypeList><contentType>metadata</contentType></contentTypeList><maxResults>30</maxResults><searchResultPostion>0</searchResultPostion><metadataList><metadataDescriptor>recordType.meta.hikvision.com/LineDetection</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
126
157
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const searchPayload = {
|
|
133
|
-
"EventSearchDescription": {
|
|
134
|
-
"searchID": dynamicSearchId,
|
|
135
|
-
"searchResultPosition": 0,
|
|
136
|
-
"maxResults": 30,
|
|
137
|
-
"timeSpanList": [{ "startTime": startTime, "endTime": endTime }],
|
|
138
|
-
"type": "all",
|
|
139
|
-
"channels": [ parseInt(channelID) ],
|
|
140
|
-
"eventType": "behavior",
|
|
141
|
-
"behavior": { "behaviorEventType": evento.toLowerCase() }
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const tempJsonPath = path.join(baseStorage, `search_${channelID}_${timestamp}.json`);
|
|
146
|
-
fs.writeFileSync(tempJsonPath, JSON.stringify(searchPayload), 'utf8');
|
|
147
|
-
|
|
148
|
-
const targetUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/eventRecordSearch?format=json`;
|
|
149
|
-
|
|
150
|
-
// Compiliamo il comando cURL inserendo gli header emersi dall'analisi di Chrome
|
|
151
|
-
// Usiamo sia il content-type form-urlencoded sia la coppia speculare cookie/sessiontag
|
|
152
|
-
const curlCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" ` +
|
|
153
|
-
`-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" ` +
|
|
154
|
-
`-H "X-Requested-With: XMLHttpRequest" ` +
|
|
155
|
-
`-H "sessiontag: ${fakeSessionToken}" ` +
|
|
156
|
-
`-b "${cookieKeyName}=${fakeSessionToken}; updatePlugin=false" ` +
|
|
157
|
-
`-d "@${tempJsonPath}" "${targetUrl}"`;
|
|
158
|
-
|
|
159
|
-
let jsonResponseRaw = await new Promise((resolve) => { exec(curlCommand, (err, stdout) => resolve(err ? null : stdout)); });
|
|
160
|
-
|
|
161
|
-
try { if (fs.existsSync(tempJsonPath)) fs.unlinkSync(tempJsonPath); } catch(e){}
|
|
162
|
-
|
|
163
|
-
if (!jsonResponseRaw) {
|
|
164
|
-
node.error("[DEBUG JSON] Nessuna risposta dall'NVR.");
|
|
165
|
-
updateNodeStatus();
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const searchResult = JSON.parse(jsonResponseRaw);
|
|
170
|
-
const matches = searchResult?.EventSearchResult?.matchList || [];
|
|
171
|
-
|
|
172
|
-
if (matches.length === 0) {
|
|
173
|
-
node.warn(`[DEBUG JSON] Zero match trovati nell'archivio protetto.`);
|
|
174
|
-
updateNodeStatus();
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Se superiamo il blocco, estraiamo i file storici
|
|
179
|
-
const matchItem = matches[0];
|
|
180
|
-
const playbackUri = matchItem?.mediaSegmentDescriptor?.playbackURI;
|
|
181
|
-
|
|
182
|
-
if (playbackUri) {
|
|
183
|
-
node.warn(`[DEBUG JSON] File individuato: ${playbackUri}`);
|
|
158
|
+
const resSearch = await camAuth.request({
|
|
159
|
+
method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
|
|
160
|
+
});
|
|
184
161
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
162
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
163
|
+
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
164
|
+
|
|
165
|
+
if (uriMatch) {
|
|
166
|
+
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
167
|
+
const resDown = await camAuth.request({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
url: `${baseUrl}/download`,
|
|
170
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
171
|
+
responseType: 'arraybuffer'
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let buffer = Buffer.from(resDown.data);
|
|
175
|
+
if (t.id === "203") {
|
|
176
|
+
// SALVA FOTO IN LOCALE
|
|
177
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
178
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
179
|
+
|
|
180
|
+
// La convertiamo subito in testo per il payload
|
|
198
181
|
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
182
|
+
|
|
183
|
+
// Registriamo il file per la distruzione futura
|
|
199
184
|
fileDaCancellare.push(fullImgPath);
|
|
185
|
+
} else {
|
|
186
|
+
// SALVA VIDEO IN LOCALE-
|
|
187
|
+
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
188
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
189
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
190
|
+
fs.writeFileSync(rawPath, buffer);
|
|
191
|
+
|
|
192
|
+
// Eseguiamo ffmpeg localmente
|
|
193
|
+
await new Promise((resolve) => {
|
|
194
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
195
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
196
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
197
|
+
fileDaCancellare.push(fixedPath);
|
|
198
|
+
} else {
|
|
199
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
200
|
+
}
|
|
201
|
+
fileDaCancellare.push(rawPath);
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
|
-
|
|
203
|
-
// --- DOWNLOAD VIDEO REGISTRATO VIA FFMPEG ---
|
|
204
|
-
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
205
|
-
const authenticatedRtspUrl = playbackUri.replace("rtsp://", `rtsp://${node.user}:${node.pass}@`);
|
|
206
|
-
const ffmpegRtspCmd = `ffmpeg -y -rtsp_transport tcp -i "${authenticatedRtspUrl}" -t 15 -c:v copy -c:a aac -movflags +faststart "${fixedPath}"`;
|
|
207
|
-
|
|
208
|
-
await new Promise((resolve) => { exec(ffmpegRtspCmd, { timeout: 25000 }, () => resolve()); });
|
|
209
|
-
|
|
210
|
-
if (fs.existsSync(fixedPath) && fs.statSync(fixedPath).size > 1000) {
|
|
211
|
-
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
212
|
-
fileDaCancellare.push(fixedPath);
|
|
213
|
-
}
|
|
214
207
|
}
|
|
215
208
|
|
|
209
|
+
// Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
|
|
216
210
|
if (output.foto_base64 || output.video_base64) {
|
|
217
211
|
node.send({ payload: output });
|
|
212
|
+
|
|
213
|
+
// TIMER A 2 MINUTI PER LA PULIZIA DEL DISCO
|
|
218
214
|
setTimeout(() => {
|
|
219
215
|
for (let file of fileDaCancellare) {
|
|
220
|
-
try {
|
|
216
|
+
try {
|
|
217
|
+
if (fs.existsSync(file)) {
|
|
218
|
+
fs.unlinkSync(file);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
|
|
222
|
+
}
|
|
221
223
|
}
|
|
222
|
-
},
|
|
224
|
+
}, 120000);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
} catch (e) {
|
|
226
|
-
node.error(`Errore
|
|
228
|
+
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
227
229
|
}
|
|
228
230
|
updateNodeStatus();
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
// ---
|
|
233
|
+
// --- ALERT STREAM ---
|
|
232
234
|
function startAlertStream() {
|
|
233
235
|
if (isClosing) return;
|
|
236
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
234
237
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
235
|
-
|
|
236
|
-
nvrAuthAxios.request({ method: 'GET', url: url, responseType: 'stream' })
|
|
238
|
+
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
237
239
|
.then(response => {
|
|
238
240
|
streamRequest = response;
|
|
239
|
-
if (!nvrOnline) {
|
|
240
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } });
|
|
241
|
-
nvrOnline = true;
|
|
242
|
-
}
|
|
241
|
+
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
243
242
|
updateNodeStatus();
|
|
244
|
-
|
|
245
243
|
response.data.on('data', (chunk) => {
|
|
246
|
-
const data = chunk.toString();
|
|
247
|
-
if (data.
|
|
248
|
-
const chMatch = data.match(/<
|
|
244
|
+
const data = chunk.toString().toLowerCase();
|
|
245
|
+
if (data.includes("active")) {
|
|
246
|
+
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
249
247
|
if (chMatch) {
|
|
250
|
-
let ev = "
|
|
251
|
-
if (data.
|
|
248
|
+
let ev = "Unknown";
|
|
249
|
+
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
252
250
|
downloadMedia(ev, chMatch[1]);
|
|
253
251
|
}
|
|
254
252
|
}
|
|
@@ -259,17 +257,13 @@ module.exports = function(RED) {
|
|
|
259
257
|
}
|
|
260
258
|
|
|
261
259
|
function handleNvrError() {
|
|
262
|
-
if (nvrOnline) {
|
|
263
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", ip: node.host, msg: "NVR Offline", nome_cliente: node.name } });
|
|
264
|
-
nvrOnline = false;
|
|
265
|
-
}
|
|
260
|
+
if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", ip: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
|
|
266
261
|
updateNodeStatus();
|
|
267
262
|
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
268
263
|
}
|
|
269
264
|
|
|
270
265
|
startAlertStream();
|
|
271
266
|
setTimeout(checkCameras, 2000);
|
|
272
|
-
|
|
273
267
|
node.on('close', (done) => {
|
|
274
268
|
isClosing = true;
|
|
275
269
|
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.
|
|
3
|
+
"version": "1.1.111",
|
|
4
4
|
"description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -22,6 +22,7 @@
|
|
|
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.1.109",
|
|
25
26
|
"xml2js": "^0.6.2"
|
|
26
27
|
}
|
|
27
28
|
}
|