Files
nr-flow-validator/flows/zigbee-monitor.yaml
T
david f95e8b8ecb Flat-structure generator + Rego guards, rename Laser & Fog to DJ Booth
Closes #1.

Generator (scripts/flow2json.py):
- Emit all nodes at top level with z field (no wrapped tabs)
- Set icon: "" on ui-button to avoid Dashboard 2.0 prependIcon TypeError
- Set z field on ui-* widgets so they render in Dashboard 2.0 SPA
- Use ui-text label from spec, not name

Policy (policy/flow.rego) — 9 new deny rules:
- deny_wrapped_tab: tab with internal nodes[] (nodered 5.0 bug)
- deny_missing_wires: missing or non-array wires (Flow.js rewireNode crash)
- deny_no_autoconnect: mqtt-broker must reconnect
- deny_invalid_protocol: protocolVersion must be 3/4/5
- deny_unused_broker: broker declared but never referenced
- Plus deny_broker_port / deny_group_ref / deny_page_ref / deny_wire_ref refinements

Flow (zigbee-monitor.*):
- 22-node flat flow: button-in → btn-filter → 4 parallel plug-outs
- 4 ui-text widgets + ui-button for permit-join (60s)
- Renamed plug: "Laser & Fog" → "DJ Booth" (matches z2m friendly_name)

Repo hygiene:
- justfile: fix host IP 192.168.9.147 → 192.168.81.147, /ui → /dashboard
- README.md: document 8 generation invariants and rule table

Verified end-to-end: publishing {"action":"single"} on
zigbee2mqtt/Door Button triggers {"state":"TOGGLE"} on all four
.../set topics including the renamed DJ Booth.

462/462 conftest tests pass.
2026-06-30 23:16:17 -07:00

141 lines
3.1 KiB
YAML

version: 1
brokers:
mosquitto:
host: mosquitto
port: 1883
plugs:
- Squiggle
- Sideboard Lamp
- Bamboo Lights
- DJ Booth
dashboards:
- base: Dashboard
pages:
- page: Home
icon: home
path: /home
groups:
- name: Plugs
width: 6
flows:
- tab: Zigbee Monitor
description: Single button toggles all plugs
nodes:
- id: zigbee-state-squiggle
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Squiggle
qos: 0
- id: zigbee-state-sideboard
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Sideboard Lamp
qos: 0
- id: zigbee-state-bamboo
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Bamboo Lights
qos: 0
- id: zigbee-state-laser
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/DJ Booth
qos: 0
- id: button-in
type: mqtt in
broker: mosquitto
topic: zigbee2mqtt/Door Button
qos: 0
- id: btn-filter
type: function
name: Single press
code: |
try {
const p = JSON.parse(msg.payload);
if (p.action === 'single') {
msg.payload = { state: 'TOGGLE' };
return msg;
}
} catch (e) {}
return null;
- id: plug-out-squiggle
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Squiggle/set
- id: plug-out-sideboard
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Sideboard Lamp/set
- id: plug-out-bamboo
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/Bamboo Lights/set
- id: plug-out-laser
type: mqtt out
broker: mosquitto
topic: zigbee2mqtt/DJ Booth/set
- id: text-squiggle
type: ui-text
group: Plugs
label: Squiggle
format: "{{msg.payload.state}}"
order: 1
- id: text-sideboard
type: ui-text
group: Plugs
label: Sideboard Lamp
format: "{{msg.payload.state}}"
order: 2
- id: text-bamboo
type: ui-text
group: Plugs
label: Bamboo Lights
format: "{{msg.payload.state}}"
order: 3
- id: text-laser
type: ui-text
group: Plugs
label: DJ Booth
format: "{{msg.payload.state}}"
order: 4
- id: permit-btn
type: ui-button
group: Plugs
label: Permit Join (60s)
topic: zigbee2mqtt/bridge/request/permit_join
payload:
value: true
time: 60
order: 5
- id: mqtt-out
type: mqtt out
broker: mosquitto
wires:
zigbee-state-squiggle: [text-squiggle]
zigbee-state-sideboard: [text-sideboard]
zigbee-state-bamboo: [text-bamboo]
zigbee-state-laser: [text-laser]
button-in: [btn-filter]
btn-filter: [plug-out-squiggle, plug-out-sideboard, plug-out-bamboo, plug-out-laser]
permit-btn: [mqtt-out]