﻿FUNCTION_BLOCK "Valve_Proc"
TITLE = 电动执行机构，可选阀位反馈
{ S7_Optimized_Access := 'FALSE' }
KNOW_HOW_PROTECT
AUTHOR : Goosy
FAMILY : GooLib
NAME : ValvePrc
VERSION : 1.3

VAR_INPUT
    CP : Bool;                     // 全关位
    OP : Bool;                     // 全开位
    remote : Bool := TRUE;         // 远程
    error : Bool;                  // 错误
    AI : Word := #S7_AI_MIN_WORD;  // 阀位采集值
    VP_raw AT AI : Int;
    zero_raw : Int := S7_ZERO;     // 零点原始值（4mA对应数值）0
    span_raw : Int := S7_SPAN;     // 极值原始值（20mA对应数值）27648
    overflow_SP : Int := 28000;    // 上溢出值
    underflow_SP : Int := -500;    // 下溢出值
    VP_SP : Real := 50.0;          // 设定值
    FT_zone : Real := 0.5;         // 定位容错区
    action_time : DInt := 100000;  // 动作时间，完成阀动作的最长时间，无阀位时在该时间后判断是否离线 (单位毫秒)
    action_DR AT action_time : Time;
    signal_time : DInt := 500;     // 信号时间，停止等信号要保持持续的最短时间，有阀位时在该时间后判断阀是否离线 (单位毫秒 阀位有效时)
    signal_DR AT signal_time : Time;
END_VAR

VAR_OUTPUT
    close_action : Bool;   // 正在执行关阀
    open_action : Bool;    // 正在执行开阀
    stop_action : Bool;    // 停止执行开关阀
    control_action : Bool; // 2位单线控制 1：开阀 0：关阀
    valve_error : Bool;    // 阀体故障或信号故障，无法操作
    VP_error : Bool ;      // 阀位值无效，断路、短路、溢出
    action_success : Bool; // 阀动作成功，当 action_success action_error 都不为 TRUE 时，表示不能判断
    action_error : Bool;   // 执行错误，即开、关、停在指定时间后无反馈
    action : Bool ;        // 阀动作中
    VP : Real;             // 阀位
END_VAR

VAR_IN_OUT
    close_CMD : Bool;      // 关阀命令
    open_CMD : Bool;       // 开阀命令
    position_CMD : Bool;   // 定位命令
    stop_CMD : Bool;       // 保位命令，即停止，保持当前阀位
END_VAR

VAR
    executable { ExternalVisible := 'False'} : BOOL;      // 可执行
    action_trigger { ExternalVisible := 'False'} : Bool;  // 阀动作触发
    signal { ExternalVisible := 'False'} : Bool;          // 阀信号中
    mode : Word;                                          // 当前模式
    last_mode : Word;                                     // 上一循环时的模式
    last_VP : Real;                                       // 上一次阀位
    signal_TOF { ExternalVisible := 'False'} : TOF_TIME;
    action_TOF { ExternalVisible := 'False'} : TOF_TIME;
END_VAR

VAR_TEMP
    ERR : Bool;             // 测量错误
    value : Real;
END_VAR

VAR CONSTANT
    S7_ZERO : Int := 0;
    S7_SPAN : Int := 27648;
    S7_AI_MIN : Int := -32768;
    S7_AI_MIN_WORD : Word := WORD#16#8000;
    S7_AI_MAX : Int := 32767;
    S7_AI_MAX_WORD : Word := WORD#16#7FFF;
    STOP_STATUS : Word := WORD#16#0000;
    CLOSE_STATUS : Word := WORD#16#0001;
    OPEN_STATUS : Word := WORD#16#0002;
    STOPPING_STATUS : Word := WORD#16#0004;
    CLOSING_STATUS : Word := WORD#16#0008;
    OPENNING_STATUS : Word := WORD#16#0010;
    POSITION_STATUS : Word := WORD#16#0020;
    ZERO : Real := 0.0;
    SPAN : Real := 100.0;
    INVALID_VALUE : Real := -100.0;
END_VAR


BEGIN
//**********************
// 阀位反馈
//**********************

// 判断输入值的有效性
IF #VP_raw = #S7_AI_MIN OR #VP_raw = #S7_AI_MAX THEN
    // 非测量输入
    #ERR := TRUE;
ELSIF #VP_raw > #overflow_SP OR #VP_raw < #underflow_SP THEN
    // 溢出
    #ERR := TRUE;
ELSE
    // 正常输入
    #ERR := FALSE;
END_IF;

