Compare commits

1 Commits

Author SHA1 Message Date
root
4010649388 fix(fb_room): latch ha_all_on/off on rising edge, not level
Node-RED sends ha_all_on and ha_all_off as short NVL pulses (same as
zigbee_swN). Level-sensitive IF switches.ha_all_on caused all outputs to
drop when the pulse cleared (~1s flash then off).

Use R_TRIG plus all_on_latch: ALL ON edge latches room on until ALL OFF
edge. Document behaviour and Node-RED sparse merge in integration guide.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 18:30:06 +00:00
2 changed files with 35 additions and 11 deletions

View File

@@ -14,12 +14,20 @@ VAR
l4: fb_light;
l5: fb_light;
l6: fb_light;
r_trig_all_on: R_TRIG;
r_trig_all_off: R_TRIG;
all_on_latch: BOOL := FALSE; // Latched room-wide ON until ha_all_off rising edge
END_VAR
(* === IMPLEMENTATION === *)
// =====================================================
// fb_room - Implementation (per redesign)
// =====================================================
// Per-light: ha_lN_on/off and zigbee_swN use R_TRIG in fb_light (NVL pulses).
// Room-wide: ha_all_on / ha_all_off are also rising-edge pulses from Node-RED;
// latch all_on_latch so outputs stay on after the pulse ends (do not use level
// on switches.ha_all_on a FALSE NVL bit would drop back to per-light state).
// =====================================================
// Light 1..6 (relay_status = previous cycle output for Option C)
l1(ha_on := switches.ha_l1_on, ha_off := switches.ha_l1_off, zigbee_sw := switches.zigbee_sw1, relay_status := relay_status.l_1);
@@ -40,15 +48,17 @@ lights.l_5 := l5.light_output;
l6(ha_on := switches.ha_l6_on, ha_off := switches.ha_l6_off, zigbee_sw := switches.zigbee_sw6, relay_status := relay_status.l_6);
lights.l_6 := l6.light_output;
// Global Commands (overwrite outputs per redesign)
IF switches.ha_all_off THEN
lights.l_1 := FALSE;
lights.l_2 := FALSE;
lights.l_3 := FALSE;
lights.l_4 := FALSE;
lights.l_5 := FALSE;
lights.l_6 := FALSE;
ELSIF switches.ha_all_on THEN
// Room-wide commands (rising edge + latch)
r_trig_all_on(CLK := switches.ha_all_on);
r_trig_all_off(CLK := switches.ha_all_off);
IF r_trig_all_on.Q THEN
all_on_latch := TRUE;
ELSIF r_trig_all_off.Q THEN
all_on_latch := FALSE;
END_IF;
IF all_on_latch THEN
lights.l_1 := TRUE;
lights.l_2 := TRUE;
lights.l_3 := TRUE;

View File

@@ -88,7 +88,7 @@ flowchart LR
|-------------|---------|---------|
| HA pulses | `ha_l1_on``ha_l6_on`, `ha_l1_off``ha_l6_off` | `fb_light` via `R_TRIG` (rising edge only) |
| Zigbee pulses | `zigbee_sw1``zigbee_sw6` | `fb_light` toggle on edge |
| Room-wide | `ha_all_on`, `ha_all_off` | Room-level FBs |
| Room-wide | `ha_all_on`, `ha_all_off` | `fb_room`: `R_TRIG` + `all_on_latch` (rising edge only; see below) |
**`struct_room_outs`** (in `NVL_Sender` as `light_*` rooms)—per-room outputs:
@@ -112,6 +112,20 @@ Physical mapping: see `codesys-tree/_io.md` (EtherCAT `%QX*` → `Application.NV
Outputs feed `NVL_Sender`; EtherCAT maps those BOOLs to contactors.
### Room-wide ALL ON / ALL OFF (`fb_room`)
Node-RED sends **`ha_all_on` / `ha_all_off` as short rising-edge pulses** (PLC Troubleshoot dashboard, same as `zigbee_swN`). They must **not** be treated as level-sensitive BOOLs.
**Previous behaviour (bug):** `IF switches.ha_all_on THEN …` forced all outputs on only while the NVL bit stayed `TRUE`. When Node-RED cleared the pulse (~120 ms1 s), outputs fell back to per-light `fb_light` state (usually off) → “flash then all off”.
**Current behaviour (`fb_room.st`):**
1. `R_TRIG` on `ha_all_on` / `ha_all_off`.
2. Rising edge on `ha_all_on` sets internal `all_on_latch := TRUE` (all six outputs forced on each scan until released).
3. Rising edge on `ha_all_off` clears `all_on_latch`; per-light `fb_light` outputs apply again.
**Node-RED alignment:** `room-config-lib.js` uses sparse `buildNvlSendPayload` (only `TRUE` bits merged into `nvl-send`) and does not send a follow-up `ha_all_on := FALSE` frame for ALL ON. After deploying this PLC change, download the application to the controller.
---
## Node-RED side
@@ -141,7 +155,7 @@ Outputs feed `NVL_Sender`; EtherCAT maps those BOOLs to contactors.
5. **Return path:** **`udp in`** `:1202`**`nvl-receive`** (parses **`NVL_Sender`**) → **NVL to HA Sync** → Home Assistant `api-call-service` (`turn_on` / `turn_off` on mapped helpers).
6. **Edge clearing** — after ~80 ms, **Clear NVL edges** / **Clear zigbee edge** reset pulse bits so the PLC sees rising edges on the next command (matches `R_TRIG` in ST).
6. **Edge clearing** — after ~120 ms, **Clear NVL edges** reset `zigbee_sw*` / `ha_lN_*` pulse bits via `buildNvlClearPayload` (explicit `FALSE` merge only for cleared keys). **`ha_all_on` / `ha_all_off`** are not cleared over UDP (one rising-edge frame only); the PLC latch in `fb_room` holds the room on until `ha_all_off`.
### Configuration files (single source of truth)