node-red-contrib-dmx-for-ha 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/docs/config_node_spec.md +236 -0
- package/docs/dmx_node_env_reference.md +341 -0
- package/docs/master_todo.md +428 -0
- package/docs/node_contracts.md +278 -0
- package/docs/nr_subflow_gotchas.md +258 -0
- package/nodes/ha-mqtt-button.html +326 -0
- package/nodes/ha-mqtt-button.js +158 -0
- package/nodes/ha-mqtt-config.html +233 -0
- package/nodes/ha-mqtt-config.js +81 -0
- package/nodes/ha-mqtt-dmx-group.html +392 -0
- package/nodes/ha-mqtt-dmx-group.js +265 -0
- package/nodes/ha-mqtt-dmx.html +547 -0
- package/nodes/ha-mqtt-dmx.js +537 -0
- package/nodes/ha-mqtt-pir.html +343 -0
- package/nodes/ha-mqtt-pir.js +183 -0
- package/nodes/ha-mqtt-relay.html +326 -0
- package/nodes/ha-mqtt-relay.js +289 -0
- package/package.json +39 -0
- package/subflow/README.md +35 -0
- package/subflow/button_node_v5.0.3.js +324 -0
- package/subflow/dmx_group_node_v0.3.8.js +860 -0
- package/subflow/dmx_node_v0.5.9.js +1994 -0
- package/subflow/pir_node_v1.0.3.js +365 -0
- package/subflow/relay_node_v4.0.2.js +553 -0
- package/subflow/subflow_definitions.json +6154 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Node Input / Output Contracts
|
|
2
|
+
**Status:** Pre-packaging specification
|
|
3
|
+
**Purpose:** Blueprint for .html editor files and node .js runtime code
|
|
4
|
+
**Principle:** Simple by default. Zero outputs except where structurally required.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Package Name (proposed)
|
|
9
|
+
`node-red-contrib-dmx-for-ha`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Shared Input Contract (all nodes)
|
|
14
|
+
|
|
15
|
+
Every node accepts the following on its single input port:
|
|
16
|
+
|
|
17
|
+
| Message type | Shape | Source | Action |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Device add | `msg.device = "add"` | SYSTEM node inject | Run MQTT discovery, subscribe to HA cmd topic |
|
|
20
|
+
| Device remove | `msg.device = "remove"` | SYSTEM node inject | Clear HA entities, unsubscribe, clear memory |
|
|
21
|
+
| HA command | `msg.payload.state = "ON"/"OFF"` | MQTT In (internal) | Process state change |
|
|
22
|
+
|
|
23
|
+
The MQTT broker connection, subscribe loop, and all publish operations are
|
|
24
|
+
handled **internally** — no external MQTT In or MQTT Out nodes required.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. DMX Group Node (v0.3.8)
|
|
29
|
+
|
|
30
|
+
### Purpose
|
|
31
|
+
Virtual fixture group. Receives a single HA command and fans it out to all
|
|
32
|
+
downstream nodes. No physical hardware — exists only in Node-RED.
|
|
33
|
+
|
|
34
|
+
### Input port (×1)
|
|
35
|
+
| Message | Shape | Source |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Device add/remove | `msg.device = "add"/"remove"` | SYSTEM node |
|
|
38
|
+
| HA command | `msg.payload.state`, optional `brightness`, `color`, `transition`, `effect` | Internal MQTT (HA dashboard) |
|
|
39
|
+
| Cascade (upstream) | `msg.dmx_trace = {source, path, depth}` + `msg.payload` | Upstream Group Node cascade output |
|
|
40
|
+
|
|
41
|
+
### Output port (×1) — Link
|
|
42
|
+
Fans the HA command payload downstream to child nodes.
|
|
43
|
+
Wire to: DMX Node input, Relay Node input, or another Group Node input.
|
|
44
|
+
|
|
45
|
+
Output message shape:
|
|
46
|
+
```javascript
|
|
47
|
+
{
|
|
48
|
+
dmx_trace: {
|
|
49
|
+
source: "LG-992", // this group's ID
|
|
50
|
+
path: ["LG-992"], // breadcrumb trail for loop detection
|
|
51
|
+
depth: 1 // increments at each Group Node hop
|
|
52
|
+
},
|
|
53
|
+
payload: { // original HA command, passed through unchanged
|
|
54
|
+
state: "ON",
|
|
55
|
+
brightness: 255,
|
|
56
|
+
color: { r:255, g:128, b:0, w:255 },
|
|
57
|
+
transition: 1.5,
|
|
58
|
+
effect: "rainbow" // if applicable
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Internal MQTT behaviour
|
|
64
|
+
- Subscribes to: `homeassistant/light/LG-{id}/cmd`
|
|
65
|
+
- Publishes discovery to: `homeassistant/light/LG-{id}/config`
|
|
66
|
+
- Publishes state to: `homeassistant/light/LG-{id}/state`
|
|
67
|
+
|
|
68
|
+
### Loop detection
|
|
69
|
+
If `msg.dmx_trace.path` already contains this node's ID, the message is
|
|
70
|
+
dropped and a `node.warn` fires. Prevents infinite loops in circular wiring.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. DMX Node (v0.5.9)
|
|
75
|
+
|
|
76
|
+
### Purpose
|
|
77
|
+
Terminal fixture node. Receives commands from HA or a Group Node cascade,
|
|
78
|
+
translates to DMX channel values and publishes via MQTT to the DMX controller.
|
|
79
|
+
|
|
80
|
+
### Input port (×1)
|
|
81
|
+
| Message | Shape | Source |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Device add/remove | `msg.device = "add"/"remove"` | SYSTEM node |
|
|
84
|
+
| HA command | `msg.payload.state`, optional `brightness`, `color`, `transition`, `effect` | Internal MQTT (HA dashboard) |
|
|
85
|
+
| Cascade | `msg.dmx_trace` + `msg.payload` | Group Node cascade output |
|
|
86
|
+
|
|
87
|
+
### Output ports
|
|
88
|
+
**None.** Terminal node — DMX is the end of the signal chain.
|
|
89
|
+
|
|
90
|
+
### Internal MQTT behaviour
|
|
91
|
+
- Subscribes to: `homeassistant/light/{prefix}-{id}{postfix}/cmd`
|
|
92
|
+
- Publishes discovery to: `homeassistant/light/{prefix}-{id}{postfix}/config`
|
|
93
|
+
- Publishes state to: `homeassistant/light/{prefix}-{id}{postfix}/state`
|
|
94
|
+
- Publishes DMX to: `MW3D/{zone}/dmx/{universe}`
|
|
95
|
+
payload format: `"{channel3digits}{value3digits}"` e.g. `"212255"`
|
|
96
|
+
|
|
97
|
+
### Supported colour modes
|
|
98
|
+
`rgbw` `rgbww` `rgb` `cct` `brightness` `onoff`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. Relay Node (v4.0.2)
|
|
103
|
+
|
|
104
|
+
### Purpose
|
|
105
|
+
Terminal 230V relay node. Receives ON/OFF commands from HA or a Group Node
|
|
106
|
+
cascade, publishes integer relay commands via MQTT to the relay controller.
|
|
107
|
+
|
|
108
|
+
**Not related to DMX.** Entirely separate control path and hardware.
|
|
109
|
+
|
|
110
|
+
### Input port (×1)
|
|
111
|
+
| Message | Shape | Source |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| Device add/remove | `msg.device = "add"/"remove"` | SYSTEM node |
|
|
114
|
+
| HA command | `msg.payload.state = "ON"/"OFF"`, optional `effect` | Internal MQTT (HA dashboard) |
|
|
115
|
+
| Cascade | `msg.dmx_trace` + `msg.payload` | Group Node cascade output |
|
|
116
|
+
|
|
117
|
+
### Output ports
|
|
118
|
+
**None.** Terminal node.
|
|
119
|
+
|
|
120
|
+
### Internal MQTT behaviour
|
|
121
|
+
- Subscribes to: `homeassistant/light/{prefix}-{id}{postfix}/cmd`
|
|
122
|
+
- Publishes discovery to: `homeassistant/light/{prefix}-{id}{postfix}/config`
|
|
123
|
+
- Publishes state to: `homeassistant/light/{prefix}-{id}{postfix}/state`
|
|
124
|
+
- Publishes relay command to: `MW3D/{zone}/relay/{controller}/{relayNumber}`
|
|
125
|
+
payload: integer `1` (ON) or `0` (OFF)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 4. Button Node (v5.0.3)
|
|
130
|
+
|
|
131
|
+
### Purpose
|
|
132
|
+
Wall button receiver. Listens for hardware controller MQTT payloads and
|
|
133
|
+
publishes to HA as a `binary_sensor` (for automations) and a `button`
|
|
134
|
+
entity (for dashboard UI mirror). HA automation logic handles everything
|
|
135
|
+
downstream — no NR output required.
|
|
136
|
+
|
|
137
|
+
### Input port (×1)
|
|
138
|
+
| Message | Shape | Source |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| Device add/remove | `msg.device = "add"/"remove"` | SYSTEM node |
|
|
141
|
+
| Physical press | `msg.payload = "{panelId}-{GPIOpin}"` plain string | Hardware controller via internal MQTT |
|
|
142
|
+
| UI press | `msg.topic = HA button cmd topic` + `msg.payload = "PRESS"` | HA dashboard via internal MQTT |
|
|
143
|
+
|
|
144
|
+
### Output ports
|
|
145
|
+
**None.** HA receives the event via `binary_sensor` state topic.
|
|
146
|
+
Automation logic lives in HA — not in Node-RED.
|
|
147
|
+
|
|
148
|
+
### Internal MQTT behaviour
|
|
149
|
+
- Subscribes to: `{controllerTopic}` (e.g. `buttons`) — plain string payloads
|
|
150
|
+
- Subscribes to: `homeassistant/button/{prefix}-{id}{postfix}-BTN/cmd`
|
|
151
|
+
- Publishes binary_sensor discovery to: `homeassistant/binary_sensor/{prefix}-{id}{postfix}/config`
|
|
152
|
+
- Publishes button discovery to: `homeassistant/button/{prefix}-{id}{postfix}-BTN/config`
|
|
153
|
+
- Publishes binary_sensor state to: `homeassistant/binary_sensor/{prefix}-{id}{postfix}/state`
|
|
154
|
+
payload: `"ON"` on press, HA `off_delay` handles auto-clear
|
|
155
|
+
|
|
156
|
+
### Dual entity design
|
|
157
|
+
Two HA entities per button:
|
|
158
|
+
- `binary_sensor.s_10_a` — automation trigger, state changes ON then auto-clears
|
|
159
|
+
- `button.s_10_a_btn` — dashboard UI mirror, press from HA frontend triggers same code path
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 5. PIR Node (v1.0.3)
|
|
164
|
+
|
|
165
|
+
### Purpose
|
|
166
|
+
PIR / motion sensor receiver. Listens for hardware controller MQTT payloads
|
|
167
|
+
and publishes to HA as a `binary_sensor` with `device_class: motion`.
|
|
168
|
+
Includes configurable warm-up delay on boot to prevent false automation
|
|
169
|
+
triggers during system startup.
|
|
170
|
+
|
|
171
|
+
### Input port (×1)
|
|
172
|
+
| Message | Shape | Source |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| Device add | `msg.device = "add"` | SYSTEM node |
|
|
175
|
+
| Device remove | `msg.device = "remove"` | SYSTEM node |
|
|
176
|
+
| Availability toggle | `msg.device = "avty"` + `msg.payload = "online"/"offline"` | SYSTEM node or admin |
|
|
177
|
+
| Motion detected | `msg.payload = "{panelId}-{GPIOpin}"` plain string | Hardware controller via internal MQTT |
|
|
178
|
+
|
|
179
|
+
### Output ports
|
|
180
|
+
**None.** HA receives motion events via `binary_sensor` state topic.
|
|
181
|
+
Automation logic lives in HA — not in Node-RED.
|
|
182
|
+
|
|
183
|
+
### Internal MQTT behaviour
|
|
184
|
+
- Subscribes to: `{controllerTopic}` (e.g. `MW3D/Master/PIR_Sensors`) — plain string
|
|
185
|
+
- Publishes discovery to: `homeassistant/binary_sensor/{prefix}-{id}{postfix}/config`
|
|
186
|
+
- Publishes state to: `homeassistant/binary_sensor/{prefix}-{id}{postfix}/state`
|
|
187
|
+
payload: `"ON"` on motion, HA `off_delay` handles auto-clear
|
|
188
|
+
- Publishes availability to: `homeassistant/binary_sensor/{prefix}-{id}{postfix}/avty`
|
|
189
|
+
payload: `"online"` / `"offline"`
|
|
190
|
+
|
|
191
|
+
### Warm-up sequence
|
|
192
|
+
On `device:add`:
|
|
193
|
+
1. Immediately publishes `offline` to availability topic
|
|
194
|
+
2. Starts configurable warm-up timer (default 120s)
|
|
195
|
+
3. After timer: publishes `online` — PIR is now active
|
|
196
|
+
4. Prevents false motion triggers during NR startup cascade
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Output Label — Group Node Cascade
|
|
201
|
+
|
|
202
|
+
The single output on the Group Node needs a label that is:
|
|
203
|
+
- Self-explanatory on first read
|
|
204
|
+
- Not too technical
|
|
205
|
+
- Accurate to what gets wired to it
|
|
206
|
+
|
|
207
|
+
**Candidates:**
|
|
208
|
+
| Label | Notes |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `Cascade` | Accurate but slightly technical |
|
|
211
|
+
| `Downstream` | Clear directional meaning |
|
|
212
|
+
| `Link` | Simple, generic |
|
|
213
|
+
| `To fixtures` | Descriptive but assumes only fixtures downstream |
|
|
214
|
+
| `Output` | Generic but honest |
|
|
215
|
+
|
|
216
|
+
**Output label: `Link`** ✓ Locked in.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Canvas Topology (reference)
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
SYSTEM node
|
|
224
|
+
│ msg.device = "add"
|
|
225
|
+
├──────────────────────────────────────────────────────┐
|
|
226
|
+
│ │
|
|
227
|
+
▼ ▼
|
|
228
|
+
[DMX Group Node LG-992] [Button Node S-10-A]
|
|
229
|
+
│ cascade output (Link) (no output — HA handles automation)
|
|
230
|
+
├──────────────┬──────────────┐
|
|
231
|
+
▼ ▼ ▼
|
|
232
|
+
[DMX Node [DMX Node [Relay Node
|
|
233
|
+
L-992-A] L-992-B] P-51]
|
|
234
|
+
(no output) (no output) (no output)
|
|
235
|
+
|
|
236
|
+
▼ ▼ ▼
|
|
237
|
+
MQTT MQTT MQTT
|
|
238
|
+
DMX ctrl DMX ctrl Relay ctrl
|
|
239
|
+
|
|
240
|
+
└──────────────┴──────────────┘
|
|
241
|
+
│
|
|
242
|
+
▼
|
|
243
|
+
MQTT Broker
|
|
244
|
+
│
|
|
245
|
+
▼
|
|
246
|
+
Home Assistant
|
|
247
|
+
(all automation logic)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Pre-packaging Checklist
|
|
253
|
+
|
|
254
|
+
- [ ] Agree on `Link` vs alternative for cascade output label
|
|
255
|
+
- [x] Package name: `node-red-contrib-dmx-for-ha`
|
|
256
|
+
- [x] Palette category: `DMX for HA`
|
|
257
|
+
- [x] Display names: DMX, DMX Group, Relay, Button, PIR
|
|
258
|
+
- [x] Internal types: ha-mqtt-dmx, ha-mqtt-dmx-group, ha-mqtt-relay, ha-mqtt-button, ha-mqtt-pir
|
|
259
|
+
- [x] Cascade output label: `Link`
|
|
260
|
+
- [x] Zero outputs on DMX, Relay, Button, PIR
|
|
261
|
+
- [x] Passthrough output removed from all node code
|
|
262
|
+
- [x] node.warn messages updated — no passthrough references
|
|
263
|
+
- [x] Shared utility module decision: INLINE for v1, extract in v2
|
|
264
|
+
- [x] MQTT broker via config node (Option 3)
|
|
265
|
+
- [x] Config node: `ha-mqtt-config` — one per zone
|
|
266
|
+
- [x] Zone in config node — multi-zone = multiple config nodes
|
|
267
|
+
- [x] MQTT settings in config node, topics per node
|
|
268
|
+
- [x] Relay controller MQTT: broker in config, topic built per node
|
|
269
|
+
- [ ] Write ha-mqtt-config .html + .js (FIRST — all other nodes depend on it)
|
|
270
|
+
- [ ] Write ha-mqtt-dmx .html + .js
|
|
271
|
+
- [ ] Write ha-mqtt-dmx-group .html + .js
|
|
272
|
+
- [ ] Write ha-mqtt-relay .html + .js
|
|
273
|
+
- [ ] Write ha-mqtt-button .html + .js
|
|
274
|
+
- [ ] Write ha-mqtt-pir .html + .js
|
|
275
|
+
- [ ] Write package.json
|
|
276
|
+
- [ ] Test packaged install end to end
|
|
277
|
+
- [ ] Test all nodes reference config node correctly
|
|
278
|
+
- [ ] Publish to flows.nodered.org
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Node-RED 4.1.x — Subflow Development Gotchas
|
|
2
|
+
|
|
3
|
+
A collection of non-obvious issues discovered while building a production subflow
|
|
4
|
+
library for a Home Assistant / MQTT lighting control system on Node-RED 4.1.6.
|
|
5
|
+
Posting these here so others don't spend hours rediscovering them.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. You Cannot Import a Subflow Definition and Its Instances in the Same JSON
|
|
10
|
+
|
|
11
|
+
This is the big one. If you export a flow that contains both a subflow definition
|
|
12
|
+
and instances of that subflow on a tab, and try to re-import it — NR will throw:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
TypeError: Cannot read properties of undefined (reading 'length')
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
There is no helpful error message. It fails silently in a confusing way.
|
|
19
|
+
|
|
20
|
+
### Why It Happens
|
|
21
|
+
|
|
22
|
+
When NR processes a JSON import, it encounters the subflow instance
|
|
23
|
+
(`"type": "subflow:abc123"`) and tries to look up the subflow definition to
|
|
24
|
+
validate the port count. If the definition hasn't been fully registered yet
|
|
25
|
+
(even if it appears earlier in the same JSON array), the lookup fails.
|
|
26
|
+
|
|
27
|
+
### The Fix — Two-Step Import
|
|
28
|
+
|
|
29
|
+
Split your flow into two separate JSON files:
|
|
30
|
+
|
|
31
|
+
**File 1 — Subflow definitions only** (no tab, no instances):
|
|
32
|
+
```json
|
|
33
|
+
[
|
|
34
|
+
{ "id": "abc123", "type": "subflow", "name": "My Node", ... },
|
|
35
|
+
{ "id": "fn001", "type": "function", "z": "abc123", ... },
|
|
36
|
+
{ "id": "mq001", "type": "mqtt out", "z": "abc123", ... }
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**File 2 — Tab and instances only** (no subflow definitions):
|
|
41
|
+
```json
|
|
42
|
+
[
|
|
43
|
+
{ "id": "tab001", "type": "tab", ... },
|
|
44
|
+
{ "id": "inst01", "type": "subflow:abc123", "z": "tab001", ... }
|
|
45
|
+
]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Import sequence:**
|
|
49
|
+
1. Import File 1 — **do NOT deploy yet**
|
|
50
|
+
2. Import File 2 immediately (while File 1 is still in memory)
|
|
51
|
+
3. Deploy both together
|
|
52
|
+
|
|
53
|
+
The key is step 2 must happen before deploying. Once you deploy File 1, NR
|
|
54
|
+
registers the subflow definitions. You can then import File 2 separately and
|
|
55
|
+
deploy again. Both sequences work — the important thing is the definition must
|
|
56
|
+
be in NR's memory before the instance import is processed.
|
|
57
|
+
|
|
58
|
+
### Bonus Gotcha — ID Conflicts
|
|
59
|
+
|
|
60
|
+
If you re-import subflow definitions that already exist in NR (same IDs), NR
|
|
61
|
+
will show a conflict warning and offer Import vs Copy. **Copy assigns new IDs**
|
|
62
|
+
which then breaks File 2 (the instances still reference the old IDs).
|
|
63
|
+
|
|
64
|
+
Solution: bump the version and regenerate all node IDs each time you publish
|
|
65
|
+
a new version of your subflow definitions. Every node in the file — subflow
|
|
66
|
+
definition, internal function nodes, internal MQTT nodes, link nodes — needs
|
|
67
|
+
a fresh ID. NR generates IDs as 16-character hex strings (`secrets.token_hex(8)`
|
|
68
|
+
in Python).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 2. The `tostatus: true` Debug Node Does Not Work Inside Subflows
|
|
73
|
+
|
|
74
|
+
If you place a debug node inside a subflow with `tostatus: true` (the "Show
|
|
75
|
+
as status" option), the status text will **not** appear under the subflow
|
|
76
|
+
instance node on the canvas. It works fine for regular function nodes but
|
|
77
|
+
does not propagate through the subflow boundary.
|
|
78
|
+
|
|
79
|
+
### The Correct Solution — Subflow Status Port
|
|
80
|
+
|
|
81
|
+
NR has a built-in mechanism for this that is easy to miss:
|
|
82
|
+
|
|
83
|
+
1. Open your subflow definition (double-click the subflow in the palette)
|
|
84
|
+
2. Click the **edit properties** button (pencil icon, top left)
|
|
85
|
+
3. Tick the **"Status"** checkbox — this adds a special status input port
|
|
86
|
+
|
|
87
|
+
In the subflow JSON this appears as a `status` field on the subflow definition:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"id": "abc123",
|
|
92
|
+
"type": "subflow",
|
|
93
|
+
"name": "My Node",
|
|
94
|
+
"status": {
|
|
95
|
+
"x": 555,
|
|
96
|
+
"y": 195,
|
|
97
|
+
"wires": [{ "id": "fn001", "port": 2 }]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `port` value is **zero-indexed**, so `"port": 2` = output 3 of the function
|
|
103
|
+
node. Wire your function node's status output to this port and status messages
|
|
104
|
+
will correctly appear under the subflow instance on the canvas.
|
|
105
|
+
|
|
106
|
+
### What the Function Node Should Send
|
|
107
|
+
|
|
108
|
+
The status port expects the standard NR status object on `msg.status`:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
node.send([
|
|
112
|
+
null, // output 1
|
|
113
|
+
null, // output 2
|
|
114
|
+
{ // output 3 → status port
|
|
115
|
+
status: {
|
|
116
|
+
fill: "green",
|
|
117
|
+
shape: "ring",
|
|
118
|
+
text: "L-991 ready — awaiting HA commands"
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
null, // output 4
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Valid `fill` values: `"red"`, `"green"`, `"yellow"`, `"blue"`, `"grey"`
|
|
126
|
+
Valid `shape` values: `"ring"`, `"dot"`
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 3. Subflow Env Var JSON — All Values Must Be Strings
|
|
131
|
+
|
|
132
|
+
When hand-crafting subflow env var definitions in JSON, all `value` fields must
|
|
133
|
+
be strings — even for `num` and `bool` types. NR will silently fail or behave
|
|
134
|
+
unexpectedly if you pass integers or booleans.
|
|
135
|
+
|
|
136
|
+
**Wrong:**
|
|
137
|
+
```json
|
|
138
|
+
{ "name": "<HA_MQTT_QOS>", "type": "num", "value": 0 }
|
|
139
|
+
{ "name": "<HA_enabled>", "type": "bool", "value": true }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Correct:**
|
|
143
|
+
```json
|
|
144
|
+
{ "name": "<HA_MQTT_QOS>", "type": "num", "value": "0" }
|
|
145
|
+
{ "name": "<HA_enabled>", "type": "bool", "value": "true" }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 4. Font Awesome Icons on Env Var Labels
|
|
151
|
+
|
|
152
|
+
You can add icons to env var labels in the subflow properties panel — they
|
|
153
|
+
appear next to the label text and make the panel much easier to scan. Add an
|
|
154
|
+
`icon` field to the `ui` object:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"name": "<DEVICE_area>",
|
|
159
|
+
"type": "str",
|
|
160
|
+
"value": "",
|
|
161
|
+
"ui": {
|
|
162
|
+
"label": { "en-US": "Area:" },
|
|
163
|
+
"type": "select",
|
|
164
|
+
"icon": "font-awesome/fa-map-marker",
|
|
165
|
+
"opts": { "opts": [ ... ] }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Useful icon mappings for home automation subflows:
|
|
171
|
+
|
|
172
|
+
| Field type | Icon |
|
|
173
|
+
|---|---|
|
|
174
|
+
| Zone / Location | `font-awesome/fa-location-arrow` |
|
|
175
|
+
| Area | `font-awesome/fa-map-marker` |
|
|
176
|
+
| Sub-area | `font-awesome/fa-map-marker` |
|
|
177
|
+
| Device type | `font-awesome/fa-lightbulb-o` |
|
|
178
|
+
| Colour mode | `font-awesome/fa-sliders` |
|
|
179
|
+
| ID / Prefix / Postfix | `font-awesome/fa-tv` |
|
|
180
|
+
| Channel numbers | `font-awesome/fa-sort-numeric-asc` |
|
|
181
|
+
| Timing fields | `font-awesome/fa-clock-o` |
|
|
182
|
+
| MQTT topics | `font-awesome/fa-exchange` |
|
|
183
|
+
| HA Icon field | `font-awesome/fa-image` |
|
|
184
|
+
| Section headers | `font-awesome/fa-hand-spock-o` |
|
|
185
|
+
|
|
186
|
+
Section separator entries (type `none`) also support icons and are useful for
|
|
187
|
+
visually grouping related env vars:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"name": "SETTINGS_Required",
|
|
192
|
+
"type": "str",
|
|
193
|
+
"value": "",
|
|
194
|
+
"ui": {
|
|
195
|
+
"label": { "en-US": "SETTINGS (*Required)" },
|
|
196
|
+
"type": "none",
|
|
197
|
+
"icon": "font-awesome/fa-hand-spock-o"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 5. MQTT In Node Inside a Subflow — Plain String Payloads
|
|
205
|
+
|
|
206
|
+
If your subflow's internal MQTT In node receives plain string payloads (e.g.
|
|
207
|
+
from a hardware controller publishing `"10-54"` rather than a JSON object),
|
|
208
|
+
set `datatype` to `"auto-detect"` not `"json"`.
|
|
209
|
+
|
|
210
|
+
With `datatype: "json"`, NR will throw:
|
|
211
|
+
```
|
|
212
|
+
Failed to parse JSON string
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
For nodes that receive HA commands (which are always JSON), `"json"` is correct.
|
|
216
|
+
For nodes that receive raw hardware controller payloads, use `"auto-detect"`.
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"type": "mqtt in",
|
|
221
|
+
"datatype": "auto-detect"
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 6. The MQTT In Node Can Subscribe to Multiple Topics
|
|
228
|
+
|
|
229
|
+
An MQTT In node with `inputs: 1` can receive subscribe/unsubscribe commands
|
|
230
|
+
from a function node. This lets one MQTT In node listen on multiple topics
|
|
231
|
+
simultaneously — useful when a node needs to receive both hardware controller
|
|
232
|
+
payloads AND HA dashboard command payloads.
|
|
233
|
+
|
|
234
|
+
Pattern:
|
|
235
|
+
```javascript
|
|
236
|
+
// On device:add, send two subscribe messages on output 2
|
|
237
|
+
node.send([discoveryPayload, { topic: "hardware/topic", action: "subscribe" }, ...]);
|
|
238
|
+
node.send([uiDiscovery, { topic: "ha/cmd/topic", action: "subscribe" }, ...]);
|
|
239
|
+
|
|
240
|
+
// Entry point — differentiate by topic
|
|
241
|
+
if (msg.topic === "hardware/topic" && msg.payload === CFG.devicePayload) {
|
|
242
|
+
handlePhysicalPress();
|
|
243
|
+
}
|
|
244
|
+
if (msg.topic === "ha/cmd/topic" && msg.payload === "PRESS") {
|
|
245
|
+
handleUiPress();
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The same MQTT In node serves as receiver for both, and both routes execute
|
|
250
|
+
identical code paths.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
*Discovered during development of a Node-RED → Home Assistant DMX/relay/sensor
|
|
255
|
+
lighting control system. NR version 4.1.6, HA 2026.x.*
|
|
256
|
+
|
|
257
|
+
*If this saved you time, consider posting your own discoveries — future
|
|
258
|
+
developers (and future AI models) will thank you.*
|