(*
  POU: fb_boiler
  Simple ON/OFF boiler: time limit + emergency stop. Priority: Emergency > Time limit > OFF > ON.
*)
FUNCTION_BLOCK fb_boiler
VAR_INPUT
    ha_on: BOOL;
    ha_off: BOOL;
    schedule_on: BOOL;
    schedule_off: BOOL;
    emergency_stop: BOOL;
    max_on_time: TIME := T#8H;
END_VAR
VAR_OUTPUT
    relay_control: BOOL;
    state: BOOL;
    on_time_seconds: DINT;
    remaining_seconds: DINT;
    emergency_active: BOOL;
    time_limit_reached: BOOL;
    error_state: BOOL;
    error_code: INT;
END_VAR
VAR
    internal_state: BOOL := FALSE;
    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;
    on_timer: TON;
    max_on_seconds: DINT;
END_VAR

max_on_seconds := TIME_TO_DINT(max_on_time) / 1000;

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

IF emergency_stop THEN
    internal_state := FALSE;
    emergency_active := TRUE;
    error_state := TRUE;
    error_code := 1;
ELSIF on_timer.Q THEN
    internal_state := FALSE;
    time_limit_reached := TRUE;
    error_state := TRUE;
    error_code := 2;
ELSE
    IF f_trig_emergency.Q THEN
        emergency_active := FALSE;
        error_state := FALSE;
        error_code := 0;
    END_IF;
    IF NOT internal_state THEN
        time_limit_reached := FALSE;
        IF error_code = 2 THEN
            error_state := FALSE;
            error_code := 0;
        END_IF;
    END_IF;
    IF r_trig_ha_off.Q OR r_trig_schedule_off.Q THEN
        internal_state := FALSE;
    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;

on_timer(IN := internal_state AND NOT emergency_active, PT := max_on_time);

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;

relay_control := internal_state AND NOT emergency_active;
state := internal_state;
