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.
- package/hik-media-buffer.js +34 -37
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -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
|
|
35
|
+
function toHikSearchDate(d) {
|
|
36
|
+
return d.toISOString().split('.')[0] + "Z";
|
|
37
|
+
}
|
|
37
38
|
|
|
38
|
-
// --- PRENDE IL NOME REALE DELLA TELECAMERA
|
|
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/
|
|
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
|
-
//
|
|
51
|
-
const match = data.match(/<
|
|
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 (
|
|
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/
|
|
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
|
-
|
|
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
|
|
77
|
-
const onlineMatch = 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
|
-
|
|
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
|
|
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
|
|
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
|
|
133
|
-
const
|
|
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":
|
|
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>${
|
|
203
|
-
<endTime>${
|
|
202
|
+
<startTime>${startSearchStr}</startTime>
|
|
203
|
+
<endTime>${endSearchStr}</endTime>
|
|
204
204
|
</timeSpan>
|
|
205
205
|
</timeSpanList>
|
|
206
|
-
<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
|
}
|