mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 17:56:34 +00:00
Server bridge was forwarding to raw topic paths that no Node-RED node listens on. Now forwards to fixed routes: camera.event, onvif.event, onvif.motion, onvif.anpr — matching what trigger nodes register. ONVIF XML parser now extracts Key section SimpleItems (PlateNumber, etc.) into the data map alongside Data section items. Previously only parsed Source and Data, missing Key-section fields like plate numbers. Node-RED trigger nodes: camera_id filter changed from Number() to String() comparison for UUIDv7 compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
75 lines
2.8 KiB
JavaScript
75 lines
2.8 KiB
JavaScript
/**
|
|
* bf-kiosk-camera-event — fires on kiosk-originated camera events forwarded
|
|
* from the BetterFrame server (ONVIF motion, object detection, line crossing,
|
|
* GPIO pulse tagged to a camera, etc.).
|
|
*
|
|
* The server's api-http `/api/kiosk/events` endpoint persists each kiosk
|
|
* event then calls `nodered.forward(topic, payload)` which POSTs to
|
|
* `${noderedUrl}/in/<topic>`. This node self-registers a POST handler at a
|
|
* fixed route — no upstream `http in` node required.
|
|
*
|
|
* Optional config:
|
|
* - camera_id: only fire for that camera id
|
|
*
|
|
* Output msg shape (kept compatible with the previous filter version):
|
|
* { topic, kiosk_id, camera_id, source_type, payload }
|
|
*/
|
|
const { readJsonBody } = require("./_http-body.js");
|
|
|
|
module.exports = function (RED) {
|
|
// Fixed ingest route. Server-side forwarders that want this node to receive
|
|
// their event should POST to /in/camera.event. (Previous releases used a
|
|
// glob-pattern filter over upstream http-in messages; that path is gone.)
|
|
const TOPIC = "camera.event";
|
|
const ROUTE = "/api/internal/" + TOPIC;
|
|
|
|
function BfKioskCameraEventNode(config) {
|
|
RED.nodes.createNode(this, config);
|
|
const node = this;
|
|
const filterId = (config.camera_id || "").toString().trim() || null;
|
|
|
|
async function handler(req, res) {
|
|
const body = await readJsonBody(req);
|
|
const kioskId = body.kiosk_id !== undefined ? body.kiosk_id
|
|
: body.source_kiosk_id !== undefined ? body.source_kiosk_id
|
|
: null;
|
|
const cameraId = body.camera_id !== undefined ? body.camera_id
|
|
: body.source_camera_id !== undefined ? body.source_camera_id
|
|
: null;
|
|
if (filterId !== null && String(cameraId) !== filterId) {
|
|
return res.status(200).end();
|
|
}
|
|
const out = {
|
|
topic: body.topic ? String(body.topic) : TOPIC,
|
|
kiosk_id: kioskId,
|
|
camera_id: cameraId,
|
|
source_type: body.source_type || null,
|
|
payload: body.payload !== undefined ? body.payload : body,
|
|
};
|
|
node.status({ fill: "green", shape: "dot", text: out.topic });
|
|
node.send(out);
|
|
res.status(200).end();
|
|
}
|
|
|
|
RED.httpNode.post(ROUTE, handler);
|
|
|
|
node.on("close", function (done) {
|
|
const stack = RED.httpNode && RED.httpNode._router && RED.httpNode._router.stack;
|
|
if (stack) {
|
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
const layer = stack[i];
|
|
if (!layer || !layer.route || layer.route.path !== ROUTE) continue;
|
|
const inner = layer.route.stack;
|
|
if (Array.isArray(inner)) {
|
|
for (let j = inner.length - 1; j >= 0; j--) {
|
|
if (inner[j] && inner[j].handle === handler) inner.splice(j, 1);
|
|
}
|
|
if (inner.length === 0) stack.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
}
|
|
RED.nodes.registerType("bf-kiosk-camera-event", BfKioskCameraEventNode);
|
|
};
|