# 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. It’s 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 don’t “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 doesn’t 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. That’s a refinement of the same idea, not a different architecture. So: you don’t *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 they’re 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