node-red-contrib-hikvision-ultimate
Version:
A native set of nodes for Hikvision (and compatible) Cameras, Alarms, Radars, NVR, Doorbells, etc.
174 lines (156 loc) • 6.04 kB
JavaScript
/* Mock HTTP server for ANPR-config.js
*
* It emulates a Hikvision ANPR camera endpoint and returns
* the same XML payloads used in tools/test-anpr-picname.js.
*
* Usage:
* node tools/mock-anpr-server.js
*
* Environment variables:
* MOCK_ANPR_PORT - TCP port to listen on (default: 18080)
*/
const http = require("http");
// Simple static XML used as a fallback / sanity check
const xml1 = `<?xml version="1.0" encoding="UTF-8"?>
<Plates version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
<Plate>
<captureTime>20200424T1616270000</captureTime>
<plateNumber>DK18HVX</plateNumber>
<picName>202004241616278800</picName>
<country>GBR</country>
<laneNo>1</laneNo>
<direction>forward</direction>
</Plate>
</Plates>`;
const PORT = Number(process.env.MOCK_ANPR_PORT || 18080);
// Extract <picTime>...</picTime> from the request body
function extractPicTime(body) {
if (!body) return null;
const match = /<picTime>(\d+)<\/picTime>/i.exec(body);
return match ? match[1] : null;
}
// Generate a fake plateNumber (just to have different values)
function generatePlateNumber(index) {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const l1 = letters[index % letters.length];
const l2 = letters[(index * 7) % letters.length];
const l3 = letters[(index * 13) % letters.length];
const n = (100 + index).toString();
return `${l1}${l2}${l3}${n}`;
}
// Generate an XML with multiple Plate entries, with picName values strictly
// increasing and in RANDOM ORDER inside the XML, so ANPR-config.js must sort.
function generateMultiPlatesXml(basePicTime, count) {
try {
const len = basePicTime.length;
const base = BigInt(basePicTime);
const plates = [];
for (let i = 0; i < count; i++) {
const increment = BigInt(i + 1) * 100n; // step of 100 on the tail
let val = base + increment;
let picNameStr = val.toString();
if (picNameStr.length < len) {
picNameStr = picNameStr.padStart(len, "0");
}
const plateNumber = generatePlateNumber(i);
const datePart = basePicTime.substring(0, 8); // YYYYMMDD
const timePart = basePicTime.substring(8, 14); // HHMMSS
const captureTime = `${datePart}T${timePart}0000`;
plates.push(
` <Plate>
<captureTime>${captureTime}</captureTime>
<plateNumber>${plateNumber}</plateNumber>
<picName>${picNameStr}</picName>
<country>GBR</country>
<laneNo>1</laneNo>
<direction>forward</direction>
</Plate>`
);
}
// Shuffle plates to ensure XML order is not sorted
for (let i = plates.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = plates[i];
plates[i] = plates[j];
plates[j] = tmp;
}
return `<?xml version="1.0" encoding="UTF-8"?>
<Plates version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
${plates.join("\n")}
</Plates>`;
} catch (error) {
console.error("[mock-anpr] Error generating multi-plates XML:", error.message || error);
return xml1;
}
}
function handlePlatesRequest(req, res, body) {
// Decide which XML to return based on the requested picTime.
// ANPR-config.js sends:
// <AfterTime><picTime>...</picTime></AfterTime>
const picTime = extractPicTime(body);
// If we have a valid picTime, generate a list of plates with
// strictly increasing picName values in random order, so that
// ANPR-config.js must sort them using sortPlates.
let responseXml;
if (picTime) {
responseXml = generateMultiPlatesXml(picTime, 10);
console.log(
`[mock-anpr] Responding with MULTI-PLATES XML (10 items) for picTime ${picTime}`
);
} else {
responseXml = xml1;
console.log(
`[mock-anpr] Responding with fallback single plate XML (no picTime found in body)`
);
}
res.writeHead(200, {
"Content-Type": "application/xml; charset=utf-8"
});
res.end(responseXml);
}
const server = http.createServer((req, res) => {
if (
req.method === "POST" &&
req.url.startsWith("/ISAPI/Traffic/channels/1/vehicleDetect/plates")
) {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
// Basic protection against overly large bodies in tests
if (body.length > 1e6) {
req.socket.destroy();
}
});
req.on("end", () => {
handlePlatesRequest(req, res, body);
});
return;
}
// Fallback for other paths
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.end("mock-anpr-server: not found");
});
server.listen(PORT, () => {
console.log("============================================================");
console.log("=== MOCK ANPR SERVER AVVIATO ===");
console.log("=== 1) AVVIA NODE-RED ===");
console.log(
"=== 2) CONFIGURA IL NODO ANPR-CONFIG CON QUESTO ENDPOINT ==="
);
console.log("=== PROTOCOL: http ===");
console.log(`=== HOST: localhost ===`);
console.log(`=== PORT: ${PORT.toString().padEnd(4, " ")} ===`);
console.log(
"=== PATH: /ISAPI/Traffic/channels/1/vehicleDetect/plates ==="
);
console.log("============================================================");
console.log(
`[mock-anpr] Mock ANPR server listening on http://localhost:${PORT}`
);
console.log(
`[mock-anpr] Endpoint: POST /ISAPI/Traffic/channels/1/vehicleDetect/plates`
);
console.log(
`[mock-anpr] Use this host/port in ANPR-config (protocol=http, no auth required).`
);
});