Files
nr-flow-validator/output/zigbee-monitor.json
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

354 lines
6.8 KiB
JSON

[
{
"id": "braff51339",
"type": "mqtt-broker",
"name": "mosquitto",
"broker": "mosquitto",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"willTopic": "",
"willQos": "0",
"willPayload": ""
},
{
"id": "uibab06e3f4",
"type": "ui-base",
"name": "Dashboard",
"path": "/dashboard",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"navigationStyle": "default",
"titleBarStyle": "default"
},
{
"id": "uit4a70230a",
"type": "ui-theme",
"name": "Default Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094CE",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
},
{
"id": "uip2851213f",
"type": "ui-page",
"name": "Home",
"ui": "uibab06e3f4",
"icon": "home",
"path": "/home",
"theme": "uit4a70230a",
"layout": "flex",
"order": 0,
"className": "",
"visible": true,
"disabled": false
},
{
"id": "uig93b85217",
"type": "ui-group",
"name": "Plugs",
"page": "uip2851213f",
"width": "6",
"height": "1",
"order": 1,
"showTitle": true,
"className": "",
"visible": true,
"disabled": false
},
{
"id": "fd235da06",
"type": "tab",
"label": "Zigbee Monitor",
"disabled": false,
"info": "Single button toggles all plugs",
"env": []
},
{
"id": "zigbee-state-squiggle",
"type": "mqtt in",
"wires": [
[
"text-squiggle"
]
],
"name": "",
"x": 170,
"y": 80,
"broker": "braff51339",
"topic": "zigbee2mqtt/Squiggle",
"qos": "0",
"datatype": "auto",
"z": "fd235da06"
},
{
"id": "zigbee-state-sideboard",
"type": "mqtt in",
"wires": [
[
"text-sideboard"
]
],
"name": "",
"x": 170,
"y": 80,
"broker": "braff51339",
"topic": "zigbee2mqtt/Sideboard Lamp",
"qos": "0",
"datatype": "auto",
"z": "fd235da06"
},
{
"id": "zigbee-state-bamboo",
"type": "mqtt in",
"wires": [
[
"text-bamboo"
]
],
"name": "",
"x": 170,
"y": 80,
"broker": "braff51339",
"topic": "zigbee2mqtt/Bamboo Lights",
"qos": "0",
"datatype": "auto",
"z": "fd235da06"
},
{
"id": "zigbee-state-laser",
"type": "mqtt in",
"wires": [
[
"text-laser"
]
],
"name": "",
"x": 170,
"y": 220,
"broker": "braff51339",
"topic": "zigbee2mqtt/DJ Booth",
"qos": "0",
"datatype": "auto",
"z": "fd235da06"
},
{
"id": "button-in",
"type": "mqtt in",
"wires": [
[
"btn-filter"
]
],
"name": "",
"x": 170,
"y": 220,
"broker": "braff51339",
"topic": "zigbee2mqtt/Door Button",
"qos": "0",
"datatype": "auto",
"z": "fd235da06"
},
{
"id": "btn-filter",
"type": "function",
"wires": [
[
"plug-out-squiggle",
"plug-out-sideboard",
"plug-out-bamboo",
"plug-out-laser"
]
],
"name": "Single press",
"x": 450,
"y": 220,
"func": "try {\n const p = JSON.parse(msg.payload);\n if (p.action === 'single') {\n msg.payload = { state: 'TOGGLE' };\n return msg;\n }\n} catch (e) {}\nreturn null;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"z": "fd235da06"
},
{
"id": "plug-out-squiggle",
"type": "mqtt out",
"wires": [],
"name": "",
"x": 770,
"y": 360,
"broker": "braff51339",
"topic": "zigbee2mqtt/Squiggle/set",
"qos": "2",
"retain": false,
"z": "fd235da06"
},
{
"id": "plug-out-sideboard",
"type": "mqtt out",
"wires": [],
"name": "",
"x": 770,
"y": 360,
"broker": "braff51339",
"topic": "zigbee2mqtt/Sideboard Lamp/set",
"qos": "2",
"retain": false,
"z": "fd235da06"
},
{
"id": "plug-out-bamboo",
"type": "mqtt out",
"wires": [],
"name": "",
"x": 770,
"y": 360,
"broker": "braff51339",
"topic": "zigbee2mqtt/Bamboo Lights/set",
"qos": "2",
"retain": false,
"z": "fd235da06"
},
{
"id": "plug-out-laser",
"type": "mqtt out",
"wires": [],
"name": "",
"x": 770,
"y": 500,
"broker": "braff51339",
"topic": "zigbee2mqtt/DJ Booth/set",
"qos": "2",
"retain": false,
"z": "fd235da06"
},
{
"id": "text-squiggle",
"type": "ui-text",
"wires": [],
"name": "Squiggle",
"x": 770,
"y": 500,
"group": "uig93b85217",
"width": "6",
"height": "2",
"order": 1,
"label": "Squiggle",
"format": "{{msg.payload.state}}",
"layout": "row-spread",
"className": "",
"z": "fd235da06"
},
{
"id": "text-sideboard",
"type": "ui-text",
"wires": [],
"name": "Sideboard Lamp",
"x": 770,
"y": 500,
"group": "uig93b85217",
"width": "6",
"height": "2",
"order": 2,
"label": "Sideboard Lamp",
"format": "{{msg.payload.state}}",
"layout": "row-spread",
"className": "",
"z": "fd235da06"
},
{
"id": "text-bamboo",
"type": "ui-text",
"wires": [],
"name": "Bamboo Lights",
"x": 770,
"y": 640,
"group": "uig93b85217",
"width": "6",
"height": "2",
"order": 3,
"label": "Bamboo Lights",
"format": "{{msg.payload.state}}",
"layout": "row-spread",
"className": "",
"z": "fd235da06"
},
{
"id": "text-laser",
"type": "ui-text",
"wires": [],
"name": "DJ Booth",
"x": 770,
"y": 640,
"group": "uig93b85217",
"width": "6",
"height": "2",
"order": 4,
"label": "DJ Booth",
"format": "{{msg.payload.state}}",
"layout": "row-spread",
"className": "",
"z": "fd235da06"
},
{
"id": "permit-btn",
"type": "ui-button",
"wires": [
[
"mqtt-out"
]
],
"name": "Permit Join (60s)",
"x": 170,
"y": 640,
"group": "uig93b85217",
"payload": "{\"value\": true, \"time\": 60}",
"payloadType": "json",
"topic": "zigbee2mqtt/bridge/request/permit_join",
"width": "3",
"height": "1",
"order": 5,
"className": "",
"z": "fd235da06",
"icon": "",
"label": "Permit Join (60s)"
},
{
"id": "mqtt-out",
"type": "mqtt out",
"wires": [],
"name": "",
"x": 770,
"y": 780,
"broker": "braff51339",
"topic": "",
"qos": "2",
"retain": false,
"z": "fd235da06"
}
]