Files
STM32F4-Base/IEC60870-Slave/104/IEC104_Link.c
T
STF 4c55544f05
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
通信协议代码
2026-10-26 08:31:48 -04:00

1106 lines
47 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 公共链路逻辑:初始化、完整状态校验、I/S序号、k/w窗口、S确认与TESTFR。
* 主站启停见IEC104_Link_Master_StartStop.c,子站启停见IEC104_Link_Slave_StartStop.c。
* 本文件不执行TCP收发;连接调度层串行提交接收、发送完成和时间事件。
*
* 协作边界:本文件定义Internal.h中的跨文件辅助函数,供两个角色实现调用。
* State_Valid按本端角色调用本文件内的static过程校验,不反向依赖角色实现。
* 带_Internal后缀的函数具有外部链接,但只供链路实现使用;其余局部辅助函数
* 采用static。公开入口负责校验,内部步骤按各自前置条件复用已校验的状态。
* 主要提交点:Prepare_I推进vs;Handle_I/S推进va(I另推进vr);
* Complete_I_Send/Complete_S_Ack_Send提交实际发送的N(R),其中I帧完成还推进sent_vs。
*/
#include "IEC104_Link_Common.h"
#include "IEC104_Link_Internal.h"
#include <stddef.h>
/* 仅用于本文件内I/S帧的序号规划;只有READY时才读取快照并编码。 */
typedef enum {
IEC104_PLAN_ERROR,
IEC104_PLAN_SKIPPED,
IEC104_PLAN_READY
} IEC104_PlanResult_t;
/**
* 功能:计算15位IEC-104序号从from前进到to的距离。
* 输入参数:from——起始序号;to——目标序号。
* 输出参数:无。
* 返回值:0至32767范围内的循环距离。
*/
static uint16_t Number_Distance(uint16_t from, uint16_t to)
{
/* 按15位序号环计算距离,使32767到0的距离为1。 */
return (uint16_t)((to - from) & IEC104_SEQUENCE_MASK);
}
/**
* 功能:将15位IEC-104序号循环加1。
* 输入参数:value——当前序号。
* 输出参数:无。
* 返回值:递增后的序号;32767的下一个序号为0。
*/
static uint16_t Number_Increment(uint16_t value)
{
/* 与0x7FFF相与,自动将32768折回0。 */
return (uint16_t)((value + 1U) & IEC104_SEQUENCE_MASK);
}
/**
* 功能:检查序号、稳定状态、标志和TESTFR等公共基础字段。
* 输入参数:state——链路状态。
* 输出参数:无。
* 返回值:公共基础字段合法返回1,否则返回0。
* 说明:S确认、角色和启停过程由后续完整校验检查。
*/
static int Link_State_Fields_Valid(const IEC104_LinkState_t *state)
{
if (state == NULL ||
state->vs > IEC104_SEQUENCE_MASK ||
state->sent_vs > IEC104_SEQUENCE_MASK ||
state->va > IEC104_SEQUENCE_MASK ||
state->vr > IEC104_SEQUENCE_MASK ||
state->acked_nr > IEC104_SEQUENCE_MASK ||
state->s_ack_nr > IEC104_SEQUENCE_MASK) {
return 0;
}
/* 确认位置不得越过本地完成位置;用共同尾端的循环距离检查,兼容序号回绕。 */
if (Number_Distance(state->sent_vs, state->vs) > Number_Distance(state->va, state->vs)) {
return 0;
}
switch (state->dt_state) {
case IEC104_DT_STOPPED:
case IEC104_DT_STARTED:
break;
default:
return 0;
}
switch (state->testfr_state) {
case IEC104_TESTFR_IDLE:
case IEC104_TESTFR_ACT_SEND_PENDING:
case IEC104_TESTFR_CON_PENDING:
return 1;
default:
return 0;
}
}
/**
* 功能:计算已经接收但本端尚未通过I/S帧确认的I帧数量。
* 输入参数:state——序号字段有效的链路状态。
* 输出参数:无。
* 返回值:acked_nr到V(R)之间的15位循环距离。
* 前置条件:state非NULL,acked_nr和vr为15位序号,未确认区间未占满序号环。
* 调用关系:公共确认逻辑及Slave停止排空共用,因此保留外部链接;不重复校验。
*/
uint16_t IEC104_Link_Unconfirmed_Rx_Internal(const IEC104_LinkState_t *state)
{
return Number_Distance(state->acked_nr, state->vr);
}
/**
* 功能:判断接收确认已完成,或已由在途S帧覆盖当前接收位置。
* 输入参数:state——已通过完整合法性检查的链路状态。
* 输出参数:无,不修改state。
* 返回值:1表示acked_nr或在途S帧已覆盖当前V(R),否则返回0。
* 说明:不要求S完成回调已提交;不能用只覆盖旧V(R)的S帧替代最新确认。
* 调用关系:Master准备STOPDT_ACT和接收STOPDT_CON时分别检查,不能缓存旧结果。
* 返回1不推进acked_nr;在途S仍须按FIFO完成,失败仍须断连。
*/
int IEC104_Link_Rx_Ack_Covered_Internal(const IEC104_LinkState_t *state)
{
return state->acked_nr == state->vr ||
(state->s_ack_state == IEC104_S_ACK_SEND_PENDING &&
state->s_ack_nr == state->vr);
}
/**
* 功能:计算已提交发送完成、且尚未被对端确认的I帧数量。
* 输入参数:state——合法链路状态,确认位置没有越过本地完成位置。
* 输出参数:无,不修改state。
* 返回值:需等待对端确认的已完成帧数;仅有排队帧时返回0。
* 说明:ACK必须在对应Complete之后处理,已完成未确认区间为va..sent_vs。
*/
static uint16_t Sent_Unacknowledged_I_Count(const IEC104_LinkState_t *state)
{
return Number_Distance(state->va, state->sent_vs);
}
/**
* 功能:检查普通S确认帧的发送状态和快照组合是否一致。
* 输入参数:state——待检查的链路状态。
* 输出参数:无。
* 返回值:组合一致返回1,否则返回0。
* 说明:调用前必须已经通过Link_State_Fields_Valid()。
*/
static int S_Ack_State_Valid(const IEC104_LinkState_t *state)
{
switch (state->s_ack_state) {
case IEC104_S_ACK_IDLE:
return state->s_ack_nr == 0U;
case IEC104_S_ACK_SEND_PENDING:
/* N(R)=0是合法快照,是否在当前确认区间内留到完成时检查。 */
return 1;
default:
return 0;
}
}
/**
* 功能:检查主站dt_state与dt_procedure组合,拒绝子站专用过程。
* 前置条件:state非NULL,公共字段及S状态已合法,role为CONTROLLING。
* 返回值:IDLE允许两种稳定状态;启动过程须STOPPED,停止过程须STARTED;
* 满足时返回1,其余返回0。只校验组合,不修改状态或验证报文。
* 调用关系:仅供本文件内State_Valid按角色分派,使用static内部链接。
* 不得反调State_Valid/Role_Valid,否则形成递归。
*/
static int Master_State_Consistent(const IEC104_LinkState_t *state)
{
switch (state->dt_procedure) {
case IEC104_DT_PROC_IDLE:
return 1;
case IEC104_DT_PROC_START_ACT_SEND_PENDING:
case IEC104_DT_PROC_START_CON_WAITING:
return state->dt_state == IEC104_DT_STOPPED;
case IEC104_DT_PROC_STOP_REQUESTED:
case IEC104_DT_PROC_STOP_ACT_SEND_PENDING:
case IEC104_DT_PROC_STOP_CON_WAITING:
return state->dt_state == IEC104_DT_STARTED;
default:
return 0;
}
}
/**
* 子站只使用IDLE和STOP_DRAINING;后者表示已停用、等待确认完成。
* CON发送进度归SendBuffer,不参与启停组合校验。
*/
static int Slave_State_Consistent(const IEC104_LinkState_t *state)
{
return state->dt_procedure == IEC104_DT_PROC_IDLE ||
(state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING && state->dt_state == IEC104_DT_STOPPED);
}
/**
* 功能:完整检查链路状态的字段定义域和跨流程一致性。
* 输入参数:state——链路状态;允许NULL,返回0。
* 输出参数:无,不修改state。
* 返回值:公共字段、S状态及本端角色的过程组合合法返回1,否则返回0。
* 调用关系:公共入口直接调用,角色入口经Role_Valid调用;本函数依次检查
* Fields、S_Ack,再分派本文件内的Master/Slave_State_Consistent,不会推进过程。
* 说明:角色末级校验不得反调本函数或Role_Valid,避免递归;本检查不证明事件
* 属于当前连接,也不替代本次N(S)/N(R)、发送长度和FIFO顺序检查。
*/
int IEC104_Link_State_Valid_Internal(const IEC104_LinkState_t *state)
{
if (!Link_State_Fields_Valid(state) || !S_Ack_State_Valid(state)) {
return 0;
}
switch (state->role) {
case IEC104_ROLE_CONTROLLING:
return Master_State_Consistent(state);
case IEC104_ROLE_CONTROLLED:
return Slave_State_Consistent(state);
default:
return 0;
}
}
/**
* 功能:校验链路后生成只含数值的I帧发送进度快照。
* 输入参数:state——本连接链路状态;所有访问须串行。
* 输出参数:progress——仅返回1时写入,不得与state重叠;失败时保持原值。
* 返回值:1表示成功;0表示指针或完整链路状态非法。
* 说明:由本模块把内部序号映射为公开的进度含义,不提交任何接收或完成事件。
*/
int IEC104_Link_Get_Send_Progress(
const IEC104_LinkState_t *state, IEC104_IFrameSendProgress_t *progress)
{
IEC104_IFrameSendProgress_t prepared;
if (progress == NULL || !IEC104_Link_State_Valid_Internal(state)) {
return 0;
}
prepared.next_frame_ns = state->vs;
prepared.next_completion_ns = state->sent_vs;
prepared.peer_ack_nr = state->va;
*progress = prepared;
return 1;
}
/**
* 功能:生成链路观察快照,由Link统一解释未确认接收数量及启停/探测状态。
* 输入参数:state——当前链路;status——非NULL且不与state重叠的输出。
* 返回值:1成功并写status,0参数或完整状态非法;失败不改输出,不预留或推进链路。
* 说明:连接层可据未确认数量结束t2区间;控制动作准入另用Can_Prepare接口。
*/
int IEC104_Link_Get_Status(const IEC104_LinkState_t *state, IEC104_LinkStatus_t *status)
{
IEC104_LinkStatus_t prepared;
if (status == NULL || !IEC104_Link_State_Valid_Internal(state)) {
return 0;
}
prepared.role = state->role;
prepared.dt_state = state->dt_state;
prepared.dt_procedure = state->dt_procedure;
prepared.testfr_state = state->testfr_state;
prepared.unconfirmed_rx_count = IEC104_Link_Unconfirmed_Rx_Internal(state);
*status = prepared;
return 1;
}
/**
* 功能:检查链路是否符合绑定新连接发送管理器的初始状态条件。
* 输入参数:state——待检查状态,允许NULL。
* 输出参数:无,不修改链路。
* 返回值:状态合法、所有序号为0、STOPPED且各过程空闲时返回1,否则0。
* 说明:完整校验同时保证空闲S快照为0;不检查连接历史或实际TCP状态。
*/
int IEC104_Link_Is_Initial_State(const IEC104_LinkState_t *state)
{
return IEC104_Link_State_Valid_Internal(state) &&
state->vs == 0U && state->sent_vs == 0U && state->va == 0U &&
state->vr == 0U && state->acked_nr == 0U &&
state->dt_state == IEC104_DT_STOPPED && state->dt_procedure == IEC104_DT_PROC_IDLE &&
state->testfr_state == IEC104_TESTFR_IDLE && state->s_ack_state == IEC104_S_ACK_IDLE;
}
/**
* 功能:判断对端确认号是否位于V(A)到已提交本地完成位置sent_vs之间。
* 输入参数:state——有效链路状态;receive_number——对端携带的N(R)。
* 输出参数:无。
* 返回值:N(R)为15位且位于va到sent_vs的前向闭区间时返回1;越过完成位置或退回va之前均返回0。
*/
static int Acknowledge_Number_Valid(const IEC104_LinkState_t *state,
uint16_t receive_number)
{
uint16_t outstanding_count;
uint16_t acknowledged_count;
if (receive_number > IEC104_SEQUENCE_MASK) {
return 0;
}
outstanding_count = Number_Distance(state->va, state->sent_vs);
acknowledged_count = Number_Distance(state->va, receive_number);
/* Prepare只预留序号;必须先提交对应Complete,才允许ACK覆盖该帧。 */
return acknowledged_count <= outstanding_count;
}
/**
* 功能:提交一份本地已经完整发送的I/S帧所携带的N(R)快照。
* 输入参数:state——已通过完整校验的链路状态;receive_number——实际上线报文携带的N(R)。
* 输出参数:快照合法时推进acked_nr;非法时不修改状态。
* 返回值:OK表示快照已提交;FATAL表示快照非法。
* 说明:这是I/S完成流程共用的内部操作,不判断具体帧类型或发送长度。
* Complete_I_Send和Complete_S_Ack_Send已校验state;这里仅校验确认号定义域
* 和acked_nr..vr区间。返回OK表示本地发送提交,不表示对端已处理该确认。
*/
static IEC104_LinkResult_t Commit_Sent_NR(
IEC104_LinkState_t *state, uint16_t receive_number)
{
uint16_t acknowledged_count;
uint16_t outstanding_count;
if (receive_number > IEC104_SEQUENCE_MASK) {
return IEC104_LINK_RESULT_FATAL;
}
acknowledged_count = Number_Distance(state->acked_nr, receive_number);
outstanding_count = IEC104_Link_Unconfirmed_Rx_Internal(state);
if (acknowledged_count > outstanding_count) {
return IEC104_LINK_RESULT_FATAL;
}
state->acked_nr = receive_number;
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:把链路状态对象设置为指定角色的新连接初始状态。
* 输入参数:state——非NULL状态对象;role——已由调用方校验或规范化的角色。
* 输出参数:保留传入角色,清零序号并复位确认、启停和探测状态。
* 返回值:无。
* 说明:仅供State_Init与State_Reset复用,不负责校验指针和角色。
*/
static void Initialize_State_Fields(IEC104_LinkState_t *state,
IEC104_LinkRole_t role)
{
state->role = role;
state->vs = 0U;
state->sent_vs = 0U;
state->va = 0U;
state->vr = 0U;
state->acked_nr = 0U;
state->s_ack_state = IEC104_S_ACK_IDLE;
state->s_ack_nr = 0U;
state->dt_state = IEC104_DT_STOPPED;
state->dt_procedure = IEC104_DT_PROC_IDLE;
state->testfr_state = IEC104_TESTFR_IDLE;
}
/**
* 功能:初始化一条连接的链路窗口与超时默认配置。
* 输入参数:config——待初始化的配置对象;允许为NULL。
* 输出参数:有效对象写入k、w、t1、t2和t3默认值。
* 返回值:无。
*/
/* 适用端:控制站与被控站。 */
void IEC104_Link_Config_Default(IEC104_LinkConfig_t *config)
{
if (config == NULL) {
return;
}
config->k = IEC104_DEFAULT_K;
config->w = IEC104_DEFAULT_W;
config->t1_ms = IEC104_DEFAULT_T1_MS;
config->t2_ms = IEC104_DEFAULT_T2_MS;
config->t3_ms = IEC104_DEFAULT_T3_MS;
}
/**
* 功能:为新连接初始化链路状态,并固定本端角色。
* 输入参数:state——待初始化的状态对象;role——CONTROLLING或CONTROLLED。
* 输出参数:成功时写入role,清零序号和确认快照,数据传输置STOPPED,
* 启停过程、S确认和TESTFR置IDLE。
* 返回值:1表示初始化成功;0表示state为NULL或role非法,此时不修改状态。
* 说明:仅初始化链路算法状态,连接配置由Config_Default单独初始化。
* 新连接处理任何链路事件前须初始化成功,不要求在TCP建连成功后才调用。
* 本模块未实现连接生命周期管理;旧事件隔离和状态内存安全复用由接入方负责。
*/
/* 适用端:控制站与被控站,由role参数确定本端角色。 */
int IEC104_Link_State_Init(IEC104_LinkState_t *state,
IEC104_LinkRole_t role)
{
if (state == NULL ||
(role != IEC104_ROLE_CONTROLLING &&
role != IEC104_ROLE_CONTROLLED)) {
return 0;
}
Initialize_State_Fields(state, role);
return 1;
}
/**
* 功能:复位链路序号、S确认、启停和探测状态,清除待完成过程的算法记录。
* 输入参数:state——待复位的链路状态;允许为NULL。
* 输出参数:state——保留有效角色,序号及S快照清零,回到STOPPED/IDLE;
* S确认和TESTFR置IDLE;非法角色规范化为INVALID。
* 返回值:无。
* 说明:本函数只复位链路算法字段,不能用于致命故障后在原连接上继续通信。
* 外部接入要求:调用方负责关闭故障连接,并防止旧事件提交到重新初始化后的状态;
* 对应连接管理、任务调度和定时器管理当前未实现,不属于本函数行为。
* Reset会读取原role,非空state须具有确定的字段内容,不能传入未初始化的自动变量。
*/
/* 适用端:控制站与被控站。 */
void IEC104_Link_State_Reset(IEC104_LinkState_t *state)
{
IEC104_LinkRole_t role;
if (state == NULL) {
return;
}
role = state->role;
if (role != IEC104_ROLE_CONTROLLING &&
role != IEC104_ROLE_CONTROLLED) {
role = IEC104_ROLE_INVALID;
}
Initialize_State_Fields(state, role);
}
/* I/S启停策略集中于下方判断函数;只查询当前状态,不缓存收发权限。 */
/* 子站START受理后即可接收I帧;STOP受理即关闭,后到START重新开放。 */
static int I_Frame_Receive_Allowed(const IEC104_LinkState_t *state)
{
return state->dt_state == IEC104_DT_STARTED &&
(state->role != IEC104_ROLE_CONTROLLED || state->dt_procedure == IEC104_DT_PROC_IDLE);
}
/**
* 功能:判断当前是否允许业务层预留新的I帧。
* 输入参数:state——已通过完整合法性检查的链路状态。
* 输出参数:无,不修改state。
* 返回值:1表示可预留新I帧,0表示当前方向尚未开放或正在停止。
* 说明:控制站在STARTDT_ACT之后可提前发送命令,无须等待STARTDT_CON。
* ACT待完成时新I帧必须排在ACT后,FIFO发送及完成顺序由调用方保证。
* 被控站受理START即开放准备;发送存储须保证新I帧不越过已有CON。
* 此处只检查启停门控,k窗口和序号环边界由Plan_I_Frame继续检查。
*/
static int New_I_Send_Allowed(const IEC104_LinkState_t *state)
{
if (state->dt_state == IEC104_DT_STARTED) {
return state->dt_procedure == IEC104_DT_PROC_IDLE;
}
return state->role == IEC104_ROLE_CONTROLLING &&
(state->dt_procedure == IEC104_DT_PROC_START_ACT_SEND_PENDING ||
state->dt_procedure == IEC104_DT_PROC_START_CON_WAITING);
}
/* 子站停止等待ACK时允许S;STOP_CON已生成后的STOPPED/IDLE拒绝S。 */
static int S_Frame_Receive_Allowed(const IEC104_LinkState_t *state)
{
return state->dt_state == IEC104_DT_STARTED ||
(state->role == IEC104_ROLE_CONTROLLED &&
state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING);
}
/* 子站的S和CON共享FIFO;无需再等待START_CON的网络完成。 */
static int Dt_Allows_S_Ack(const IEC104_LinkState_t *state)
{
return state->role != IEC104_ROLE_CONTROLLED || state->dt_state == IEC104_DT_STARTED ||
state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING;
}
/**
* 功能:判断停止阶段是否要求普通S确认绕过w门限。
* 输入参数:state——已通过完整合法性检查的链路状态。
* 输出参数:无,不修改state。
* 返回值:停止请求及排空的四个阶段为1,其余为0。
* 说明:只决定确认紧迫性,不绕过在途S或无待确认数据检查。
*/
static int Dt_Requires_Immediate_Ack(const IEC104_LinkState_t *state)
{
return state->dt_procedure == IEC104_DT_PROC_STOP_REQUESTED ||
state->dt_procedure == IEC104_DT_PROC_STOP_ACT_SEND_PENDING ||
state->dt_procedure == IEC104_DT_PROC_STOP_CON_WAITING ||
state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING;
}
/**
* 功能:检查I帧的发送序号和确认号,并更新链路状态。
* 输入参数:state——当前链路状态;apci——已完成基础解析的I帧APCI。
* 输出参数:成功时更新V(A)和V(R);待确认数量由acked_nr到V(R)推导。
* 返回值:OK表示接收方向已经开放、序号合法并提交;FATAL表示接收方向未开放,
* 或状态、参数、帧类型、序号非法。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Handle_I_Frame(
IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci)
{
uint16_t next_receive_number;
/* 先完成所有检查;合法状态的V(R)为15位,N(S)==V(R)也保证N(S)未越界。 */
if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL ||
!I_Frame_Receive_Allowed(state) ||
apci->frame_type != IEC104_FRAME_TYPE_I ||
apci->send_number != state->vr ||
!Acknowledge_Number_Valid(state, apci->receive_number)) {
return IEC104_LINK_RESULT_FATAL;
}
next_receive_number = Number_Increment(state->vr);
/* 未确认区间不得占满15位序号环,否则acked_nr==V(R)会被误判为没有积压。 */
if (next_receive_number == state->acked_nr) {
return IEC104_LINK_RESULT_FATAL;
}
/* I帧中的N(R)确认本端发送数据,合法N(S)使本端的期望接收序号V(R)加1。 */
state->va = apci->receive_number;
state->vr = next_receive_number;
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:检查S帧的确认号并更新已确认发送序号。
* 输入参数:state——当前链路状态;apci——已完成基础解析的S帧APCI。
* 输出参数:成功时更新V(A)。
* 返回值:OK表示接收阶段和确认号合法并提交;其他情况返回FATAL且不修改状态。
* 说明:STARTED下允许接收S;被控站STOPPED下仅STOP_DRAINING允许,以完成停止排空。
* 被控站START受理即STARTED,无须等待START_CON完成;STOPPED/IDLE拒绝S。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Handle_S_Frame(
IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci)
{
if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL ||
apci->frame_type != IEC104_FRAME_TYPE_S ||
!S_Frame_Receive_Allowed(state) ||
!Acknowledge_Number_Valid(state, apci->receive_number)) {
return IEC104_LINK_RESULT_FATAL;
}
/* S帧不携带用户数据,只用N(R)推进本端的发送确认位置。 */
state->va = apci->receive_number;
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:只根据链路状态规划下一帧I帧的序号快照,不编码报文也不修改状态。
* 输入参数:state——当前链路状态;k——发送窗口上限;
* tx——调用方提供的非NULL临时快照。
* 输出参数:仅READY时tx写入当前V(S)、V(R),frame_length暂置0,等待编码层填写。
* 返回值:READY表示可生成;SKIPPED表示门控或窗口阻止发送;ERROR表示参数/状态非法。
* 说明:本函数是只读规划阶段。调用方必须在同一串行临界区内完成编码,并且
* 只有编码成功后才能把tx提交给发送队列并推进V(S)。
* Can_Prepare_I_Frame与Prepare_I_Frame共用本函数;tx均指向栈上临时对象,故此处不再判空。
*/
static IEC104_PlanResult_t Plan_I_Frame(const IEC104_LinkState_t *state,
uint16_t k,
IEC104_IFrameTx_t *tx)
{
uint16_t next_send_number;
uint16_t outstanding_count;
if (!IEC104_Link_State_Valid_Internal(state) ||
k == 0U || k > IEC104_SEQUENCE_MASK) {
return IEC104_PLAN_ERROR;
}
if (!New_I_Send_Allowed(state)) {
return IEC104_PLAN_SKIPPED;
}
outstanding_count = Number_Distance(state->va, state->vs);
if (outstanding_count >= k) {
return IEC104_PLAN_SKIPPED;
}
next_send_number = Number_Increment(state->vs);
/* 未完成区间包含在未确认区间内;保留显式检查,避免完成边界的回绕歧义。 */
if (next_send_number == state->sent_vs) {
return IEC104_PLAN_SKIPPED;
}
tx->send_number = state->vs;
tx->receive_number = state->vr;
tx->frame_length = 0U;
return IEC104_PLAN_READY;
}
/* 只读复用实际构帧的启停、k窗口及序号边界检查,不编码、不预留N(S)。 */
int IEC104_Link_Can_Prepare_I_Frame(const IEC104_LinkState_t *state, uint16_t k)
{
IEC104_IFrameTx_t planned_tx;
IEC104_PlanResult_t planned = Plan_I_Frame(state, k, &planned_tx);
return planned == IEC104_PLAN_READY ? 1 : (planned == IEC104_PLAN_SKIPPED ? 0 : -1);
}
/**
* 功能:检查启停状态和k窗口,构造I帧并立即预留其N(S)。
* 输入参数:state——链路状态;k——发送窗口;asdu/asdu_len——完整ASDU;
* frame/capacity——输出缓冲及容量;tx——发送队列保存的构帧快照。
* 输出参数:成功时写入frame和tx,并推进V(S)。
* 返回值:成功返回完整APDU长度;0表示当前不允许新I帧或窗口已满;
* -1表示参数或状态非法。
* 说明:成功后N(S)不可撤销;报文须进入严格FIFO队列,发送失败由上层断连。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_Prepare_I_Frame(IEC104_LinkState_t *state,
uint16_t k,
const uint8_t *asdu,
uint16_t asdu_len,
uint8_t *frame,
uint16_t capacity,
IEC104_IFrameTx_t *tx)
{
IEC104_IFrameTx_t planned_tx;
IEC104_PlanResult_t planned;
int length;
if (asdu == NULL ||
asdu_len < IEC104_ASDU_HEADER_LEN ||
asdu_len > IEC104_MAX_ASDU_LEN ||
frame == NULL ||
capacity < (uint16_t)(IEC104_APCI_LEN + asdu_len) ||
tx == NULL) {
return -1;
}
planned = Plan_I_Frame(state, k, &planned_tx);
if (planned != IEC104_PLAN_READY) {
return planned == IEC104_PLAN_SKIPPED ? 0 : -1;
}
length = IEC104_Build_Iframe(frame,
planned_tx.send_number,
planned_tx.receive_number,
asdu, (int)asdu_len);
if (length < 0) {
return -1;
}
/* 编码成功后才一次性提交状态和发送队列快照。 */
planned_tx.frame_length = (uint16_t)length;
*tx = planned_tx;
state->vs = Number_Increment(planned_tx.send_number);
return length;
}
/**
* 功能:提交一帧由Prepare_I_Frame生成的I帧最终本地发送结果。
* 输入参数:state——链路状态;tx——构帧时保存的I帧快照;
* sent_len——最终累计成功发送的字节数,等于快照长度才算成功;发送失败也可传-1。
* 输出参数:完整发送且快照合法时推进acked_nr和sent_vs;失败时不修改状态。
* 返回值:OK表示完整发送并提交;FATAL表示参数、状态、长度、顺序或快照非法。
* 说明:Send限定为本地发送提交;返回OK不表示对端已经确认本帧。
* V(S)已在Prepare阶段推进;本接口按FIFO推进发送完成边界,不再次推进V(S)。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Complete_I_Send(
IEC104_LinkState_t *state, const IEC104_IFrameTx_t *tx, int sent_len)
{
IEC104_LinkResult_t result;
if (!IEC104_Link_State_Valid_Internal(state) || tx == NULL ||
tx->send_number > IEC104_SEQUENCE_MASK ||
tx->receive_number > IEC104_SEQUENCE_MASK ||
tx->frame_length <
(uint16_t)(IEC104_APCI_LEN + IEC104_ASDU_HEADER_LEN) ||
tx->frame_length >
(uint16_t)(IEC104_APCI_LEN + IEC104_MAX_ASDU_LEN) ||
sent_len != (int)tx->frame_length) {
return IEC104_LINK_RESULT_FATAL;
}
/* 没有待完成帧或N(S)不匹配时拒绝,避免重复/乱序回调提交到别的帧。 */
if (state->sent_vs == state->vs ||
tx->send_number != state->sent_vs) {
return IEC104_LINK_RESULT_FATAL;
}
result = Commit_Sent_NR(state, tx->receive_number);
if (result != IEC104_LINK_RESULT_OK) {
return result;
}
state->sent_vs = Number_Increment(tx->send_number);
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:判断最早已完成发送且尚未被对端确认的I帧是否达到t1超时。
* 输入参数:state——链路状态;elapsed_ms——该帧实际完成发送后经过的毫秒数;
* t1_ms——t1配置值。
* 输出参数:无,不修改state。
* 返回值:1表示已经超时;0表示无已完成未确认帧或尚未超时;-1表示参数非法。
* 说明:只预留未Complete的帧不参与t1;时间戳和队列仍由调用方维护。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_T1_I_Ack_Expired(const IEC104_LinkState_t *state,
uint32_t elapsed_ms, uint32_t t1_ms)
{
if (!IEC104_Link_State_Valid_Internal(state) || t1_ms == 0U) {
return -1;
}
if (Sent_Unacknowledged_I_Count(state) == 0U) {
return 0;
}
return elapsed_ms >= t1_ms ? 1 : 0;
}
/**
* 功能:只根据链路状态规划S确认的N(R),不编码报文也不修改状态。
* 输入参数:state——当前链路状态;w——接收确认门限;
* immediate——非0表示绕过w门限立即确认;
* receive_number——用于接收待编码N(R)的输出地址。
* 输出参数:仅READY时写入当前V(R)。
* 返回值:READY表示应生成;SKIPPED表示当前无需或已有S帧在途。
* 前置条件:state已通过完整校验,w在1..32767内,receive_number为有效输出地址。
* 说明:STOPDT排空半态会自动绕过w门限。实际S帧编码成功后,构帧步骤才把
* receive_number保存到s_ack_nr并进入SEND_PENDING。
*/
static IEC104_PlanResult_t Plan_S_Ack(const IEC104_LinkState_t *state,
uint16_t w, int immediate,
uint16_t *receive_number)
{
uint16_t unconfirmed_count;
if (!Dt_Allows_S_Ack(state)) {
return IEC104_PLAN_SKIPPED;
}
if (state->s_ack_state == IEC104_S_ACK_SEND_PENDING) {
return IEC104_PLAN_SKIPPED;
}
unconfirmed_count = IEC104_Link_Unconfirmed_Rx_Internal(state);
if (unconfirmed_count == 0U) {
return IEC104_PLAN_SKIPPED;
}
if (!immediate && unconfirmed_count < w &&
!Dt_Requires_Immediate_Ack(state)) {
return IEC104_PLAN_SKIPPED;
}
*receive_number = state->vr;
return IEC104_PLAN_READY;
}
/**
* 功能:只读查询普通S确认需求,供连接层在取得控制槽前决定是否调用发送准备接口。
* 输入参数:state——当前链路;w——合法接收门限;immediate——非0立即确认。
* 返回值:1需要且允许准备,0当前无需/不能准备,-1参数或状态非法。
* 说明:直接复用构帧使用的Plan_S_Ack;查询不消耗发送编号、不改变S快照或acked_nr。
*/
int IEC104_Link_Can_Prepare_S_Ack(const IEC104_LinkState_t *state, uint16_t w, int immediate)
{
uint16_t receive_number;
if (!IEC104_Link_State_Valid_Internal(state) || w == 0U || w > IEC104_SEQUENCE_MASK) {
return -1;
}
return Plan_S_Ack(state, w, immediate, &receive_number) == IEC104_PLAN_READY;
}
/**
* 功能:按已规划的N(R)编码S帧,成功后提交公共S确认状态。
* 输入参数:state——链路状态;response——输出缓冲;receive_number——规划的N(R)。
* 输出参数:成功时写入6字节S帧,将N(R)保存到s_ack_nr并置SEND_PENDING。
* 前置条件:state已通过完整校验且无S在途;response非NULL且至少6字节;
* receive_number为本次选定的有效15位确认号。
* 说明:普通S确认和STOPDT共用此步骤;使用传入快照,不重新规划或读取V(R)。
* 本步骤不推进acked_nr,须等Complete_S_Ack_Send确认发送成功后提交。
* 返回值:6表示编码并提交成功;-1表示编码失败,此时不提交状态。
* 调用关系:本文件的普通确认/t2以及Master、Slave的停止准备均调用本函数,
* 所以不能直接设为static;是否应发送、容量和角色均由调用入口检查。
*/
int IEC104_Link_Prepare_S_Ack_Internal(IEC104_LinkState_t *state,
uint8_t *response, uint16_t receive_number)
{
int length = IEC104_Build_Sframe(response, receive_number);
if (length != IEC104_APCI_LEN) {
return -1;
}
/* 编码成功后提交与报文字节完全一致的N(R)快照。 */
state->s_ack_nr = receive_number;
state->s_ack_state = IEC104_S_ACK_SEND_PENDING;
return length;
}
/**
* 功能:普通S确认与t2共用的规划、编码和提交路径。
* 输入参数:state——链路状态;w——确认门限;immediate——非0立即确认;
* response——输出缓冲。
* 输出参数:生成时写入S帧并记录其N(R)快照和在途状态;未生成时保持原值。
* 前置条件:state和w已校验,response至少有IEC104_APCI_LEN字节空间。
* 返回值:6表示生成,0表示当前不生成,-1表示编码失败。
*/
static int Prepare_S_Ack_Validated(IEC104_LinkState_t *state,
uint16_t w, int immediate,
uint8_t *response)
{
uint16_t receive_number;
IEC104_PlanResult_t planned = Plan_S_Ack(state, w, immediate, &receive_number);
if (planned != IEC104_PLAN_READY) {
return 0;
}
return IEC104_Link_Prepare_S_Ack_Internal(state, response, receive_number);
}
/**
* 功能:按w门限、显式立即确认或STOPDT排空要求生成普通S帧并保存N(R)快照。
* 输入参数:state——链路状态;w——接收确认门限;immediate——非0跳过w门限,仍受启停及S在途限制;
* response——输出缓冲;capacity——至少6字节。
* 输出参数:成功时写入S帧并记录SEND_PENDING及构帧时V(R)。
* 返回值:6表示生成;0表示当前无需或不能由普通流程生成;-1表示参数/状态非法。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_Prepare_S_Ack(IEC104_LinkState_t *state,
uint16_t w, int immediate,
uint8_t *response, uint16_t capacity)
{
if (!IEC104_Link_State_Valid_Internal(state) || w == 0U || w > IEC104_SEQUENCE_MASK ||
response == NULL || capacity < IEC104_APCI_LEN) {
return -1;
}
return Prepare_S_Ack_Validated(state, w, immediate, response);
}
/**
* 功能:提交公共S确认帧的最终发送结果,普通确认与两端停止流程共用。
* 输入参数:state——链路状态;sent_len——最终累计发送字节数。
* 输出参数:成功时按快照推进acked_nr并回IDLE;失败或快照失效时不推进确认。
* 返回值:OK表示发送成功并提交;IGNORED表示没有S帧等待完成;
* FATAL表示状态非法、发送失败或快照失效。
* 说明:状态非法直接返回且不清理;状态合法但无S在途时先返回IGNORED,
* 不检查sent_len。有S在途时,发送失败或快照失效均清除S状态和快照。
* 成功同样清除S状态/快照,所有结果均保留当前启停过程。
* 普通确认及两端STOPDT中的S帧均由本接口完成。本接口不携带帧身份,
* 调用方须按原发送节点恰好分派一次。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Complete_S_Ack_Send(
IEC104_LinkState_t *state, int sent_len)
{
IEC104_LinkResult_t result;
if (!IEC104_Link_State_Valid_Internal(state)) {
return IEC104_LINK_RESULT_FATAL;
}
if (state->s_ack_state != IEC104_S_ACK_SEND_PENDING) {
return IEC104_LINK_RESULT_IGNORED;
}
if (sent_len != IEC104_APCI_LEN) {
state->s_ack_state = IEC104_S_ACK_IDLE;
state->s_ack_nr = 0U;
return IEC104_LINK_RESULT_FATAL;
}
result = Commit_Sent_NR(state, state->s_ack_nr);
state->s_ack_state = IEC104_S_ACK_IDLE;
state->s_ack_nr = 0U;
return result;
}
/**
* 功能:在最早已接收但本端尚未确认的I帧达到t2等待时间后生成S确认帧。
* 输入参数:state——链路状态;elapsed_ms——已经等待的毫秒数;
* t2_ms——t2配置值;response——输出缓冲;capacity——至少6字节。
* 输出参数:到期且可生成时写入S帧,并保存普通S帧的N(R)快照。
* 返回值:6表示生成;0表示尚不需要或当前已有流程负责;-1表示参数/状态非法。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_Prepare_T2_Ack(IEC104_LinkState_t *state,
uint32_t elapsed_ms, uint32_t t2_ms,
uint8_t *response, uint16_t capacity)
{
if (!IEC104_Link_State_Valid_Internal(state) || response == NULL ||
capacity < IEC104_APCI_LEN || t2_ms == 0U) {
return -1;
}
if (elapsed_ms < t2_ms) {
return 0;
}
/* 已校验输入;到期后由共用规划判断待确认数据和在途S帧。 */
return Prepare_S_Ack_Validated(state, 1U, 1, response);
}
/**
* 功能:检查链路状态自洽且本端固定角色与调用接口要求一致。
* 输入参数:state——待检查的链路状态对象;role——接口要求的本端角色。
* 输出参数:无,不修改state。
* 返回值:状态自洽且角色匹配返回1,否则返回0。
* 调用关系:Master/Slave角色专用公开入口使用;先调用State_Valid再比较角色,
* 短路求值保证state为NULL时不会解引用。末级角色校验不得反调此函数。
*/
int IEC104_Link_Role_Valid_Internal(const IEC104_LinkState_t *state,
IEC104_LinkRole_t role)
{
return IEC104_Link_State_Valid_Internal(state) && state->role == role;
}
/**
* 功能:处理TESTFR_ACT,复用U帧构造接口生成TESTFR_CON应答。
* 输入参数:apci——已通过APCI解析的TESTFR_ACT;response——输出缓冲;
* capacity——缓冲实际容量,至少6字节。
* 输出参数:response——写入68 04 83 00 00 00。
* 返回值:OK表示生成固定6字节应答;FATAL表示参数或报文非法。
* 说明:不读写链路状态,不执行网络发送,不处理主动探测或超时。
* 此应答没有对应Complete接口;发送队列负责发送结果和失败断连,不能
* 把TESTFR_CON发送完成交给处理本端主动ACT的Complete_Testfr_Act_Send。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Handle_Testfr_Act(
const IEC104_ApciInfo_t *apci, uint8_t *response, uint16_t capacity)
{
if (apci == NULL || response == NULL || capacity < IEC104_APCI_LEN ||
apci->frame_type != IEC104_FRAME_TYPE_U ||
apci->u_function != IEC104_U_TESTFR_ACT) {
return IEC104_LINK_RESULT_FATAL;
}
/* 缓冲已验证且功能码固定,编码必定生成6字节;(void)表示有意忽略返回值。 */
(void)IEC104_Build_Uframe(response, IEC104_U_TESTFR_CON);
return IEC104_LINK_RESULT_OK;
}
/* 已通过完整状态检查;查询与构帧共用唯一的主动探测准入条件。 */
static int Testfr_Act_Allowed(const IEC104_LinkState_t *state)
{
return state->testfr_state == IEC104_TESTFR_IDLE;
}
/**
* 功能:查询当前是否允许新一轮主动TESTFR,不读取时钟或改变状态。
* 输入参数:state——待检查链路;调用方另外判断是否已满足t3空闲时长。
* 返回值:1可准备,0当前已有探测,-1参数或完整状态非法。
* 说明:普通主动探测和t3构帧均复用Testfr_Act_Allowed,不在连接层另判枚举值。
*/
int IEC104_Link_Can_Prepare_Testfr_Act(const IEC104_LinkState_t *state)
{
if (!IEC104_Link_State_Valid_Internal(state)) {
return -1;
}
return Testfr_Act_Allowed(state);
}
/**
* 功能:主动TESTFR与t3共用的状态检查、编码和提交路径。
* 输入参数:state——链路状态;request——输出缓冲。
* 输出参数:生成时写入6字节TESTFR_ACT,并进入TESTFR_ACT_SEND_PENDING。
* 前置条件:state已校验,request至少有IEC104_APCI_LEN字节空间。
* 返回值:6表示生成,0表示已有探测。
* 说明:与只读查询共用Testfr_Act_Allowed,真正发送完成后才开始等待CON;
* 本函数不判断链路空闲时间,参数校验和t3时间比较由公开入口负责。
*/
static int Prepare_Testfr_Act_Validated(IEC104_LinkState_t *state,
uint8_t *request)
{
if (!Testfr_Act_Allowed(state)) {
return 0;
}
/*
* 缓冲已验证且TESTFR_ACT为合法常量,编码器必定写入6字节并返回6。
* (void)只显式丢弃这次调用的返回值,函数仍会执行编码并写入request。
*/
(void)IEC104_Build_Uframe(request, IEC104_U_TESTFR_ACT);
state->testfr_state = IEC104_TESTFR_ACT_SEND_PENDING;
return IEC104_APCI_LEN;
}
/**
* 功能:生成主动TESTFR_ACT请求,并记录等待发送完成的状态。
* 输入参数:state——链路状态;request——输出缓冲;capacity——实际容量,至少6字节。
* 输出参数:request——写入68 04 43 00 00 00;state——探测转为ACT_SEND_PENDING。
* 返回值:6表示生成请求;0表示已有探测进行中;-1表示参数或链路状态非法。
* 说明:返回0或-1时不修改状态和缓冲;不执行发送,不修改启停、序号及确认位置。
* 返回6后发送层必须保证恰好提交一次Complete;发送超时也以失败长度提交。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_Prepare_Testfr_Act(IEC104_LinkState_t *state,
uint8_t *request, uint16_t capacity)
{
if (!IEC104_Link_State_Valid_Internal(state) || request == NULL || capacity < IEC104_APCI_LEN) {
return -1;
}
return Prepare_Testfr_Act_Validated(state, request);
}
/**
* 功能:在链路空闲达到t3后生成主动TESTFR_ACT请求。
* 输入参数:state——链路状态;idle_elapsed_ms——空闲毫秒数;
* t3_ms——t3配置值;request——输出缓冲;capacity——至少6字节。
* 输出参数:到期且允许生成时写入TESTFR_ACT,并复用主动探测发送状态。
* 返回值:6表示生成;0表示尚未到期或已有探测;-1表示参数或链路状态非法。
* 说明:先校验完整状态和参数,再比较t3时间;返回6只表示生成报文,尚未发送。
* 连接层统一定义有效链路活动,并更新t3的单调计时起点;本函数不保存时刻。
* t3活动更新不得重置正在等待TESTFR_CON的t1起点。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_Prepare_T3_Testfr(IEC104_LinkState_t *state,
uint32_t idle_elapsed_ms,
uint32_t t3_ms,
uint8_t *request,
uint16_t capacity)
{
if (!IEC104_Link_State_Valid_Internal(state) || request == NULL ||
capacity < IEC104_APCI_LEN || t3_ms == 0U) {
return -1;
}
if (idle_elapsed_ms < t3_ms) {
return 0;
}
/* 已校验输入;t3到期后直接复用主动探测准备路径。 */
return Prepare_Testfr_Act_Validated(state, request);
}
/**
* 功能:提交TESTFR_ACT最终发送结果,成功后开始等待TESTFR_CON。
* 输入参数:state——链路状态;sent_len——最终累计成功发送字节数,恰好6表示成功。
* 输出参数:有匹配ACT时,完整发送成功转CON_PENDING;发送失败回IDLE并由上层断连。
* 返回值:OK表示完整发送并提交;IGNORED表示没有待完成的ACT;
* FATAL表示状态非法或发送失败。
* 说明:部分写入仍在继续时不要调用;状态合法但无匹配ACT时先返回IGNORED,不检查长度。
* 调用方须先提交ACT完整发送结果,再分派对端CON;不缓存提前CON。
* 返回OK后,由连接层以本次ACT实际完整发送时刻为等待CON的t1起点,
* 不能用延迟处理Complete时的当前时刻代替;本函数不操作外部定时器。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Complete_Testfr_Act_Send(
IEC104_LinkState_t *state, int sent_len)
{
if (!IEC104_Link_State_Valid_Internal(state)) {
return IEC104_LINK_RESULT_FATAL;
}
if (state->testfr_state != IEC104_TESTFR_ACT_SEND_PENDING) {
return IEC104_LINK_RESULT_IGNORED;
}
if (sent_len != IEC104_APCI_LEN) {
state->testfr_state = IEC104_TESTFR_IDLE;
return IEC104_LINK_RESULT_FATAL;
}
/* 完整发送成功只开始等待对端确认;收到CON后才结束本轮探测。 */
state->testfr_state = IEC104_TESTFR_CON_PENDING;
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:接受正在等待的TESTFR_CON,结束本端主动探测。
* 输入参数:state——链路状态;apci——已通过APCI解析的TESTFR_CON。
* 输出参数:仅CON_PENDING时回IDLE;非OK时不修改状态。
* 返回值:OK表示接受确认;IGNORED表示当前未等待CON;
* FATAL表示参数、报文或链路状态非法。
* 说明:输入校验先于等待状态检查;ACT_SEND_PENDING时返回IGNORED且不缓存CON。
* 调用方须先提交ACT完整发送结果,再处理CON;本接口不修改启停状态、
* 链路序号及确认位置,也不生成应答报文。返回OK后由连接层取消本轮等待计时,
* 本接口不操作外部定时器。
*/
/* 适用端:控制站与被控站。 */
IEC104_LinkResult_t IEC104_Link_Handle_Testfr_Con(
IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci)
{
if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL ||
apci->frame_type != IEC104_FRAME_TYPE_U ||
apci->u_function != IEC104_U_TESTFR_CON) {
return IEC104_LINK_RESULT_FATAL;
}
if (state->testfr_state != IEC104_TESTFR_CON_PENDING) {
return IEC104_LINK_RESULT_IGNORED;
}
state->testfr_state = IEC104_TESTFR_IDLE;
return IEC104_LINK_RESULT_OK;
}
/**
* 功能:判断主动TESTFR流程等待TESTFR_CON是否达到t1超时。
* 输入参数:state——链路状态;elapsed_ms——距当前探测ACT实际完整发送的毫秒数;
* t1_ms——t1配置值,必须大于0。
* 输出参数:无,不修改state。
* 返回值:1表示已经超时;0表示未等待CON或尚未超时;-1表示参数或链路状态非法。
* 说明:先校验状态及t1_ms;合法时仅对CON_PENDING判定t1,ACT_SEND_PENDING返回0。
* ACT_SEND_PENDING表示本地发送结果尚未提交,发送层须限制排队和发送等待时间。
* 连接层定时器只负责唤醒;回调须校验连接代次,并在连接串行调度中按当前
* 有效探测的起点重新计算elapsed_ms,不能沿用同一连接上旧探测的时间差。
* 本函数不识别探测轮次,也不操作定时器或连接;只有返回1才按t1超时断连。
*/
/* 适用端:控制站与被控站。 */
int IEC104_Link_T1_Testfr_Con_Expired(const IEC104_LinkState_t *state,
uint32_t elapsed_ms,
uint32_t t1_ms)
{
if (!IEC104_Link_State_Valid_Internal(state) || t1_ms == 0U) {
return -1;
}
if (state->testfr_state != IEC104_TESTFR_CON_PENDING) {
return 0;
}
return elapsed_ms >= t1_ms ? 1 : 0;
}