803 lines
29 KiB
Markdown
803 lines
29 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)
|
||
|
||
## 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
|