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.
- package/hik-media-buffer.js +24 -27
- 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,18 +114,16 @@ module.exports = function(RED) {
|
|
|
112
114
|
|
|
113
115
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
114
116
|
|
|
115
|
-
// ---
|
|
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. 📸
|
|
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,
|
|
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. 🎥
|
|
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>
|