Files
kkelomatic_home/docs/codesys/plc-algorithm-design.md
2026-02-07 22:21:43 +02:00

803 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PLC Algorithm Design - Lighting and Water Boiler Control
## Overview
This document defines the PLC algorithms for the home automation system:
1. **Lighting Control** - Improved logic with command-based control for Home Assistant and toggle-based for Zigbee switches
2. **Water Boiler Control** - Simple ON/OFF with safety features (time limit, emergency stop)
## System Architecture
```
┌─────────────────────────────────────────────────────────────────────────┐
│ CODESYS PLC (Raspberry Pi) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ NVL_Receiver │ │ MainTask │ │ NVL_Sender │ │
│ │ (from Node-RED)│ │ (4ms cycle) │ │ (to Node-RED) │ │
│ └────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▲ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PLC_PRG │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Lights │ │ Boiler │ │ Safety_Monitor │ │ │
│ │ │ Program │ │ Program │ │ (future) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └─────────────────────────┘ │ │
│ └─────────┼────────────────┼──────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ EtherCAT I/O │ │
│ │ ┌──────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ EL1809 │ │ Output Module │ │ │
│ │ │ 16x DI 24V │ │ 16x DO (Relays) │ │ │
│ │ │ (Switches) │ │ (Lights + Boiler) │ │ │
│ │ └──────────────┘ └──────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Physical │ │ Physical │
│ Switches │ │ Relays │
└─────────────────┘ └─────────────────┘
```
---
## Light organization: do you need 6 per room?
### Short answers
- **Do you need exactly 6 predefined?** No. Its a convenient maximum. Unused slots are fine (no relay wired). You can use a smaller max (e.g. 4) or a larger one (e.g. 8) if you prefer.
- **Is room-based organization a good idea?** Yes. It matches how people think and how voice/HA work (“turn off bedroom”, “kitchen lights”). Keep organizing by room.
- **Best approach:** Room-based with a **fixed max lights per room** (e.g. 6 or 8). Use only the slots you need; leave the rest logically “empty.” Optionally, use **named slots** per room (e.g. `main`, `bedside_left`) instead of `l_1`..`l_6` so the PLC matches your naming and you dont “waste” slots mentally.
### Options (trade-offs)
| Approach | Description | Pros | Cons |
|---------|-------------|------|------|
| **A. Fixed N per room (current)** | Every room has e.g. 6 slots (`l_1`..`l_6`). Use only what you need. | Simple, same code for every room, easy Node-RED mapping. | Empty slots still in struct/UI; adding a 7th light in one room needs a new room or structure change. |
| **B. Named slots per room** | Each room has semantic slots: `main`, `bedside_left`, `under_cabinet`, etc. (from `light-naming-configuration.md`). Not all rooms have all slots. | Clear meaning, matches your naming; “unused” is obvious. | More types or optional slots in CODESYS; Node-RED must know which rooms have which slots. |
| **C. Flat list of lights** | One list of e.g. 64 lights; each has a relay index. Room exists only in HA/Node-RED. | No per-room limit; add lights by config. | “Turn off all kitchen” is done in HA/Node-RED (send OFF to each kitchen light). PLC doesnt do room-level all_off. |
### Recommendation
- **Stay room-based** for control and UX.
- **Keep a max per room (e.g. 6 or 8)** and accept that some slots are unused. No need to “predefine” exactly 6; think of it as “up to 6 (or 8) circuits per room.”
- If you want the PLC to reflect real names, consider **B (named slots)** later: e.g. `struct_room_lights` with `main`, `bedside_left`, `bedside_right`, `closet`, … and only wire the ones that exist in that room. Thats a refinement of the same idea, not a different architecture.
So: you dont *need* 6 predefined; you need a *maximum* per room. Organizing by room is a good idea; the rest is how many slots per room and whether theyre named or generic.
---
## Part 1: Lighting Control Algorithm
**Alignment**: This section follows the structures and logic from **`docs/redesign/fb_switch-redesign-recommendation.md`**: flat `struct_switches` / `struct_lights`, `fb_lightControl` with HA ON/OFF + Zigbee toggle only, and `fb_switch` applying global commands by overwriting outputs.
### 1.1 Design Goals
- **Command-based control** for Home Assistant (explicit ON/OFF)
- **Toggle-based control** for Zigbee switches (edge detection)
- **State synchronization** between PLC and Home Assistant
- **Relay status feedback** for actual state monitoring
### 1.2 Data Structures (per Redesign Recommendation)
#### Input: struct_switches (flat, for Node-RED/JSON compatibility)
```iec
TYPE struct_switches :
STRUCT
// Home Assistant Commands (set/reset)
ha_l1_on: BOOL; // HA command: Light 1 ON
ha_l1_off: BOOL; // HA command: Light 1 OFF
ha_l2_on: BOOL;
ha_l2_off: BOOL;
ha_l3_on: BOOL;
ha_l3_off: BOOL;
ha_l4_on: BOOL;
ha_l4_off: BOOL;
ha_l5_on: BOOL;
ha_l5_off: BOOL;
ha_l6_on: BOOL;
ha_l6_off: BOOL;
// Zigbee Switch Inputs (edge detection for toggle)
zigbee_sw1: BOOL; // Zigbee switch 1 (edge detected)
zigbee_sw2: BOOL;
zigbee_sw3: BOOL;
zigbee_sw4: BOOL;
zigbee_sw5: BOOL;
zigbee_sw6: BOOL;
// Global Commands
ha_all_on: BOOL; // HA: All lights ON
ha_all_off: BOOL; // HA: All lights OFF
END_STRUCT
END_TYPE
```
#### Output: struct_lights (flat, control + status feedback)
```iec
TYPE struct_lights :
STRUCT
l_1: BOOL; // Light 1 control output
l_2: BOOL; // Light 2 control output
l_3: BOOL; // Light 3 control output
l_4: BOOL; // Light 4 control output
l_5: BOOL; // Light 5 control output
l_6: BOOL; // Light 6 control output
// Status feedback (read from actual relay outputs)
l_1_status: BOOL; // Actual relay 1 state
l_2_status: BOOL; // Actual relay 2 state
l_3_status: BOOL; // Actual relay 3 state
l_4_status: BOOL; // Actual relay 4 state
l_5_status: BOOL; // Actual relay 5 state
l_6_status: BOOL; // Actual relay 6 state
END_STRUCT
END_TYPE
```
### 1.3 Function Block: fb_lightControl (per Redesign)
Individual light control: HA ON/OFF + Zigbee toggle only. No all_on/all_off inside this FB.
```iec
FUNCTION_BLOCK fb_lightControl
VAR_INPUT
// Home Assistant Commands
ha_on: BOOL; // HA command: Turn ON
ha_off: BOOL; // HA command: Turn OFF
// Zigbee Switch Input
zigbee_sw: BOOL; // Zigbee switch (edge detected)
// Status Feedback
relay_status: BOOL; // Actual relay state (read from EtherCAT)
END_VAR
VAR_OUTPUT
light_output: BOOL; // Control output to relay
light_status: BOOL; // Status to send back (actual relay state)
END_VAR
VAR
// Edge triggers
r_trig_ha_on: R_TRIG;
r_trig_ha_off: R_TRIG;
r_trig_zigbee: R_TRIG;
// Internal state
light_state: BOOL := FALSE;
END_VAR
```
#### Algorithm Logic (Priority: HA OFF > HA ON > Zigbee toggle)
```iec
// =====================================================
// fb_lightControl - Implementation (per redesign)
// =====================================================
// Priority (highest to lowest):
// 1. HA OFF command - Always turns light OFF
// 2. HA ON command - Always turns light ON
// 3. Zigbee toggle - Toggles current state
// 4. Maintain current state
// =====================================================
// Edge detection for commands
r_trig_ha_on(CLK := ha_on);
r_trig_ha_off(CLK := ha_off);
r_trig_zigbee(CLK := zigbee_sw);
// State machine
IF r_trig_ha_off.Q THEN
light_state := FALSE;
ELSIF r_trig_ha_on.Q THEN
light_state := TRUE;
ELSIF r_trig_zigbee.Q THEN
light_state := NOT light_state;
END_IF;
// Output assignment
light_output := light_state;
// Status feedback (read from actual relay)
light_status := relay_status;
```
### 1.4 Function Block: fb_switch (per Redesign)
Room-level block: 6× fb_lightControl; global commands applied by overwriting outputs.
```iec
FUNCTION_BLOCK fb_switch
VAR_INPUT
switches: struct_switches;
relay_status: struct_lights; // Read from EtherCAT outputs (actual relay states)
END_VAR
VAR_OUTPUT
lights: struct_lights;
END_VAR
VAR
l1: fb_lightControl;
l2: fb_lightControl;
l3: fb_lightControl;
l4: fb_lightControl;
l5: fb_lightControl;
l6: fb_lightControl;
END_VAR
```
#### Algorithm Logic
```iec
// =====================================================
// fb_switch - Implementation (per redesign)
// =====================================================
// Light 1 Control
l1(
ha_on := switches.ha_l1_on,
ha_off := switches.ha_l1_off,
zigbee_sw := switches.zigbee_sw1,
relay_status := relay_status.l_1_status
);
lights.l_1 := l1.light_output;
lights.l_1_status := l1.light_status;
// Light 2 Control
l2(
ha_on := switches.ha_l2_on,
ha_off := switches.ha_l2_off,
zigbee_sw := switches.zigbee_sw2,
relay_status := relay_status.l_2_status
);
lights.l_2 := l2.light_output;
lights.l_2_status := l2.light_status;
// Light 3 Control
l3(
ha_on := switches.ha_l3_on,
ha_off := switches.ha_l3_off,
zigbee_sw := switches.zigbee_sw3,
relay_status := relay_status.l_3_status
);
lights.l_3 := l3.light_output;
lights.l_3_status := l3.light_status;
// Light 4 Control
l4(
ha_on := switches.ha_l4_on,
ha_off := switches.ha_l4_off,
zigbee_sw := switches.zigbee_sw4,
relay_status := relay_status.l_4_status
);
lights.l_4 := l4.light_output;
lights.l_4_status := l4.light_status;
// Light 5 Control
l5(
ha_on := switches.ha_l5_on,
ha_off := switches.ha_l5_off,
zigbee_sw := switches.zigbee_sw5,
relay_status := relay_status.l_5_status
);
lights.l_5 := l5.light_output;
lights.l_5_status := l5.light_status;
// Light 6 Control
l6(
ha_on := switches.ha_l6_on,
ha_off := switches.ha_l6_off,
zigbee_sw := switches.zigbee_sw6,
relay_status := relay_status.l_6_status
);
lights.l_6 := l6.light_output;
lights.l_6_status := l6.light_status;
// 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
lights.l_1 := TRUE;
lights.l_2 := TRUE;
lights.l_3 := TRUE;
lights.l_4 := TRUE;
lights.l_5 := TRUE;
lights.l_6 := TRUE;
END_IF;
```
---
## Part 2: Water Boiler Control Algorithm
### 2.1 Design Goals
Based on your requirements:
- **Simple ON/OFF control** via relay output only
- **Maximum heating time limit** (safety feature)
- **Emergency stop button** support
- **State monitoring and feedback**
### 2.2 Data Structures
#### Boiler Commands (from Node-RED/Home Assistant)
```iec
TYPE struct_boiler_commands :
STRUCT
// Control Commands
ha_on: BOOL; // Home Assistant: Turn ON
ha_off: BOOL; // Home Assistant: Turn OFF
schedule_on: BOOL; // Scheduled ON (from automation)
schedule_off: BOOL; // Scheduled OFF (from automation)
// Safety Input
emergency_stop: BOOL; // Emergency stop (physical button or HA)
// Configuration (can be set via Node-RED)
max_on_time_minutes: INT; // Maximum ON time in minutes (default: 480 = 8 hours)
END_STRUCT
END_TYPE
```
#### Boiler Status (to Node-RED/Home Assistant)
```iec
TYPE struct_boiler_status :
STRUCT
// State
state: BOOL; // Current boiler state (ON/OFF)
relay_output: BOOL; // Actual relay output state
// Runtime Info
on_time_minutes: INT; // Current ON time in minutes
remaining_minutes: INT; // Remaining time before auto-shutoff
// Safety Status
emergency_active: BOOL; // Emergency stop is active
time_limit_reached: BOOL; // Max time limit was reached
// Error Handling
error_state: BOOL; // Error detected
error_code: INT; // Error code (0=none, 1=emergency, 2=time limit)
END_STRUCT
END_TYPE
```
### 2.3 Function Block: fb_waterBoiler
Simple ON/OFF boiler control with time limit and emergency stop.
```iec
FUNCTION_BLOCK fb_waterBoiler
VAR_INPUT
// Control Commands
ha_on: BOOL; // Home Assistant ON command
ha_off: BOOL; // Home Assistant OFF command
schedule_on: BOOL; // Scheduled ON
schedule_off: BOOL; // Scheduled OFF
// Safety
emergency_stop: BOOL; // Emergency stop input
// Configuration
max_on_time: TIME := T#8H; // Maximum ON time (default 8 hours)
END_VAR
VAR_OUTPUT
// Outputs
relay_control: BOOL; // Control output to relay
// Status
state: BOOL; // Current state
on_time_seconds: DINT; // Current ON time in seconds
remaining_seconds: DINT; // Remaining time in seconds
// Safety Status
emergency_active: BOOL; // Emergency stop active
time_limit_reached: BOOL; // Time limit was reached
error_state: BOOL; // Error flag
error_code: INT; // Error code
END_VAR
VAR
// Internal State
internal_state: BOOL := FALSE;
last_state: BOOL := FALSE;
// Edge Detection
r_trig_ha_on: R_TRIG;
r_trig_ha_off: R_TRIG;
r_trig_schedule_on: R_TRIG;
r_trig_schedule_off: R_TRIG;
r_trig_emergency: R_TRIG;
f_trig_emergency: F_TRIG;
// Timers
on_timer: TON; // Counts ON time
// Time tracking
max_on_seconds: DINT;
END_VAR
```
### 2.4 Algorithm Logic
```iec
// =====================================================
// fb_waterBoiler - Implementation
// =====================================================
// Priority (highest to lowest):
// 1. Emergency Stop - Immediate shutdown
// 2. Time Limit Exceeded - Auto shutdown
// 3. OFF Commands - Turn off
// 4. ON Commands - Turn on (if safe)
// =====================================================
// Calculate max time in seconds
max_on_seconds := TIME_TO_DINT(max_on_time) / 1000;
// Edge detection for commands
r_trig_ha_on(CLK := ha_on);
r_trig_ha_off(CLK := ha_off);
r_trig_schedule_on(CLK := schedule_on);
r_trig_schedule_off(CLK := schedule_off);
r_trig_emergency(CLK := emergency_stop);
f_trig_emergency(CLK := emergency_stop);
// =====================================================
// SAFETY CHECKS (highest priority)
// =====================================================
// Priority 1: Emergency Stop
IF emergency_stop THEN
internal_state := FALSE;
emergency_active := TRUE;
error_state := TRUE;
error_code := 1; // Emergency stop active
// Priority 2: Time Limit Check
ELSIF on_timer.Q THEN
internal_state := FALSE;
time_limit_reached := TRUE;
error_state := TRUE;
error_code := 2; // Time limit exceeded
// =====================================================
// CONTROL LOGIC (only when safe)
// =====================================================
ELSE
// Clear safety flags when emergency released
IF f_trig_emergency.Q THEN
emergency_active := FALSE;
error_state := FALSE;
error_code := 0;
END_IF;
// Clear time limit flag when boiler turned off
IF NOT internal_state THEN
time_limit_reached := FALSE;
IF error_code = 2 THEN
error_state := FALSE;
error_code := 0;
END_IF;
END_IF;
// Priority 3: OFF Commands (HA OFF or Schedule OFF)
IF r_trig_ha_off.Q OR r_trig_schedule_off.Q THEN
internal_state := FALSE;
// Priority 4: ON Commands (HA ON or Schedule ON)
ELSIF (r_trig_ha_on.Q OR r_trig_schedule_on.Q) AND NOT time_limit_reached THEN
internal_state := TRUE;
END_IF;
END_IF;
// =====================================================
// TIMER MANAGEMENT
// =====================================================
// ON timer - counts total ON time
on_timer(
IN := internal_state AND NOT emergency_active,
PT := max_on_time
);
// Calculate current ON time in seconds
IF internal_state THEN
on_time_seconds := TIME_TO_DINT(on_timer.ET) / 1000;
remaining_seconds := max_on_seconds - on_time_seconds;
IF remaining_seconds < 0 THEN
remaining_seconds := 0;
END_IF;
ELSE
on_time_seconds := 0;
remaining_seconds := max_on_seconds;
END_IF;
// =====================================================
// OUTPUT ASSIGNMENT
// =====================================================
// Relay control - only ON when state is ON and no emergency
relay_control := internal_state AND NOT emergency_active;
// State output
state := internal_state;
```
### 2.5 State Diagram
```
┌─────────────────────────────────────────┐
│ EMERGENCY STOP │
│ (highest priority) │
│ Sets: emergency_active = TRUE │
│ error_code = 1 │
│ relay_control = FALSE │
└─────────────────────────────────────────┘
┌──────────────────┴──────────────────┐
▼ │
┌───────────┐ │
│ │ │
START ───►│ OFF │◄──────────────────────────────┤
│ │ │
└─────┬─────┘ │
│ │
│ ON Command │
│ (ha_on OR schedule_on) │
│ AND NOT emergency_stop │
│ AND NOT time_limit_reached │
▼ │
┌───────────┐ │
│ │ Time Limit │
│ ON │─────Exceeded──────────────────┘
│ │ (on_timer.Q)
└─────┬─────┘
│ OFF Command
│ (ha_off OR schedule_off)
└──────────────────────────────────────►OFF
```
---
## Part 3: Main Program Integration
### 3.1 PLC_PRG Structure
```iec
PROGRAM PLC_PRG
VAR
// =====================================================
// LIGHTING CONTROL
// =====================================================
// Room instances (fb_switch per redesign)
masterBedroom: fb_switch;
masterBathroom: fb_switch;
bedroom_1: fb_switch;
bedroom_2: fb_switch;
bathroom: fb_switch;
guest_wc: fb_switch;
kitchen: fb_switch;
pantry: fb_switch;
livingRoom: fb_switch;
diningRoom: fb_switch;
entrance: fb_switch;
hallway: fb_switch;
veranda: fb_switch;
front: fb_switch;
back: fb_switch;
side: fb_switch;
// =====================================================
// WATER BOILER CONTROL
// =====================================================
waterBoiler: fb_waterBoiler;
// =====================================================
// GLOBAL COMMANDS
// =====================================================
global_all_lights_off: BOOL;
global_all_lights_on: BOOL;
END_VAR
```
### 3.2 Main Program Logic
```iec
// =====================================================
// PLC_PRG - Main Program Implementation
// =====================================================
// =====================================================
// SECTION 1: LIGHTING CONTROL
// =====================================================
// Master Bedroom (fb_switch: switches + relay_status in, lights out)
masterBedroom(
switches := NVL_Receiver.masterBedroom,
relay_status := EtherCAT_RelayFeedback.masterBedroom // Actual relay states from I/O
);
NVL_Sender.l_masterBedroom := masterBedroom.lights;
EtherCAT_Outputs.masterBedroom_l1 := masterBedroom.lights.l_1;
EtherCAT_Outputs.masterBedroom_l2 := masterBedroom.lights.l_2;
// ... l_3..l_6 and repeat for all rooms
// =====================================================
// SECTION 2: WATER BOILER CONTROL
// =====================================================
waterBoiler(
ha_on := NVL_Receiver.boiler.ha_on,
ha_off := NVL_Receiver.boiler.ha_off,
schedule_on := NVL_Receiver.boiler.schedule_on,
schedule_off := NVL_Receiver.boiler.schedule_off,
emergency_stop := NVL_Receiver.boiler.emergency_stop
OR DI_Emergency_Stop, // Physical button OR remote
max_on_time := T#8H
);
// Map outputs
EtherCAT_Outputs.boiler_relay := waterBoiler.relay_control;
// Status to Node-RED
NVL_Sender.boiler_status.state := waterBoiler.state;
NVL_Sender.boiler_status.relay_output := waterBoiler.relay_control;
NVL_Sender.boiler_status.on_time_minutes := DINT_TO_INT(waterBoiler.on_time_seconds / 60);
NVL_Sender.boiler_status.remaining_minutes := DINT_TO_INT(waterBoiler.remaining_seconds / 60);
NVL_Sender.boiler_status.emergency_active := waterBoiler.emergency_active;
NVL_Sender.boiler_status.time_limit_reached := waterBoiler.time_limit_reached;
NVL_Sender.boiler_status.error_state := waterBoiler.error_state;
NVL_Sender.boiler_status.error_code := waterBoiler.error_code;
```
---
## Part 4: I/O Mapping
### 4.1 EtherCAT Digital Outputs
| Address | Function | Description |
|---------|----------|-------------|
| 16#1A00 - 16#1A0E | Lighting | Room light relays (existing) |
| 16#1A0F | Boiler | Water boiler relay (new) |
### 4.2 EtherCAT Digital Inputs (if using physical emergency stop)
| Address | Function | Description |
|---------|----------|-------------|
| 16#1900 - 16#190F | Switches | Room physical switches (existing) |
| TBD | Emergency | Boiler emergency stop button (optional) |
---
## Part 5: Network Variables
### 5.1 NVL_Sender (PLC → Node-RED)
```iec
VAR_GLOBAL
// Lighting status (struct_lights per room, per redesign)
l_masterBedroom: struct_lights;
l_masterBathroom: struct_lights;
l_bedroom_1: struct_lights;
l_bedroom_2: struct_lights;
l_bathroom: struct_lights;
l_guestWc: struct_lights;
l_kitchen: struct_lights;
l_pantry: struct_lights;
l_livingRoom: struct_lights;
l_dinningRoom: struct_lights;
l_entrance: struct_lights;
l_hallway: struct_lights;
l_outVeranda: struct_lights;
l_outFront: struct_lights;
l_outBack: struct_lights;
l_outSide: struct_lights;
// Boiler status (NEW)
boiler_status: struct_boiler_status;
END_VAR
```
### 5.2 NVL_Receiver (Node-RED → PLC)
```iec
VAR_GLOBAL
// Lighting commands (struct_switches per room, per redesign)
masterBedroom: struct_switches;
masterBathroom: struct_switches;
bedroom_1: struct_switches;
bedroom_2: struct_switches;
bathroom: struct_switches;
guestWc: struct_switches;
kitchen: struct_switches;
pantry: struct_switches;
livingRoom: struct_switches;
dinningRoom: struct_switches;
entrance: struct_switches;
hallway: struct_switches;
outVeranda: struct_switches;
outFront: struct_switches;
outBack: struct_switches;
outSide: struct_switches;
// Boiler commands (NEW)
boiler: struct_boiler_commands;
END_VAR
```
---
## Part 6: Error Codes Reference
### Boiler Error Codes
| Code | Name | Description | Action |
|------|------|-------------|--------|
| 0 | No Error | Normal operation | None |
| 1 | Emergency Stop | Emergency stop activated | Release emergency stop, then send ON command |
| 2 | Time Limit | Maximum ON time exceeded | Send OFF command, then ON again to reset |
---
## Part 7: Testing Checklist
### Lighting Tests
- [ ] HA ON command turns light ON
- [ ] HA OFF command turns light OFF
- [ ] Zigbee toggle switches light state
- [ ] All-off command turns all lights OFF
- [ ] All-on command turns all lights ON
- [ ] Relay status feedback is accurate
- [ ] Multiple commands in sequence work correctly
### Boiler Tests
- [ ] HA ON command starts boiler
- [ ] HA OFF command stops boiler
- [ ] Schedule ON command works
- [ ] Schedule OFF command works
- [ ] Emergency stop immediately stops boiler
- [ ] Emergency stop prevents ON commands
- [ ] Releasing emergency stop allows normal operation
- [ ] Time limit auto-shutoff works (test with short time)
- [ ] ON time counter is accurate
- [ ] Remaining time is accurate
- [ ] Error codes are set correctly
- [ ] Status feedback to HA is correct
---
## Implementation Notes
1. **Redesign alignment**: Lighting control (Part 1) follows **`docs/redesign/fb_switch-redesign-recommendation.md`**: flat `struct_switches` / `struct_lights`, `fb_lightControl` (HA ON/OFF + Zigbee toggle, no all_on/all_off), `fb_switch` with global commands applied by overwriting outputs. Node-RED should send `ha_l1_on`/`ha_l1_off` and `zigbee_sw1` (edge) per the redesign.
2. **Backward Compatibility**: The new lighting structure replaces the old toggle-only logic; Node-RED and HA need to be updated to the new command format.
2. **Emergency Stop**: Can be either a physical button (EtherCAT input) or a virtual command from Home Assistant/Node-RED.
3. **Time Limit Configuration**: Default is 8 hours but can be changed via `max_on_time` parameter or Node-RED configuration.
4. **Task Configuration**: Run on EtherCAT_Task (4ms cycle) for responsive control.
---
**Document Version**: 1.1
**Created**: 2026-02-07
**Updated**: Aligned Part 1 (Lighting) with `docs/redesign/fb_switch-redesign-recommendation.md`.
**Status**: Design Complete - Ready for Implementation