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,35 @@
|
|
|
1
|
+
# Subflow Reference Implementation
|
|
2
|
+
|
|
3
|
+
These files are the original Node-RED subflow implementation of the nodes.
|
|
4
|
+
They predate the packaged node version and are kept here as a reference.
|
|
5
|
+
|
|
6
|
+
## What these are
|
|
7
|
+
|
|
8
|
+
Before the nodes were packaged as proper NR nodes (`ha-mqtt-*`), they ran
|
|
9
|
+
as subflows — Node-RED function nodes with env var menus. The `.js` files
|
|
10
|
+
here are the function node code that was pasted inside each subflow.
|
|
11
|
+
|
|
12
|
+
## When to use these
|
|
13
|
+
|
|
14
|
+
- As a reference for the business logic if you need to understand what the
|
|
15
|
+
packaged node `.js` files are doing
|
|
16
|
+
- For debugging — the subflow version can be imported into NR without npm install
|
|
17
|
+
- As a fallback if the packaged nodes are not available in your NR version
|
|
18
|
+
|
|
19
|
+
## Import instructions
|
|
20
|
+
|
|
21
|
+
1. Import `subflow_definitions.json` into NR — do NOT deploy yet
|
|
22
|
+
2. Wire up instances and deploy
|
|
23
|
+
3. Paste the relevant `.js` file contents into each function node
|
|
24
|
+
|
|
25
|
+
See `/docs/nr_subflow_gotchas.md` for important NR import notes.
|
|
26
|
+
|
|
27
|
+
## Versions
|
|
28
|
+
|
|
29
|
+
| File | Version |
|
|
30
|
+
|---|---|
|
|
31
|
+
| dmx_node_v0.5.9.js | 0.5.9 |
|
|
32
|
+
| dmx_group_node_v0.3.8.js | 0.3.8 |
|
|
33
|
+
| relay_node_v4.0.2.js | 4.0.2 |
|
|
34
|
+
| button_node_v5.0.3.js | 5.0.3 |
|
|
35
|
+
| pir_node_v1.0.3.js | 1.0.3 |
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// BUTTON NODE — HOME ASSISTANT in NODE-RED
|
|
2
|
+
// Discord: @deswaggy | Version: 5.0.3
|
|
3
|
+
// Extensions: "BETTER COMMENTS", "TODO TREE"
|
|
4
|
+
|
|
5
|
+
//! BIG & BRIGHT
|
|
6
|
+
//@ Topic Headers
|
|
7
|
+
//# Description / Explanations
|
|
8
|
+
//$ Logic
|
|
9
|
+
//^ Debug Comments
|
|
10
|
+
//? Breaks / Headers
|
|
11
|
+
|
|
12
|
+
// Naming conventions:
|
|
13
|
+
// <env_var> = Sub-flow environmental (Edit subflow template)
|
|
14
|
+
// <<<flow_env>>> = Flow-level environmental (Tab → gear → flow envs)
|
|
15
|
+
|
|
16
|
+
// Role of this node:
|
|
17
|
+
// Receives physical wall button press payloads from the button controller
|
|
18
|
+
// via MQTT, filters for its own payload, and reports the press to HA as
|
|
19
|
+
// a binary_sensor state change (ON → auto-clears via HA off_delay).
|
|
20
|
+
//
|
|
21
|
+
// Also discovers a companion HA "button" entity so the physical wall button
|
|
22
|
+
// can be mirrored in the HA dashboard. Pressing the UI button follows the
|
|
23
|
+
// same code path as a physical press.
|
|
24
|
+
//
|
|
25
|
+
// Physical press path:
|
|
26
|
+
// Controller MQTT → NR MQTT In → Button Node → binary_sensor ON → HA automation
|
|
27
|
+
//
|
|
28
|
+
// UI mirror press path:
|
|
29
|
+
// HA UI button → HA publishes to cmd_t → NR MQTT In → Button Node → same path
|
|
30
|
+
//
|
|
31
|
+
// NO websocket dependency. Pure MQTT throughout.
|
|
32
|
+
//
|
|
33
|
+
// Controller payload format: "{panelId}-{GPIOpin}" e.g. "10-54"
|
|
34
|
+
// Panel IDs: 10=Master B.Ctrl#1, 11=#2, 12=#3, 13=PIR#1, 14=PIR#2 etc.
|
|
35
|
+
|
|
36
|
+
//! IMPORTANT — NODE-RED FUNCTION NODE SETUP:
|
|
37
|
+
// This node requires EXACTLY 4 outputs configured in the NR function node settings.
|
|
38
|
+
// Output 1: HA MQTT (binary_sensor state + discovery config) → wire to MQTT Out node
|
|
39
|
+
// Output 2: MQTT subscribe / unsubscribe actions → wire to MQTT In node
|
|
40
|
+
// Output 3: Node status + debug → wire to Status / Debug node
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
//!@ CONFIG — all env reads in one place
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
const CFG = {
|
|
47
|
+
//@ HA identity
|
|
48
|
+
discoveryPrefix: env.get("<HA_discovery_prefix>"),
|
|
49
|
+
siteId: env.get("<HA_site_unique_id>"),
|
|
50
|
+
//^ binary_sensor is always the automation trigger entity — hardcoded, not env-driven
|
|
51
|
+
component: "binary_sensor",
|
|
52
|
+
uidPrefix: env.get("<HA_unique_id_prefix>"), //^ S = Switch (electrician convention)
|
|
53
|
+
uid: env.get("<HA_unique_id>"), //^ cable number from electrical plan
|
|
54
|
+
uidPostfix: String(env.get("<HA_unique_id_postfix>") || ""), //^ button letter A/B/C/D
|
|
55
|
+
|
|
56
|
+
//@ MQTT — HA side
|
|
57
|
+
retain: env.get("<HA_MQTT_retain_flag>"),
|
|
58
|
+
qos: env.get("<HA_MQTT_QOS>"),
|
|
59
|
+
configTopic: env.get("<HA_config_topic>"),
|
|
60
|
+
commandTopic: env.get("<HA_command_topic>"),
|
|
61
|
+
stateTopic: env.get("<HA_state_topic>"),
|
|
62
|
+
|
|
63
|
+
//@ HA features
|
|
64
|
+
enabledByDefault: env.get("<HA_enabled_by_default>"),
|
|
65
|
+
icon: env.get("<HA_icon>"),
|
|
66
|
+
|
|
67
|
+
//@ Button specific
|
|
68
|
+
//^ buttonPayload: the controller payload that identifies THIS button
|
|
69
|
+
//^ Format: "{panelId}-{GPIOpin}" e.g. "10-54" = controller 10, GPIO pin 54
|
|
70
|
+
buttonPayload: String(env.get("<BUTTON_Payload>")),
|
|
71
|
+
buttonPosition: env.get("<BUTTON_Position>"), //^ Top Left, Top Right, Bottom Left, Bottom Right etc.
|
|
72
|
+
buttonLedColor: env.get("<BUTTON_Info_LED_Color>"), //^ Blue, Red, Green, White, Orange
|
|
73
|
+
//^ How long HA shows the button as ON before auto-clearing (seconds, float).
|
|
74
|
+
//^ Implemented via HA binary_sensor off_delay — HA manages the timer, not NR.
|
|
75
|
+
//^ Default 0.5s feels responsive without being too brief for automation edge detection.
|
|
76
|
+
holdTimeSecs: parseFloat(env.get("<BUTTON_hold_time_seconds>")) || 0.5,
|
|
77
|
+
|
|
78
|
+
//@ Controller — physical button hardware side
|
|
79
|
+
controllerZone: env.get("<CONTROLLER_zone>"),
|
|
80
|
+
controllerSubscribeTopic: env.get("<CONTROLLER_MQTT_subscribe_topic>"),
|
|
81
|
+
//^ Full topic path. New firmware: "MW3D/Master/10/buttons"
|
|
82
|
+
//^ Old firmware (translation layer): "buttons"
|
|
83
|
+
controllerFw: env.get("<CONTROLLER_fw_v>"),
|
|
84
|
+
|
|
85
|
+
//@ Button type — default "Wall", kept for future controller/type flexibility
|
|
86
|
+
buttonType: env.get("<BUTTON_Type>") || "Wall",
|
|
87
|
+
|
|
88
|
+
//@ Controller model — for hw_version documentation and future multi-controller support
|
|
89
|
+
controllerModel: env.get("<CONTROLLER_Model>") || "SuperHouse_ButtonController",
|
|
90
|
+
|
|
91
|
+
//@ Device metadata
|
|
92
|
+
device: {
|
|
93
|
+
situation: env.get("<DEVICE_situation>"),
|
|
94
|
+
area: env.get("<DEVICE_area>"),
|
|
95
|
+
subLocation: env.get("<DEVICE_Sub_Location>"),
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
//@ Node metadata
|
|
99
|
+
nodeCode: env.get("<NODE_code>") || "BUTTON NODE",
|
|
100
|
+
nodeSw: env.get("<NODE_sw_v>"),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
//@ TOPIC BUILDERS
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/** HA MQTT base topic for this button's binary_sensor entity. */
|
|
108
|
+
const fixtureTopic = `${CFG.discoveryPrefix}/${CFG.component}/${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`;
|
|
109
|
+
|
|
110
|
+
/** HA MQTT base topic for the companion UI button entity (mirror of physical button). */
|
|
111
|
+
const uiButtonTopic = `${CFG.discoveryPrefix}/button/${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}-BTN`;
|
|
112
|
+
|
|
113
|
+
/** The command topic HA publishes to when the UI button is pressed. */
|
|
114
|
+
const uiButtonCmdTopic = `${uiButtonTopic}/${CFG.commandTopic}`;
|
|
115
|
+
|
|
116
|
+
/** Short fixture ID for NR node status messages. */
|
|
117
|
+
const fixtureId = `${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`;
|
|
118
|
+
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
//@ MQTT SEND HELPERS
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Publish binary_sensor state to HA on output 1.
|
|
125
|
+
* @param {"ON"|"OFF"} state
|
|
126
|
+
*/
|
|
127
|
+
function sendState(state) {
|
|
128
|
+
node.send([{
|
|
129
|
+
retain: CFG.retain,
|
|
130
|
+
qos: CFG.qos,
|
|
131
|
+
topic: `${fixtureTopic}/${CFG.stateTopic}`,
|
|
132
|
+
payload: state,
|
|
133
|
+
}, null, null, null]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Send Node-RED status + debug message on output 3. */
|
|
137
|
+
function sendStatus(fill, shape, text) {
|
|
138
|
+
node.send([null, null, {
|
|
139
|
+
debug: { node: fixtureId, message: text },
|
|
140
|
+
status: { fill, shape, text },
|
|
141
|
+
}, null, null]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
//@ SERIAL NUMBER BUILDER
|
|
146
|
+
//# SHARED PATTERN — extract to shared utility on node package build
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function buildSerialNumber() {
|
|
150
|
+
//# Human-readable format matching DMX Node convention.
|
|
151
|
+
//# Cable ID and payload are what a field tech needs — not a machine slug.
|
|
152
|
+
return `(${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}) Payload: ${CFG.buttonPayload} — See ⓘ MQTT Info below.`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Build a clean slug for the HA object_id (entity_id prefix). */
|
|
156
|
+
function buildObjectId() {
|
|
157
|
+
//# object_id sets the HA entity_id: binary_sensor.s_33a
|
|
158
|
+
//# Matches plan ID directly — what electricians and techs expect.
|
|
159
|
+
return `${CFG.uidPrefix}_${CFG.uid}${CFG.uidPostfix}`.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
//@ PRESS HANDLER
|
|
164
|
+
//# Used for both physical button press and HA UI button press — identical path.
|
|
165
|
+
//# HA auto-clears the binary_sensor via off_delay in the discovery payload.
|
|
166
|
+
//# NR does not need a timer — HA manages the hold duration.
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
function handlePress(source) {
|
|
170
|
+
console.log(`${fixtureId} press — source:${source} position:${CFG.buttonPosition}`);
|
|
171
|
+
sendState("ON");
|
|
172
|
+
sendStatus("blue", "dot", `${fixtureId} pressed — ${CFG.buttonPosition}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
//@ DEVICE REQUEST HANDLERS
|
|
177
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
function buildSerialNumberStr() {
|
|
180
|
+
return buildSerialNumber();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function handleDeviceAdd() {
|
|
184
|
+
const { device: dev } = CFG;
|
|
185
|
+
|
|
186
|
+
//# Binary sensor discovery — the automation trigger entity
|
|
187
|
+
const binarySensorDiscovery = {
|
|
188
|
+
retain: CFG.retain,
|
|
189
|
+
qos: CFG.qos,
|
|
190
|
+
topic: `${fixtureTopic}/${CFG.configTopic}`,
|
|
191
|
+
payload: {
|
|
192
|
+
unique_id: `${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`,
|
|
193
|
+
//^ object_id sets the HA entity_id: binary_sensor.s_33a
|
|
194
|
+
//^ Locked to plan ID — survives user renaming the friendly name in HA UI.
|
|
195
|
+
object_id: buildObjectId(),
|
|
196
|
+
name: `${CFG.buttonPosition} button ${dev.situation} the ${CFG.controllerZone} ${dev.area} ${dev.subLocation}`,
|
|
197
|
+
stat_t: `${fixtureTopic}/${CFG.stateTopic}`,
|
|
198
|
+
//^ off_delay: HA automatically clears the binary_sensor after this many seconds.
|
|
199
|
+
//^ NR publishes ON, HA manages the timer and publishes OFF itself.
|
|
200
|
+
//^ This removes the need for any NR-side timer logic.
|
|
201
|
+
off_delay: CFG.holdTimeSecs,
|
|
202
|
+
enabled_by_default: CFG.enabledByDefault,
|
|
203
|
+
icon: CFG.icon,
|
|
204
|
+
device: {
|
|
205
|
+
identifiers: `${CFG.component}-${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`,
|
|
206
|
+
name: `(${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}) - Wall Button/s ${dev.situation} the ${CFG.controllerZone} - ${dev.area} - ${dev.subLocation}`,
|
|
207
|
+
model: `Wall button/s located ${dev.situation} the ${CFG.controllerZone}-${dev.area} - ${dev.subLocation}`,
|
|
208
|
+
model_id: `referenced on plan as: (${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`,
|
|
209
|
+
suggested_area: `${CFG.controllerZone} ${dev.area} ${dev.subLocation}`,
|
|
210
|
+
hw_version: `${CFG.buttonType} button — ${CFG.buttonLedColor} LED indicator, Green Cat5 cable. ` +
|
|
211
|
+
`Controller model: ${CFG.controllerModel} in ${CFG.controllerZone} Server Rack, firmware: ${CFG.controllerFw}. ` +
|
|
212
|
+
`Publishes payload "${CFG.buttonPayload}" on topic: ${CFG.controllerSubscribeTopic}`,
|
|
213
|
+
serial_number: buildSerialNumberStr(),
|
|
214
|
+
sw_version: `${CFG.nodeCode}: ${CFG.nodeSw}`,
|
|
215
|
+
manufacturer: "DeSwaggy — Discord: @deswaggy",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
//# UI button discovery — the dashboard mirror entity
|
|
221
|
+
//# Pressing this in HA UI publishes "PRESS" to uiButtonCmdTopic.
|
|
222
|
+
//# NR receives it and follows the same path as a physical press.
|
|
223
|
+
//# Stateless in HA — no state topic. Just a command trigger.
|
|
224
|
+
const uiButtonDiscovery = {
|
|
225
|
+
retain: CFG.retain,
|
|
226
|
+
qos: CFG.qos,
|
|
227
|
+
topic: `${uiButtonTopic}/${CFG.configTopic}`,
|
|
228
|
+
payload: {
|
|
229
|
+
unique_id: `${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}-BTN`,
|
|
230
|
+
object_id: `${buildObjectId()}_btn`,
|
|
231
|
+
name: `${CFG.buttonPosition} (UI) ${dev.situation} the ${CFG.controllerZone} ${dev.area} ${dev.subLocation}`,
|
|
232
|
+
cmd_t: uiButtonCmdTopic,
|
|
233
|
+
payload_press: "PRESS",
|
|
234
|
+
enabled_by_default: CFG.enabledByDefault,
|
|
235
|
+
icon: CFG.icon,
|
|
236
|
+
//# Same device as binary_sensor — one device, two entities in HA
|
|
237
|
+
device: {
|
|
238
|
+
identifiers: `${CFG.component}-${CFG.uidPrefix}-${CFG.uid}${CFG.uidPostfix}`,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
//# Subscribe to controller topic (physical presses) AND UI button cmd topic
|
|
244
|
+
const subscribeController = {
|
|
245
|
+
retain: CFG.retain,
|
|
246
|
+
qos: CFG.qos,
|
|
247
|
+
topic: CFG.controllerSubscribeTopic,
|
|
248
|
+
action: "subscribe",
|
|
249
|
+
};
|
|
250
|
+
const subscribeUiButton = {
|
|
251
|
+
retain: CFG.retain,
|
|
252
|
+
qos: CFG.qos,
|
|
253
|
+
topic: uiButtonCmdTopic,
|
|
254
|
+
action: "subscribe",
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
//# Send binary_sensor discovery first, then UI button discovery
|
|
258
|
+
node.send([binarySensorDiscovery, subscribeController, { status: { fill: "green", shape: "ring", text: `${fixtureId} discovery sent — awaiting press` } }, null, null]);
|
|
259
|
+
node.send([uiButtonDiscovery, subscribeUiButton, null, null, null]);
|
|
260
|
+
|
|
261
|
+
console.log(`${fixtureId} device added — position:${CFG.buttonPosition} payload:${CFG.buttonPayload}`);
|
|
262
|
+
console.log(`${fixtureId} subscribed to: ${CFG.controllerSubscribeTopic} and ${uiButtonCmdTopic}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function handleDeviceRemove() {
|
|
266
|
+
node.send([
|
|
267
|
+
{ retain: CFG.retain, qos: CFG.qos, topic: `${fixtureTopic}/${CFG.configTopic}`, payload: "" },
|
|
268
|
+
{ retain: CFG.retain, qos: CFG.qos, topic: CFG.controllerSubscribeTopic, action: "unsubscribe" },
|
|
269
|
+
{ status: { fill: "red", shape: "ring", text: `${fixtureId} removed` } },
|
|
270
|
+
null,
|
|
271
|
+
null,
|
|
272
|
+
]);
|
|
273
|
+
//# Also remove UI button entity and unsubscribe from its cmd topic
|
|
274
|
+
node.send([
|
|
275
|
+
{ retain: CFG.retain, qos: CFG.qos, topic: `${uiButtonTopic}/${CFG.configTopic}`, payload: "" },
|
|
276
|
+
{ retain: CFG.retain, qos: CFG.qos, topic: uiButtonCmdTopic, action: "unsubscribe" },
|
|
277
|
+
null, null, null,
|
|
278
|
+
]);
|
|
279
|
+
console.log(`${fixtureId} device removed.`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
+
//!? ENTRY POINT
|
|
284
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
node.send([null, null, null, msg]);
|
|
287
|
+
|
|
288
|
+
console.log(`${fixtureId} payload:"${msg.payload}" topic:"${msg.topic || ""}"`);
|
|
289
|
+
|
|
290
|
+
//? ── DEVICE REQUEST — add / remove / debug ───────────────────────────────────
|
|
291
|
+
if (msg.device != null && (typeof msg.device === "string" || msg.device?.request != null)) {
|
|
292
|
+
const _devReq = typeof msg.device === "string" ? msg.device : msg.device.request;
|
|
293
|
+
console.log(`${fixtureId} device.request: ${_devReq}`);
|
|
294
|
+
switch (_devReq) {
|
|
295
|
+
case "add": handleDeviceAdd(); break;
|
|
296
|
+
case "remove": handleDeviceRemove(); break;
|
|
297
|
+
case "debug": console.log(`${fixtureId} DEBUG:`, JSON.stringify(msg)); break;
|
|
298
|
+
default: node.warn(`${fixtureId} — unknown device.request: "${_devReq}"`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
//? ── UI BUTTON PRESS — from HA dashboard ─────────────────────────────────────
|
|
302
|
+
} else if (msg.topic === uiButtonCmdTopic && msg.payload === "PRESS") {
|
|
303
|
+
handlePress("HA UI");
|
|
304
|
+
|
|
305
|
+
//? ── PHYSICAL BUTTON PRESS — from controller MQTT ────────────────────────────
|
|
306
|
+
} else if (String(msg.payload) === CFG.buttonPayload) {
|
|
307
|
+
handlePress("physical");
|
|
308
|
+
|
|
309
|
+
//? ── UNKNOWN MESSAGE ─────────────────────────────────────────────────────────
|
|
310
|
+
} else {
|
|
311
|
+
//# Payload didn't match — this button node is ignoring a press meant for another button.
|
|
312
|
+
//# This is normal and expected on the shared controller topic. No warn needed.
|
|
313
|
+
//# Only warn if the topic is the UI command topic but payload is unexpected.
|
|
314
|
+
if (msg.topic === uiButtonCmdTopic) {
|
|
315
|
+
node.warn(
|
|
316
|
+
`${fixtureId} — unexpected payload on UI button cmd topic. ` +
|
|
317
|
+
`Expected "PRESS", got: "${msg.payload}"`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
//# Silently ignore all other payloads — they belong to sibling button nodes.
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
node.done();
|
|
324
|
+
return [null, null, null, msg];
|