- Introduced new naming conventions for data types and function blocks to align with the redesign - Updated references in the documentation to reflect changes from `fb_switch` to `fb_room` and `fb_lightControl` to `fb_light` - Enhanced clarity in the mapping of existing to new names for better understanding of the redesign This update improves consistency and readability in the documentation for future development.
822 lines
30 KiB
Markdown
822 lines
30 KiB
Markdown
# 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)
|
||
|
||
> **Naming convention**: New types, FBs, program, and NVLs use **different names** (no suffix) so the
|
||
> redesign can run alongside the existing project. Existing names (`struct_switches`, `fb_switch`, `PLC_PRG`, etc.) are left unchanged.
|
||
|
||
### Name Mapping (existing → new, no suffix)
|
||
|
||
| Existing name (do not touch) | New name | Kind |
|
||
|------------------------------|----------|------|
|
||
| `struct_switches` | `struct_room_cmds` | DUT (type) |
|
||
| `struct_lights` | `struct_room_outs` | DUT (type) |
|
||
| `fb_toogleButton` | `fb_light` | Function Block |
|
||
| `fb_switch` | `fb_room` | Function Block |
|
||
| — (new) | `fb_boiler` | Function Block |
|
||
| — (new) | `struct_boiler_cmd` | DUT (type) |
|
||
| — (new) | `struct_boiler_status` | DUT (type) |
|
||
| `PLC_PRG` | `PLC_App` | Program |
|
||
| `Lights` | (merged into `PLC_App`) | Program |
|
||
| `NVL_Sender` | `NVL_Out` | NVL |
|
||
| `NVL_Receiver` | `NVL_In` | NVL |
|
||
|
||
## System Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ CODESYS PLC (Raspberry Pi) │
|
||
├─────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
||
│ │ NVL_In │ │ MainTask │ │ NVL_Out │ │
|
||
│ │ (from Node-RED)│ │ (4ms cycle) │ │ (to Node-RED) │ │
|
||
│ └────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘ │
|
||
│ │ │ │ │
|
||
│ ▼ ▼ ▲ │
|
||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||
│ │ PLC_App │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
|
||
│ │ │ Lights │ │ Boiler │ │ Safety_Monitor │ │ │
|
||
│ │ │ (section) │ │ (section) │ │ (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_room_cmds` / `struct_room_outs`, `fb_light` with HA ON/OFF + Zigbee toggle only, and `fb_room` 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_room_cmds (flat, for Node-RED/JSON compatibility)
|
||
|
||
```iec
|
||
TYPE struct_room_cmds :
|
||
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_room_outs (flat, control + status feedback)
|
||
|
||
```iec
|
||
TYPE struct_room_outs :
|
||
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_light (per Redesign)
|
||
|
||
Individual light control: HA ON/OFF + Zigbee toggle only. No all_on/all_off inside this FB.
|
||
|
||
```iec
|
||
FUNCTION_BLOCK fb_light
|
||
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_light - 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_room (per Redesign)
|
||
|
||
Room-level block: 6× fb_light; global commands applied by overwriting outputs.
|
||
|
||
```iec
|
||
FUNCTION_BLOCK fb_room
|
||
VAR_INPUT
|
||
switches: struct_room_cmds;
|
||
relay_status: struct_room_outs; // Read from EtherCAT outputs (actual relay states)
|
||
END_VAR
|
||
VAR_OUTPUT
|
||
lights: struct_room_outs;
|
||
END_VAR
|
||
VAR
|
||
l1: fb_light;
|
||
l2: fb_light;
|
||
l3: fb_light;
|
||
l4: fb_light;
|
||
l5: fb_light;
|
||
l6: fb_light;
|
||
END_VAR
|
||
```
|
||
|
||
#### Algorithm Logic
|
||
|
||
```iec
|
||
// =====================================================
|
||
// fb_room - 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_cmd :
|
||
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_boiler
|
||
|
||
Simple ON/OFF boiler control with time limit and emergency stop.
|
||
|
||
```iec
|
||
FUNCTION_BLOCK fb_boiler
|
||
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_boiler - 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_App structure
|
||
|
||
```iec
|
||
PROGRAM PLC_App
|
||
VAR
|
||
// =====================================================
|
||
// LIGHTING CONTROL
|
||
// =====================================================
|
||
// Room instances (fb_room)
|
||
masterBedroom: fb_room;
|
||
masterBathroom: fb_room;
|
||
bedroom_1: fb_room;
|
||
bedroom_2: fb_room;
|
||
bathroom: fb_room;
|
||
guest_wc: fb_room;
|
||
kitchen: fb_room;
|
||
pantry: fb_room;
|
||
livingRoom: fb_room;
|
||
diningRoom: fb_room;
|
||
entrance: fb_room;
|
||
hallway: fb_room;
|
||
veranda: fb_room;
|
||
front: fb_room;
|
||
back: fb_room;
|
||
side: fb_room;
|
||
|
||
// =====================================================
|
||
// WATER BOILER CONTROL
|
||
// =====================================================
|
||
boiler: fb_boiler;
|
||
|
||
// =====================================================
|
||
// GLOBAL COMMANDS
|
||
// =====================================================
|
||
global_all_lights_off: BOOL;
|
||
global_all_lights_on: BOOL;
|
||
END_VAR
|
||
```
|
||
|
||
### 3.2 Main Program Logic
|
||
|
||
```iec
|
||
// =====================================================
|
||
// PLC_App - Main Program Implementation
|
||
// =====================================================
|
||
|
||
// =====================================================
|
||
// SECTION 1: LIGHTING CONTROL
|
||
// =====================================================
|
||
|
||
// Master Bedroom (fb_room: switches + relay_status in, lights out)
|
||
masterBedroom(
|
||
switches := NVL_In.masterBedroom,
|
||
relay_status := EtherCAT_RelayFeedback.masterBedroom // Actual relay states from I/O
|
||
);
|
||
NVL_Out.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
|
||
// =====================================================
|
||
|
||
boiler(
|
||
ha_on := NVL_In.boiler.ha_on,
|
||
ha_off := NVL_In.boiler.ha_off,
|
||
schedule_on := NVL_In.boiler.schedule_on,
|
||
schedule_off := NVL_In.boiler.schedule_off,
|
||
emergency_stop := NVL_In.boiler.emergency_stop
|
||
OR DI_Emergency_Stop, // Physical button OR remote
|
||
max_on_time := T#8H
|
||
);
|
||
|
||
// Map outputs
|
||
EtherCAT_Outputs.boiler_relay := boiler.relay_control;
|
||
|
||
// Status to Node-RED
|
||
NVL_Out.boiler_status.state := boiler.state;
|
||
NVL_Out.boiler_status.relay_output := boiler.relay_control;
|
||
NVL_Out.boiler_status.on_time_minutes := DINT_TO_INT(boiler.on_time_seconds / 60);
|
||
NVL_Out.boiler_status.remaining_minutes := DINT_TO_INT(boiler.remaining_seconds / 60);
|
||
NVL_Out.boiler_status.emergency_active := boiler.emergency_active;
|
||
NVL_Out.boiler_status.time_limit_reached := boiler.time_limit_reached;
|
||
NVL_Out.boiler_status.error_state := boiler.error_state;
|
||
NVL_Out.boiler_status.error_code := boiler.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_Out (PLC → Node-RED)
|
||
|
||
```iec
|
||
VAR_GLOBAL
|
||
// Lighting status (struct_room_outs per room)
|
||
l_masterBedroom: struct_room_outs;
|
||
l_masterBathroom: struct_room_outs;
|
||
l_bedroom_1: struct_room_outs;
|
||
l_bedroom_2: struct_room_outs;
|
||
l_bathroom: struct_room_outs;
|
||
l_guestWc: struct_room_outs;
|
||
l_kitchen: struct_room_outs;
|
||
l_pantry: struct_room_outs;
|
||
l_livingRoom: struct_room_outs;
|
||
l_dinningRoom: struct_room_outs;
|
||
l_entrance: struct_room_outs;
|
||
l_hallway: struct_room_outs;
|
||
l_outVeranda: struct_room_outs;
|
||
l_outFront: struct_room_outs;
|
||
l_outBack: struct_room_outs;
|
||
l_outSide: struct_room_outs;
|
||
|
||
// Boiler status
|
||
boiler_status: struct_boiler_status;
|
||
END_VAR
|
||
```
|
||
|
||
### 5.2 NVL_In (Node-RED → PLC)
|
||
|
||
```iec
|
||
VAR_GLOBAL
|
||
// Lighting commands (struct_room_cmds per room)
|
||
masterBedroom: struct_room_cmds;
|
||
masterBathroom: struct_room_cmds;
|
||
bedroom_1: struct_room_cmds;
|
||
bedroom_2: struct_room_cmds;
|
||
bathroom: struct_room_cmds;
|
||
guestWc: struct_room_cmds;
|
||
kitchen: struct_room_cmds;
|
||
pantry: struct_room_cmds;
|
||
livingRoom: struct_room_cmds;
|
||
dinningRoom: struct_room_cmds;
|
||
entrance: struct_room_cmds;
|
||
hallway: struct_room_cmds;
|
||
outVeranda: struct_room_cmds;
|
||
outFront: struct_room_cmds;
|
||
outBack: struct_room_cmds;
|
||
outSide: struct_room_cmds;
|
||
|
||
// Boiler commands
|
||
boiler: struct_boiler_cmd;
|
||
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_room_cmds` / `struct_room_outs`, `fb_light` (HA ON/OFF + Zigbee toggle), `fb_room` 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
|