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

29 KiB
Raw Blame History

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)

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)

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.

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)

// =====================================================
// 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.

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

// =====================================================
// 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)

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)

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.

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

// =====================================================
// 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

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

// =====================================================
// 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)

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)

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.

  3. Emergency Stop: Can be either a physical button (EtherCAT input) or a virtual command from Home Assistant/Node-RED.

  4. Time Limit Configuration: Default is 8 hours but can be changed via max_on_time parameter or Node-RED configuration.

  5. 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