Files
kkelomatic_home/docs/redesign/fb_switch-redesign-recommendation.md
nearxos bf7bd56fe7 Initial commit: Home automation docs and CODESYS project
- Reorganized project: codesys/, docs/codesys|redesign|integration|reference/, scripts/
- CODESYS project and exports in codesys/
- Documentation index in docs/README.md
- Redesign and light naming configuration
- Water boiler control and safety design

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:52:46 +02:00

11 KiB

fb_switch Redesign Recommendation

Current Implementation Issues

Problem 1: Toggle-Based Control

Issue: The current fb_toogleButton implementation uses toggle logic, which causes state desynchronization with Home Assistant.

Why this is a problem:

  • Home Assistant sends explicit ON/OFF commands
  • CODESYS treats every input as a toggle trigger
  • If HA thinks light is ON and sends ON again, CODESYS toggles it OFF
  • State becomes out of sync between HA and actual relay state

Example Scenario:

  1. HA sends sw_1 = TRUE (turn ON)
  2. CODESYS toggles: OFF → ON ✓
  3. HA sends sw_1 = TRUE again (thinking it's already ON)
  4. CODESYS toggles: ON → OFF ✗ (wrong!)

Problem 2: No Direct Set/Reset Commands

Issue: Current structure only has toggle triggers, no explicit ON/OFF commands.

Current Structure:

struct_switches:
    sw_1: BOOL;  // Treated as toggle trigger
    all_on: BOOL;  // Toggle trigger
    all_off: BOOL;  // Toggle trigger

What's needed:

  • Separate ON and OFF commands
  • Edge detection for Zigbee switches (toggle behavior)
  • Direct set/reset for Home Assistant

Problem 3: Status Feedback Limitations

Issue: Current lights.l_X outputs represent toggle button state, not actual relay status.

Problems:

  • No direct feedback from physical relay outputs
  • Status may not match actual relay state if relay fails
  • No way to detect relay hardware failures

New Data Structure

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

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

New Function Block: fb_lightControl

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 detection for Zigbee
    zigbee_prev: BOOL;
    zigbee_edge: BOOL;
    
    // Command processing
    ha_on_prev: BOOL;
    ha_off_prev: BOOL;
    ha_on_edge: BOOL;
    ha_off_edge: BOOL;
    
    // Internal state
    light_state: BOOL;
    
    // Edge triggers
    r_trig_ha_on: R_TRIG;
    r_trig_ha_off: R_TRIG;
    r_trig_zigbee: R_TRIG;
END_VAR

Implementation Logic

Priority Order (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 - If no commands

Logic Flow:

// Edge detection for commands
r_trig_ha_on(CLK := ha_on);
ha_on_edge := r_trig_ha_on.Q;

r_trig_ha_off(CLK := ha_off);
ha_off_edge := r_trig_ha_off.Q;

r_trig_zigbee(CLK := zigbee_sw);
zigbee_edge := r_trig_zigbee.Q;

// State machine
IF ha_off_edge THEN
    light_state := FALSE;
ELSIF ha_on_edge THEN
    light_state := TRUE;
ELSIF zigbee_edge THEN
    light_state := NOT light_state;
END_IF

// Output assignment
light_output := light_state;

// Status feedback (read from actual relay)
light_status := relay_status;

New fb_switch Implementation

FUNCTION_BLOCK fb_switch
VAR_INPUT
    switches: struct_switches;
    relay_status: struct_lights;  // Read from EtherCAT outputs
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

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

// ... (repeat for l3-l6)

// Global Commands
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;

Node-RED Integration Changes

Current Flow (Toggle-Based)

Zigbee Switch → MQTT → Node-RED → CODESYS (sw_1 = TRUE) → Toggle
Home Assistant → MQTT → Node-RED → CODESYS (sw_1 = TRUE) → Toggle (WRONG!)

New Flow (Command-Based)

Zigbee Switch → MQTT → Node-RED → CODESYS (zigbee_sw1 = edge) → Toggle
Home Assistant → MQTT → Node-RED → CODESYS (ha_l1_on = TRUE) → Set ON
Home Assistant → MQTT → Node-RED → CODESYS (ha_l1_off = TRUE) → Set OFF

Node-RED Flow Example

For Zigbee Switches:

// Detect button press (edge detection)
if (msg.payload.action === "single") {
    // Send edge trigger to CODESYS
    msg.payload = {
        zigbee_sw1: true  // Edge detected
    };
    return msg;
}

For Home Assistant:

// Direct ON/OFF commands
if (msg.payload === "ON") {
    msg.payload = {
        ha_l1_on: true,
        ha_l1_off: false
    };
} else if (msg.payload === "OFF") {
    msg.payload = {
        ha_l1_on: false,
        ha_l1_off: true
    };
}
return msg;

Status Feedback to Home Assistant:

// Read status from CODESYS
const status = msg.payload.l_1_status;
// Publish to HA
msg.topic = "homeassistant/light/room1_light1/state";
msg.payload = status ? "ON" : "OFF";
return msg;

Home Assistant Configuration

MQTT Light Entity

light:
  - platform: mqtt
    name: "Room 1 Light 1"
    state_topic: "homeassistant/light/room1_light1/state"
    command_topic: "homeassistant/light/room1_light1/set"
    state_value_template: "{{ value_json.state }}"
    command_on_template: '{"state":"ON"}'
    command_off_template: '{"state":"OFF"}'
    qos: 1
    retain: true

Node-RED Flow for HA Integration

[HA Command] → [Parse] → [Convert to CODESYS format] → [Network Variable]
[CODESYS Status] → [Network Variable] → [Convert to HA format] → [HA State]

Benefits of Redesign

1. State Synchronization

  • HA always knows actual relay state
  • No state desynchronization
  • Reliable ON/OFF commands

2. Multiple Control Sources

  • HA uses set/reset commands
  • Zigbee switches use toggle (edge detection)
  • Both work simultaneously without conflicts

3. Status Feedback

  • Actual relay state read from EtherCAT
  • Hardware failure detection possible
  • Accurate status in Home Assistant

4. Better Debugging

  • Clear separation of commands
  • Can trace which source triggered action
  • Easier troubleshooting

5. Extensibility

  • Easy to add more control sources
  • Can add priority levels
  • Can add scheduling/automation

Migration Path

Step 1: Update Data Structures

  1. Modify struct_switches to include HA commands and Zigbee inputs
  2. Modify struct_lights to include status feedback
  3. Keep old structure temporarily for backward compatibility

Step 2: Create New Function Blocks

  1. Implement fb_lightControl with new logic
  2. Update fb_switch to use new function blocks
  3. Test in simulation first

Step 3: Update Node-RED Flows

  1. Modify flows to send HA commands (ON/OFF)
  2. Keep Zigbee flows with edge detection
  3. Add status feedback flows

Step 4: Update Home Assistant

  1. Configure MQTT light entities
  2. Test control from HA
  3. Verify status updates

Step 5: Testing

  1. Test HA control (ON/OFF)
  2. Test Zigbee switch control (toggle)
  3. Test status feedback
  4. Test both simultaneously

Alternative: Simpler Approach (If Full Redesign Too Complex)

If the full redesign is too complex, here's a simpler improvement:

Simplified Improvement

Keep toggle logic but add state tracking:

FUNCTION_BLOCK fb_lightControl_Simple
VAR_INPUT
    toggle: BOOL;  // Toggle command (edge detected)
    set_on: BOOL;  // Set ON command (edge detected)
    set_off: BOOL; // Set OFF command (edge detected)
    relay_status: BOOL;  // Actual relay state
END_VAR
VAR_OUTPUT
    output: BOOL;
    status: BOOL;
END_VAR
VAR
    state: BOOL;
    r_trig_toggle: R_TRIG;
    r_trig_on: R_TRIG;
    r_trig_off: R_TRIG;
END_VAR

// Edge detection
r_trig_toggle(CLK := toggle);
r_trig_on(CLK := set_on);
r_trig_off(CLK := set_off);

// State machine
IF r_trig_off.Q THEN
    state := FALSE;
ELSIF r_trig_on.Q THEN
    state := TRUE;
ELSIF r_trig_toggle.Q THEN
    state := NOT state;
END_IF;

output := state;
status := relay_status;  // Read from actual relay

Data Structure:

struct_switches:
    sw1_toggle: BOOL;  // Zigbee toggle
    sw1_on: BOOL;      // HA ON command
    sw1_off: BOOL;     // HA OFF command
    // ... repeat for sw2-sw6

This simpler approach:

  • Keeps existing structure mostly intact
  • Adds explicit ON/OFF commands for HA
  • Maintains toggle for Zigbee
  • Adds status feedback
  • ⚠️ Still requires data structure changes
  • ⚠️ Less clean than full redesign

Recommendation

I recommend the full redesign because:

  1. Better long-term maintainability
  2. Clear separation of concerns
  3. Easier to extend and debug
  4. Proper state management
  5. Industry-standard approach

However, if time is limited, the simplified approach is a good compromise that addresses the main issues.


Next Steps:

  1. Review this recommendation
  2. Decide on full redesign vs. simplified approach
  3. Plan migration strategy
  4. Update Node-RED flows accordingly
  5. Test thoroughly before deploying