// 设定阀位值及其有效性
#VP_error := #ERR;
IF #ERR THEN
    #VP := #INVALID_VALUE; // 无效时不计算
ELSE
    #value := (#VP_raw - #zero_raw)
            * (#SPAN - #ZERO)
            / (#span_raw - #zero_raw)
            + #ZERO;
    #VP := #value;
END_IF;

//**********************
// 阀动作处理
//**********************

// 准备动作变量
#valve_error := #error OR (#CP AND #OP); //阀故障
#executable := #remote AND NOT #valve_error; // 可执行
#action := #mode = #STOPPING_STATUS OR #mode = #OPENNING_STATUS OR #mode = #CLOSING_STATUS OR #mode = #POSITION_STATUS;
IF NOT #executable THEN
    // 切换至停止状态
    // 开到位和关到位情况会在后继代码中做修正，无须在此切换
    IF #action THEN
        #mode := #STOP_STATUS;
    END_IF;
    // 输出线圈会在后继代码中复位
    #signal := FALSE;
    #action := FALSE;
    #action_success := FALSE;
    #action_error := FALSE;
    #close_CMD := FALSE;
    #open_CMD := FALSE;
    #position_CMD := FALSE;
    #stop_CMD := FALSE;
    // 重置动作计时
    RESET_TIMER(TIMER := #signal_TOF);
    RESET_TIMER(TIMER := #action_TOF);
END_IF;
// action 上升沿
#action_trigger := #last_mode <> #mode AND #action; // action 已隐含 executable
#last_mode := #mode;
IF #action_trigger THEN // 重置离线错误，记录动作前的阀位
    #action_success := FALSE;
    #action_error := FALSE;
    IF NOT #VP_error THEN
        #last_VP := #value;
    END_IF;
END_IF;

// 动作计时
#signal_TOF(
    IN := #action_trigger,
    PT := #signal_DR);
#action_TOF(
    IN := #action_trigger,
    PT := #action_DR);

// 停止命令优先
IF #stop_CMD THEN
    IF #mode = #STOPPING_STATUS THEN
        #mode := #STOP_STATUS; // 确保停止状态可以继续响应 stop_CMD
    ELSE
        #mode := #STOPPING_STATUS;
        #stop_CMD := FALSE;
    END_IF;
    #close_CMD := FALSE;
    #open_CMD := FALSE;
    #position_CMD := FALSE;
END_IF;

// 停阀动作
IF #mode = #STOPPING_STATUS THEN
    #open_action := FALSE;
    #close_action := FALSE;
    #stop_action := TRUE;
END_IF;
// 复位动作
IF #mode = #STOP_STATUS THEN
    #open_action := FALSE;
    #close_action := FALSE;
    #stop_action := FALSE;
END_IF;

// 全开状态的判定和动作
IF #OP AND #mode <> #CLOSING_STATUS AND #mode <> #POSITION_STATUS THEN
    IF #mode = #OPENNING_STATUS THEN
        #action_success := TRUE;
    END_IF;
    #mode := #OPEN_STATUS;
END_IF;
// 复位动作
IF #mode = #OPEN_STATUS THEN
    #open_CMD := FALSE;
    #close_action := FALSE;
    #open_action := FALSE;
    #control_action := TRUE;
    #stop_action := FALSE;
END_IF;

// 全关状态的判定
IF #CP AND #mode <> #OPENNING_STATUS AND #mode <> #POSITION_STATUS THEN
    IF #mode = #CLOSING_STATUS THEN
        #action_success := TRUE;
    END_IF;
    #mode := #CLOSE_STATUS;
END_IF;
// 复位动作
IF #mode = #CLOSE_STATUS THEN
    #close_CMD := FALSE;
    #open_action := FALSE;
    #close_action := FALSE;
    #control_action := FALSE;
    #stop_action := FALSE;
END_IF;

// 开阀
IF #open_CMD THEN
    IF #mode = #OPENNING_STATUS THEN
        #mode := #STOP_STATUS; // 确保 OPENNING_STATUS 可以继续响应 open_CMD
    ELSE
        #mode := #OPENNING_STATUS;
        #open_CMD := FALSE;
    END_IF;
    #close_CMD := FALSE;
    #position_CMD := FALSE;
END_IF;
// 动作
IF #mode = #OPENNING_STATUS THEN
    #close_action := FALSE;
    #open_action := TRUE;
    #control_action := TRUE;
    #stop_action := FALSE;
END_IF;

// 关阀
IF #close_CMD THEN
    IF #mode = #CLOSING_STATUS THEN
        #mode := #STOP_STATUS; // 确保 CLOSING_STATUS 可以继续响应 close_CMD
    ELSE
        #mode := #CLOSING_STATUS;
        #close_CMD := FALSE;
    END_IF;
    #open_CMD := FALSE;
    #position_CMD := FALSE;
END_IF;
// 动作
IF #mode = #CLOSING_STATUS THEN
    #close_action := TRUE;
    #open_action := FALSE;
    #control_action := FALSE;
    #stop_action := FALSE;
END_IF;

// 指定阀位控制
IF #VP_error THEN // 阀位无效时关闭指定阀位命令
    #position_CMD := FALSE;
    IF #mode = #POSITION_STATUS THEN
        #action_error := TRUE;
        #mode := #STOPPING_STATUS;
    END_IF;
END_IF;
IF #position_CMD THEN
    IF #mode = #POSITION_STATUS THEN
        #mode := #STOP_STATUS; // 确保 POSITION_STATUS 可以继续响应 position_CMD
    ELSE
        #mode := #POSITION_STATUS;
        #position_CMD := FALSE;
    END_IF;
END_IF;
// 动作
IF #mode = #POSITION_STATUS THEN
    IF #value > #VP_SP + #FT_zone THEN //当设定值容错区低于阀位，关阀动作
        #close_action := TRUE;
        #open_action := FALSE;
        #stop_action := FALSE;
    ELSIF #value < #VP_SP - #FT_zone THEN //当设定值容错区高于阀位，开阀动作
        #close_action := FALSE;
        #open_action := TRUE;
        #stop_action := FALSE;
    ELSE //当设定值在阀位容错区内，停止动阀，复位定位命令
        #close_action := FALSE;
        #open_action := FALSE;
        #position_CMD := FALSE;
        #action_success := TRUE;
        #mode := #STOPPING_STATUS;
    END_IF;
END_IF;

// 根据有无阀位，取不同的动作无响应时间

// 信号结束时判断执行结果
// signal 已隐含 executable
IF (NOT #signal_TOF.Q) AND #signal THEN
    IF #mode = #STOPPING_STATUS AND NOT #VP_error THEN // 有阀位，判断停止动作结果
        #action_success := (#value < #last_VP + #FT_zone) AND (#value > #last_VP - #FT_zone);
        #action_error := NOT #action_success;
    ELSIF #mode = #STOPPING_STATUS THEN // 无阀位，停止动作无法判断，直接切换状态
        #mode := #STOP_STATUS;
        RESET_TIMER(TIMER := #action_TOF);
    ELSIF #action AND NOT #VP_error THEN // 其它3个动作在信号结束时只能有阀位时判断失败情况
        IF #mode = #CLOSING_STATUS THEN // 关动作判断是否失败
            #action_error := #value > (#last_VP - #FT_zone);
        ELSIF #mode = #OPENNING_STATUS THEN // 开动作判断是否失败
            #action_error := #value < #last_VP + #FT_zone;
        ELSIF #mode = #POSITION_STATUS THEN // 定位动作判断是否失败
            // 这里无需考虑目标阀位对动作的影响，因为进入目标阀位后必定不在 POSITION_STATUS 状态下。
            #action_error := (#value < #last_VP + #FT_zone) AND (#value > #last_VP - #FT_zone);
        END_IF;
    END_IF;
END_IF;
#signal := #signal_TOF.Q;

// 提前结束无须超时检查
IF #action_success THEN
    #action_error := FALSE;
END_IF;
IF (#action_error OR #action_success) AND #action THEN
    #mode := #STOP_STATUS;
    #action := FALSE; // 用于屏蔽下方超时检查
    RESET_TIMER(TIMER := #action_TOF);
END_IF;
// 动作超时判断执行情况(即动作未能提前结束)
IF (NOT #action_TOF.Q) AND #action THEN
    // 隐含 NOT action_error AND NOT action_success
    IF #mode = #STOPPING_STATUS THEN
        // 无阀位时，STOPPING_STATUS 才会延伸到这里
        // 不作检查，仅仅切换至已停状态
        #mode := #STOP_STATUS;
    ELSE
        // 其余 CLOSING_STATUS OPENNING_STATUS POSITION_STATUS 3种状态
        // 这3种状态能延伸到这里，一定因为动作失败
        #action_success := FALSE;
        #action_error := TRUE;
        #mode := #STOP_STATUS;
    END_IF;
END_IF;
END_FUNCTION_BLOCK
