Files
STM32F4-Base/IEC60870-Slave/104/IEC104_Slave.h
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

164 lines
11 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.
/*
* 104子站业务入口:内部持有TcpServer,NextEvent自动提供ASDU及原连接的发送接口。
* 服务来自静态池;业务只驱动Slave的事件循环,无需创建TCP对象或手动绑定generation。
* 仅包含本头文件即可使用服务接口、共用ASDU及IEC60870_MasterConnection_Send_*应答接口。
*
* 常规顺序:Create -> Config_Default/Init -> 循环Poll、NextEvent -> Close -> NextEvent读空 -> Destroy。
* 自有事件循环可用Prepare/应用等待/Process替代Poll;两种方式使用同一套底层调度。
* 处理I_FRAME前检查asdu_result==IEC60870_ASDU_OK,再通过&event.connection提交业务应答。
* 主动上报由Enqueue_Event提交;NextEvent不自动执行请求、确认或终止业务。
*
* 带error参数的接口允许error为NULL;非NULL时完整填写本次结果、阶段和网络错误。
* 服务结果须比较IEC104_TCP_OK;事件结果须比较IEC104_TCP_EVENT_READY,不能混用真假判断。
* 所有操作在同一事件循环线程串行执行,Create/Destroy也须跨服务串行。
*/
#ifndef IEC104_SLAVE_H
#define IEC104_SLAVE_H
#include "IEC60870_Slave.h"
#include "IEC104_TcpTypes.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 不透明的子站句柄:完整结构只在IEC104_Slave.c定义,业务不能访问或分配内部成员。
* Slave与TcpServer是两个分别管理的对象;这里是持有关系,不可把两种句柄互相强转。 */
typedef struct IEC104_Slave IEC104_Slave_t;
/* 复用TCP监听和协议配置的值类型,不暴露TcpServer的对象布局或操作接口。 */
typedef IEC104_TcpServerConfig_t IEC104_SlaveConfig_t;
/* transport保存原通知;I_FRAME且asdu_result为OK时,asdu和connection可直接使用。
* asdu借用本对象transport中的字节;确认/终止会原地修改这份副本的COT/P/N。
* 具体对象、地址及业务权限仍须由应用校验,ASDU_OK不代表允许执行请求。
* NextEvent为EMPTY/ERROR时整个输出保持不变;READY会替换上次事件。
* 使用期间地址必须稳定。延迟处理可直接读入任务存储,或使用Copy_Event复制;
* 禁止用赋值/memcpy搬移含自引用的事件。一个事件的多个应答沿用同一connection。
* transport元数据、connection及以下内部字段只读;用ASDU接口修改报文。
* 内部字段保存原服务实例和原代次,重连、重新Init及静态槽复用均不重新绑定旧请求。
*/
typedef struct {
IEC104_TcpEvent_t transport; /* 原通知的独立副本;只有type对应的data成员有效。 */
IEC60870_Asdu_t asdu; /* 借用本事件I帧中APCI之后的字节;仅asdu_result为OK时可用。 */
IEC60870_AsduResult_t asdu_result; /* 非I_FRAME为UNSUPPORTED;异常原文为ERROR。 */
IEC60870_MasterConnection_t connection; /* 仅ASDU_OK的I_FRAME提供发送函数。 */
IEC104_Slave_t *_owner; /* 以下字段由模块维护:产生本事件的Slave地址。 */
uint64_t _owner_serial; /* 原Slave_Create编号,防止静态槽销毁重用后旧应答串入新服务。 */
uint64_t _generation; /* 原TCP会话代次,防止断线重连后把旧应答发给新主站。 */
IEC104_TcpSubmitResult_t _last_submit_result; /* 此接口最近的提交结果,不表示实时发送/ACK状态。 */
} IEC104_SlaveEvent_t;
/* Get_Event_Identity校验后给出的来源身份,可按值保存,不借用事件原文或自引用视图。
* 三个字段只读,调用Check_Request时原样传回;slave只用于身份比较,不延长服务寿命。
* 不同Slave实例的局部编号可能相同,不能丢弃slave而只比较两个编号。 */
typedef struct {
const IEC104_Slave_t *slave; /* 产生事件的服务句柄。 */
uint64_t instance_id; /* 区分该服务静态槽的历次使用。 */
uint64_t generation; /* 原TCP会话代次;断线重连后旧身份不会重新绑定。 */
} IEC104_SlaveEventIdentity_t;
/* 业务请求推进检查,与发送容量/ACK状态无关;须按枚举值显式分支。 */
typedef enum {
IEC104_SLAVE_REQUEST_ERROR = -1, /* 非法服务/NULL身份,或服务状态查询失败。 */
IEC104_SLAVE_REQUEST_STALE = 0, /* 身份未绑定、服务/代次不符,或原连接不再处于激活状态。 */
IEC104_SLAVE_REQUEST_WAIT_EVENTS = 1, /* 先按FIFO处理Slave通知,再检查请求,防止漏过STOP/撤销。 */
IEC104_SLAVE_REQUEST_READY = 2 /* 原请求会话仍可处理;实际发送是否接手以发送接口为准。 */
} IEC104_SlaveRequestState_t;
/* 从静态池取得子站及其私有TCP服务,无malloc、无网络操作;容量不足返回NULL。
* 容量由IEC104_TCP_SERVER_CAPACITY决定,与直接使用TcpServer共享底层名额。
* 成功后配对Close、读空事件、Destroy;业务不得自行分配、复制或free服务对象。 */
IEC104_Slave_t *IEC104_Slave_Create(void);
/* 只销毁已关闭且事件读空的服务;失败保留句柄。成功后所有旧句柄失效。
* 未Init或Init失败后也可直接销毁;运行中为INVALID_STATE,未读事件为EVENTS_PENDING。
* 旧事件仍可读其原文字节,但发送接口不能用于新的服务实例。error允许NULL。 */
IEC104_TcpResult_t IEC104_Slave_Destroy(IEC104_Slave_t *self, IEC104_TcpError_t *error);
/* 默认监听0.0.0.0:2404及推荐协议参数;NULL无操作。 */
void IEC104_Slave_Config_Default(IEC104_SlaveConfig_t *config);
/* 初始化网络和监听,复制config;成功后用Prepare/Process或Poll驱动。
* 初始化成功只表示可以接入主站;TCP连接及104激活状态分别由事件/状态查询反映。
* 失败不会销毁句柄;再次Init要求服务已Close且旧事件读空。 */
IEC104_TcpResult_t IEC104_Slave_Init(IEC104_Slave_t *self,
const IEC104_SlaveConfig_t *config, IEC104_TcpError_t *error);
/* 只读查询调度条件。need_process或pending_event_count非零时不得阻塞等待。
* socket只供事件循环注册;业务不得直接recv/send/close。 */
IEC104_TcpResult_t IEC104_Slave_Prepare(const IEC104_Slave_t *self,
IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error);
/* 有界处理一批socket/定时就绪,不等待;ready的service_token及socket token来自原注册。
* 返回后读取NextEvent并重新Prepare;旧通知的身份不可替换成当前token。 */
IEC104_TcpResult_t IEC104_Slave_Process(IEC104_Slave_t *self,
const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error);
/* 独立循环入口:组合准备、最多等待max_wait_ms和有界推进;0不等待。
* 有待消费通知时不阻塞等待,调用方仍须读取NextEvent;等待上限不等于整个调用的耗时上限。 */
IEC104_TcpResult_t IEC104_Slave_Poll(IEC104_Slave_t *self,
uint32_t max_wait_ms, IEC104_TcpError_t *error);
/* 按原FIFO取一个事件,自动绑定原服务/代次并创建I帧的可写ASDU视图。
* READY交付事件;EMPTY/ERROR保持输出。不执行业务、不自动确认、不进行I/O。
* I_FRAME须检查asdu_result;解析失败仍交付原事件,connection为空,不丢弃诊断原文。 */
IEC104_TcpEventResult_t IEC104_Slave_NextEvent(IEC104_Slave_t *self, IEC104_SlaveEvent_t *event);
/* 把事件及原文字节复制到稳定的任务存储,重建目标ASDU与接口的自引用。
* 不改变原服务实例/代次,不提交或重发任何报文;源和目标相同时只校验。
* true成功;false为NULL、非法事件/视图或未经本函数修复的按值副本,目标保持原值。 */
bool IEC104_Slave_Copy_Event(IEC104_SlaveEvent_t *destination, const IEC104_SlaveEvent_t *source);
/* 校验event的服务归属、身份字段和有效I帧视图,再写出来源身份;失败不改identity。
* self须为当前有效服务,event须由NextEvent/Copy_Event取得;identity为非NULL输出。
* 允许同一服务的历史连接事件,不要求原连接仍在线,便于按FIFO处理断开/重连通知。
* 其他服务、已销毁复用实例及未经Copy_Event修复的I帧副本返回false;不进行I/O或消费事件。 */
bool IEC104_Slave_Get_Event_Identity(const IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, IEC104_SlaveEventIdentity_t *identity);
/* 检查由Get_Event_Identity取得的请求身份是否仍允许业务推进,不发送、不消费通知。
* 服务查询成功后,未读通知优先返回WAIT_EVENTS;读空后再判断来源、代次和激活状态。
* 零初始化身份在通知读空后返回STALE,可用于业务尚未绑定会话时的检查。
* READY不预留发送资源,不保证窗口/队列有空间;也不检查总召等业务自己的阶段或期限。 */
IEC104_SlaveRequestState_t IEC104_Slave_Check_Request(const IEC104_Slave_t *self,
const IEC104_SlaveEventIdentity_t *identity);
/* 查询该事件连接最近一次发送的OK/WAIT/ERROR/FATAL;空或无效接口返回ERROR。
* connection传&event.connection;结果仅在该接口提交时更新,不随Poll或ACK自动改变。
* true/OK为已接手,不代表已写完或已ACK;仅WAIT可保存原事件/ASDU等待重试。
* 确认/终止提交失败也不恢复已修改的字段;FATAL可能已写出字节。 */
IEC104_TcpSubmitResult_t IEC104_Slave_Get_Last_Submit_Result(
const IEC60870_MasterConnection_t *connection);
/* 返回标准104只读参数2/2/3、最大249,供主动上报构造ASDU;无效句柄返回NULL。
* 参数为静态常量,已取得的指针不借用服务存储。 */
const IEC60870_AppLayerParameters_t *IEC104_Slave_Get_Application_Layer_Parameters(
const IEC104_Slave_t *self);
/* 校验104布局后把ASDU复制到NORMAL,支持离线缓存;本次不直接写socket。
* 空间不足沿用NORMAL覆盖最旧原文的策略;返回OK表示已接手。 */
IEC104_TcpSubmitResult_t IEC104_Slave_Enqueue_Event(IEC104_Slave_t *self, IEC60870_Asdu_t *asdu);
/* 取得当前主站地址副本;尚未连接返回NOT_CONNECTED,不使用历史请求代替当前状态。 */
IEC104_TcpResult_t IEC104_Slave_Get_Remote_Address(const IEC104_Slave_t *self,
IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error);
/* 关闭指定事件的原连接,保留监听/NORMAL;无需业务提取或传入generation。
* event须来自本服务实例;其他服务或已销毁复用实例的事件为INVALID_ARGUMENT。
* 当前无连接为NOT_CONNECTED;仍有连接但原代次已过期为STALE_GENERATION,不影响新连接。
* 成功后继续读取收尾事件;本操作不等待STOPDT或ACK。 */
IEC104_TcpResult_t IEC104_Slave_Close_Connection(IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, IEC104_TcpError_t *error);
/* 只读取得服务、队列、连接状态副本;Create后、Init失败后、Close后也可调用。 */
IEC104_TcpResult_t IEC104_Slave_Get_Status(const IEC104_Slave_t *self,
IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error);
/* 关闭监听和当前连接,保留未读通知;随后NextEvent读空,再Destroy归还静态槽。 */
IEC104_TcpResult_t IEC104_Slave_Close(IEC104_Slave_t *self, IEC104_TcpError_t *error);
#ifdef __cplusplus
}
#endif
#endif /* IEC104_SLAVE_H */