PLC application sources (NVL, rooms, boiler), HA/Node-RED integration guide, and NVL patch utilities. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
4.4 KiB
Smalltalk
152 lines
4.4 KiB
Smalltalk
(* === DECLARATION === *)
|
|
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
|
|
|
|
(* === IMPLEMENTATION === *)
|
|
// =====================================================
|
|
// 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;
|