2026-05-12 23:47:53 +00:00
|
|
|
/**
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
* bf-kiosk-camera-event — fire a flow whenever a BetterFrame kiosk camera
|
|
|
|
|
* event matching a topic pattern arrives. Defaults to `camera.*` (ONVIF
|
|
|
|
|
* motion, object detection, line crossing, etc.).
|
2026-05-12 23:47:53 +00:00
|
|
|
*
|
|
|
|
|
* Two delivery paths can land here:
|
|
|
|
|
* 1. The BF server has forwarded an authenticated kiosk event via the
|
|
|
|
|
* `/in/kiosk/<topic>` ingest endpoint. The flow operator wires an
|
|
|
|
|
* `http in` node on that path and connects it to this node — we just
|
|
|
|
|
* filter by topic.
|
|
|
|
|
* 2. A separate flow injects msg.topic + msg.payload directly.
|
|
|
|
|
*
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
* This is a pure filter/router. It does NOT itself subscribe to the BF
|
|
|
|
|
* server; that wiring is done with stock Node-RED http-in or websocket
|
|
|
|
|
* nodes upstream.
|
|
|
|
|
*
|
|
|
|
|
* Renamed from `bf-event-in` — kept the same envelope shape for backward
|
|
|
|
|
* compatibility with flows that consume the output message.
|
2026-05-12 23:47:53 +00:00
|
|
|
*/
|
|
|
|
|
module.exports = function (RED) {
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
function BfKioskCameraEventNode(config) {
|
2026-05-12 23:47:53 +00:00
|
|
|
RED.nodes.createNode(this, config);
|
|
|
|
|
const node = this;
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
const pattern = (config.topic_pattern || "camera.*").trim();
|
2026-05-12 23:47:53 +00:00
|
|
|
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
// Convert glob-ish pattern to RegExp: `camera.*` → /^camera\..*$/
|
2026-05-12 23:47:53 +00:00
|
|
|
function toRegex(p) {
|
|
|
|
|
if (!p) return null;
|
|
|
|
|
const escaped = p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
|
|
|
return new RegExp("^" + escaped + "$");
|
|
|
|
|
}
|
|
|
|
|
const re = toRegex(pattern);
|
|
|
|
|
|
|
|
|
|
node.on("input", function (msg, send, done) {
|
|
|
|
|
// Common BF envelope shape:
|
|
|
|
|
// { topic, kiosk_id, camera_id, source_type, payload }
|
|
|
|
|
// We accept either a fully-formed msg or one where the body lives in
|
|
|
|
|
// msg.payload (typical for Node-RED http-in).
|
|
|
|
|
const body = (msg && msg.payload && typeof msg.payload === "object") ? msg.payload : {};
|
|
|
|
|
const topic = msg.topic || body.topic || "";
|
|
|
|
|
if (!topic) {
|
|
|
|
|
node.status({ fill: "yellow", shape: "ring", text: "no topic" });
|
|
|
|
|
return done && done();
|
|
|
|
|
}
|
|
|
|
|
if (re && !re.test(String(topic))) {
|
|
|
|
|
// Filter miss — drop silently.
|
|
|
|
|
return done && done();
|
|
|
|
|
}
|
|
|
|
|
const out = {
|
|
|
|
|
topic: String(topic),
|
|
|
|
|
kiosk_id: msg.kiosk_id || body.kiosk_id || body.source_kiosk_id || null,
|
|
|
|
|
camera_id: msg.camera_id || body.camera_id || body.source_camera_id || null,
|
|
|
|
|
source_type: body.source_type || null,
|
|
|
|
|
payload: body.payload !== undefined ? body.payload : body,
|
|
|
|
|
};
|
|
|
|
|
node.status({ fill: "green", shape: "dot", text: out.topic });
|
|
|
|
|
send(out);
|
|
|
|
|
done && done();
|
|
|
|
|
});
|
|
|
|
|
}
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
RED.nodes.registerType("bf-kiosk-camera-event", BfKioskCameraEventNode);
|
2026-05-12 23:47:53 +00:00
|
|
|
};
|