2026-05-12 23:47:53 +00:00
|
|
|
# @betterframe/nodered-nodes
|
|
|
|
|
|
|
|
|
|
BetterFrame integration nodes for Node-RED. Drag-and-droppable nodes for the
|
|
|
|
|
BetterFrame admin REST API and kiosk event ingest.
|
|
|
|
|
|
|
|
|
|
## Nodes
|
|
|
|
|
|
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
|
|
|
| Node | Category | Purpose |
|
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-server-config` | config | Shared server URL + admin API key |
|
|
|
|
|
| `bf-kiosk-camera-event` | Triggers | Filter incoming kiosk camera events (default `camera.*`) |
|
|
|
|
|
| `bf-trigger-display-power` | Triggers | Fires on `display.power.changed` |
|
|
|
|
|
| `bf-trigger-layout-changed` | Triggers | Fires on `layout.changed` |
|
|
|
|
|
| `bf-trigger-kiosk-changed` | Triggers | Fires on `kiosk.changed` (connect/disconnect/heartbeat) |
|
|
|
|
|
| `bf-trigger-camera-changed` | Triggers | Fires on `camera.changed` (created/updated/deleted) |
|
2026-05-13 00:29:12 +00:00
|
|
|
| `bf-trigger-status` | Triggers | Fires on `kiosk.status` (heartbeat-only telemetry; optional kiosk_id filter) |
|
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-layout-switch` | BetterFrame | Switch a display's active layout |
|
|
|
|
|
| `bf-power` | BetterFrame | Wake / standby a kiosk display |
|
|
|
|
|
| `bf-fan` | BetterFrame | Set fan mode (auto/pwm) on a kiosk |
|
|
|
|
|
| `bf-cameras` | BetterFrame | Fetch the camera list |
|
|
|
|
|
| `bf-config-get` | BetterFrame | Fetch BF state (displays/kiosks/cameras/layouts/entities, by id or full list) |
|
|
|
|
|
| `bf-config-set` | BetterFrame | Mutate BF state (default layout, enabled, priority, name) |
|
2026-05-13 00:29:12 +00:00
|
|
|
| `bf-status` | BetterFrame | Fetch current kiosk state by ID (telemetry, last_seen_at, etc.) |
|
|
|
|
|
| `bf-snapshot` | BetterFrame | Fetch a JPEG snapshot for a camera entity (binary Buffer payload) |
|
2026-05-12 23:47:53 +00:00
|
|
|
|
|
|
|
|
## Authentication
|
|
|
|
|
|
|
|
|
|
All action/query nodes use an **admin-scoped API key** created in the
|
|
|
|
|
BetterFrame admin UI. The key is sent as `Authorization: Bearer bf-...`.
|
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
|
|
|
Configure once on a `bf-server-config` node and reference it from the others.
|
2026-05-12 23:47:53 +00:00
|
|
|
|
|
|
|
|
## Event ingest path
|
|
|
|
|
|
2026-05-13 00:42:37 +00:00
|
|
|
Trigger nodes are **self-contained** — each one registers its own
|
|
|
|
|
`POST /in/<topic>` handler on Node-RED's user-facing HTTP server (via
|
|
|
|
|
`RED.httpNode.post`) when the flow is deployed. You do **not** need to wire
|
|
|
|
|
an upstream `http in` node anymore.
|
|
|
|
|
|
|
|
|
|
The BetterFrame server's `nodered-bridge.forward(topic, payload)` posts
|
|
|
|
|
events directly to `http://<nodered-host>:1880/in/<topic>`. Each trigger
|
|
|
|
|
node listens on its own fixed topic:
|
|
|
|
|
|
|
|
|
|
| Node | Internal route |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| `bf-trigger-display-power` | `POST /in/display.power.changed` |
|
|
|
|
|
| `bf-trigger-layout-changed` | `POST /in/layout.changed` |
|
|
|
|
|
| `bf-trigger-kiosk-changed` | `POST /in/kiosk.changed` |
|
|
|
|
|
| `bf-trigger-camera-changed` | `POST /in/camera.changed` |
|
|
|
|
|
| `bf-trigger-status` | `POST /in/kiosk.status` |
|
|
|
|
|
| `bf-kiosk-camera-event` | `POST /in/camera.event` |
|
|
|
|
|
|
|
|
|
|
The server emits these topics from coordinator-ws (kiosk WS lifecycle) and
|
|
|
|
|
the admin routes (layout/power/camera mutations). Multiple instances of the
|
|
|
|
|
same trigger node on the same canvas are fine — Express runs all matching
|
|
|
|
|
handlers in registration order.
|
|
|
|
|
|
|
|
|
|
If the Angie proxy fronts Node-RED, the otherwise-unmatched root paths
|
|
|
|
|
(public HTTP-in URLs) and the `/in/kiosk/<topic>` (kiosk-key gated) /
|
|
|
|
|
`/in/public/<topic>` (public, rate-limited) routes are still available
|
|
|
|
|
for user-authored flows that use stock `http in` nodes — those layers
|
|
|
|
|
strip the prefix before proxying. The trigger nodes' fixed
|
|
|
|
|
`/in/<topic>` routes are reserved for internal server-to-Node-RED
|
|
|
|
|
delivery and are not exposed through the proxy's gated surfaces by
|
|
|
|
|
default.
|
|
|
|
|
|
|
|
|
|
Each trigger node also offers an optional ID filter (display_id / kiosk_id /
|
|
|
|
|
camera_id) so you can drop one node per entity without a downstream switch.
|
2026-05-12 23:47:53 +00:00
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
### Dev (single-host BetterFrame install)
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
# Symlink the package into Node-RED's user dir so edits hot-reload.
|
|
|
|
|
ln -s "$(pwd)/nodered" ~/.node-red/node_modules/@betterframe/nodered-nodes
|
|
|
|
|
# Restart Node-RED.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Docker compose
|
|
|
|
|
|
|
|
|
|
The compose stack mounts `nodered-data` as `/data`. Either:
|
|
|
|
|
|
|
|
|
|
- bake the package into the Node-RED image by extending the Dockerfile with
|
|
|
|
|
`npm install /repo/nodered`, or
|
|
|
|
|
- mount `./nodered` into `/data/node_modules/@betterframe/nodered-nodes` and
|
|
|
|
|
restart the container.
|