164 lines
11 KiB
C
164 lines
11 KiB
C
/*
|
||
* 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 */
|