- 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>
452 lines
11 KiB
Markdown
452 lines
11 KiB
Markdown
# 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**:
|
|
```iec
|
|
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
|
|
|
|
## Recommended Redesign
|
|
|
|
### New Data Structure
|
|
|
|
```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
|
|
|
|
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
|
|
|
|
```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 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**:
|
|
|
|
```iec
|
|
// 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
|
|
|
|
```iec
|
|
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**:
|
|
```javascript
|
|
// 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**:
|
|
```javascript
|
|
// 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**:
|
|
```javascript
|
|
// 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
|
|
|
|
```yaml
|
|
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**:
|
|
|
|
```iec
|
|
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**:
|
|
```iec
|
|
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
|