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

169 lines
10 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/U队列,不执行TCP I/O。 */
#ifndef IEC104_SENDBUFFER_H
#define IEC104_SENDBUFFER_H
#include "IEC104_Link_Common.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 本地发送描述;子站启停只发送CON,TESTFR支持ACT和CON。 */
typedef enum {
IEC104_SEND_BUFFER_FRAME_INVALID = 0,
IEC104_SEND_BUFFER_FRAME_I,
IEC104_SEND_BUFFER_FRAME_S_ACK,
IEC104_SEND_BUFFER_FRAME_STARTDT_CON,
IEC104_SEND_BUFFER_FRAME_STOPDT_CON,
IEC104_SEND_BUFFER_FRAME_TESTFR_ACT,
IEC104_SEND_BUFFER_FRAME_TESTFR_CON
} IEC104_SendBufferFrameType_t;
/* 必须精确比较枚举值;正数不统一表示存在报文或发送完成。 */
typedef enum {
IEC104_SEND_BUFFER_ERROR = -1, /* 参数、身份、状态或报文描述非法 */
IEC104_SEND_BUFFER_FULL = 0, /* S/U控制队列无空槽 */
IEC104_SEND_BUFFER_OK = 1, /* 操作成功;记账时表示尚未整帧写完 */
IEC104_SEND_BUFFER_EMPTY = 2, /* 没有已提交帧;未Commit的预留不算已提交帧 */
IEC104_SEND_BUFFER_BUSY = 3, /* 已有预留,或I帧不能取得当前发送位置 */
IEC104_SEND_BUFFER_FRAME_WRITTEN = 4 /* 字节已全部写完,尚需适用的链路完成处理及缓冲释放 */
} IEC104_SendBufferResult_t;
typedef struct {
uint64_t connection_generation; /* 非零且跨连接不复用 */
uint64_t frame_serial; /* 每次预留分配;取消后也不复用 */
} IEC104_SendFrameId_t;
/* apdu只在本次预留有效时可写,Commit/Cancel后不可继续修改。 */
typedef struct {
IEC104_SendFrameId_t frame_id; /* 预留身份,Commit/Cancel必须原样传回 */
uint8_t *apdu; /* Link直接构帧的可写地址 */
uint16_t apdu_capacity; /* I槽255字节,S/U槽6字节 */
} IEC104_SendBufferReservation_t;
/* 数值可以复制;pending_data借用队首存储,Commit_Send_Completion/Abort/Init后失效。 */
typedef struct {
IEC104_SendFrameId_t frame_id; /* 当前发送身份,用于每次增量及完成匹配 */
const uint8_t *pending_data; /* APDU起点+written_bytes,供实际I/O借用 */
uint16_t remaining_bytes; /* 本次最多可写的剩余字节数 */
uint16_t apdu_length; /* 完整报文长度,不随部分写入改变 */
uint16_t written_bytes; /* 累计成功写入字节数 */
IEC104_SendBufferFrameType_t frame_type; /* 完成分派类型,不按当前Link状态推断 */
IEC104_IFrameTx_t i_frame_snapshot; /* I帧原始快照;S/U查询时为0 */
uint16_t encoded_nr; /* I/S中实际编码的N(R),U帧为0 */
uint64_t queued_at_ms; /* 本地发送期限的原始起点 */
uint64_t written_at_ms; /* 仅整帧写完时有效,允许时刻0 */
} IEC104_SendBufferHeadInfo_t;
/* 共用写入元数据;frame_type保存上面的枚举值。 */
typedef struct {
uint64_t frame_serial; /* 本次预留分配的编号,不随槽位复用而复用 */
uint64_t queued_at_ms; /* Commit时保存的原始单调时刻 */
uint64_t written_at_ms; /* 最后一段成功写入的实际时刻,允许0 */
uint16_t apdu_length; /* 完整长度;尚未提交的预留为0 */
uint16_t written_bytes; /* 累计已写长度,不可超过apdu_length */
uint16_t encoded_nr; /* Commit从APCI读取的确认快照 */
uint8_t frame_type; /* 已提交枚举类型;尚未提交为INVALID */
} IEC104_SendBufferFrameState_t;
/* 调用方仅为控制帧分配数组,每槽只有6字节APDU,不再为S/U预留255字节。 */
typedef struct {
IEC104_SendBufferFrameState_t state; /* 本槽身份、类型、写入进度及时间 */
uint8_t apdu[IEC104_APCI_LEN]; /* S/U固定6字节报文 */
} IEC104_SendBufferControlFrame_t;
typedef struct {
IEC104_SendBufferControlFrame_t *control_frames; /* 调用方提供的S/U槽数组 */
uint16_t control_capacity; /* S/U槽总数,1..65535 */
uint16_t control_head_index; /* 控制队首下标;空队列由control_count=0表示 */
uint16_t control_count; /* 已提交S/U数量,不含正在预留的槽 */
IEC104_SendBufferFrameState_t i_frame_state; /* 唯一当前I槽的元数据 */
IEC104_IFrameTx_t i_frame_snapshot; /* 原Prepare快照,交回Link.Complete使用 */
uint8_t i_apdu[IEC104_MAX_APDU_LEN]; /* 唯一当前I帧,Commit_Send_Completion成功后可复用 */
uint8_t has_i_frame; /* 0/1:I槽已提交,包括尚未写出字节的当前帧 */
uint8_t has_reservation; /* 0/1:当前是否存在尚未Commit/Cancel的预留 */
uint8_t reservation_is_i_frame; /* 0/1:预留是否为I槽;无预留时必须为0 */
uint8_t is_initialized; /* 1表示Init成功,不表示TCP存活 */
uint64_t connection_generation; /* 本次连接的非零、不复用代次 */
uint64_t next_frame_serial; /* 下一个预留身份,耗尽时报错而不回绕 */
} IEC104_SendBuffer_t;
/* 不复制协议序号边界;Link管序号,AsduQueue保留原文,Manager保存确认记录。 */
typedef struct {
uint64_t connection_generation; /* 查询时的绑定代次 */
uint16_t control_count; /* 已提交控制帧数 */
uint16_t i_frame_ns; /* 仅has_i_frame=1时有效 */
uint8_t has_i_frame; /* 当前是否有已提交I帧 */
uint8_t has_reservation; /* 当前是否还有未结束的预留 */
uint8_t is_idle; /* 当前无I帧、S/U或预留;完成发送后也可重新空闲 */
uint8_t control_has_room; /* 无预留且控制槽未满;只表示容量,不保证编号可分配 */
} IEC104_SendBufferStatus_t;
/*
* 初始化新连接;control_capacity>=1,generation非零且跨连接不复用。
* control_frames由调用方持有;无malloc、无锁,全部操作串行,存储/参数不得重叠。
* 控制帧有独立容量,不再设置control_reserve_count或APDU节点池。
*/
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Init(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferControlFrame_t *control_frames,
uint16_t control_capacity, uint64_t connection_generation);
/* 查询只返回数值副本,OK写status;失败不改输出,不提供已完成/ACK协议边界。 */
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Get_Status(
const IEC104_SendBuffer_t *buffer, IEC104_SendBufferStatus_t *status);
/*
* S/U预留队尾,容量不足FULL;I只能在无当前帧且控制队列为空时预留,否则BUSY。
* 任一时刻只有一个预留;OK写输出,非OK不改对象或输出。编号耗尽ERROR。
* 先取得存储,再调用会产生发送义务的Link构帧接口。
*/
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Reserve_S_U_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferReservation_t *reservation);
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Reserve_I_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferReservation_t *reservation);
/*
* I提交为唯一当前帧,S/U追加到独立控制FIFO;校验APCI、身份和原始快照。
* 已准备的当前I帧(包括EAGAIN后尚未写出字节)先完成,再发后来的控制帧。
* 禁止交错写两个APDU,禁止撤回已编号的帧;Prepare后Commit失败须结束连接。
* Commit成功后借用的构帧指针不可再写;queued_at_ms是原始本地发送期限起点。
*/
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Commit_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id,
IEC104_SendBufferFrameType_t frame_type, uint16_t apdu_length,
const IEC104_IFrameTx_t *i_frame_snapshot, uint64_t queued_at_ms);
/* 核对唯一预留身份后归还存储;仅限Link尚未构帧成功时使用,不回滚协议状态。 */
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Cancel_Reservation(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id);
/* 查看已提交帧,不挑选业务;已写完但未提交完成时仍返回OK且remaining_bytes为0。上层入口为Manager.Prepare_Next_Send。 */
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Peek_Send_Head(
const IEC104_SendBuffer_t *buffer, IEC104_SendBufferHeadInfo_t *head_info);
/*
* 匹配当前帧,记录实际成功写入的增量;0不推进,部分写入OK,整帧FRAME_WRITTEN。
* 最后一次时间不得早于入队时刻;已写完不能重报,输出缓冲借用须在完成前结束。
* 同帧最多一个未回报写操作;不执行I/O、不调用Link.Complete。
*/
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Record_Written_Bytes(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id,
uint16_t bytes_written_now, uint64_t written_at_ms);
/* 对应Link.Complete及I帧本地完成记录提交后调用;TESTFR_CON无Link.Complete。成功释放当前槽,不等待ACK。 */
extern IEC104_SendBufferResult_t IEC104_SendBuffer_Commit_Send_Completion(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id);
/* 1到期、0无帧/未到期、-1非法;部分写入不续期,已写完未释放的帧也参与检查。I帧ACK的t1由Manager查询。 */
extern int IEC104_SendBuffer_Head_Send_Expired(
const IEC104_SendBuffer_t *buffer, uint64_t connection_generation,
uint64_t now_ms, uint32_t timeout_ms);
/* 旧I/O结束后清除引用;不关闭TCP,不释放调用方数组。 */
extern void IEC104_SendBuffer_Abort(IEC104_SendBuffer_t *buffer);
#ifdef __cplusplus
}
#endif
#endif /* IEC104_SENDBUFFER_H */