Files
STM32F4-Base/IEC60870-Slave/common/IEC60870_Asdu.h
T
STF cd42b4d61d
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m51s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 18s
添加遥控业务代码
2026-10-30 20:03:19 -07:00

170 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.
/*
* 101/104共用ASDU容器。公共头、VSQ/SQ、容量、IOA布局由本层维护,
* 数值、品质、命令限定词和时标的编解码由具体信息对象完成。
* 参数与字节缓冲区均由调用方提供;无动态内存或可变全局状态。
*
* 发送:具体类型Create -> Asdu_Create -> Add_InformationObject(逐个)-> Get_Data。
* 接收:Asdu_Parse -> 按info.type_id选择具体结构 -> Get_Element。
* Type ID决定载荷格式,COT表示传送原因,CA是站公共地址,IOA是点/对象地址;
* VSQ低7位是对象数,bit7是SQ。SQ=1只保存首IOA,随后各对象地址依次加1。
*/
#ifndef IEC60870_ASDU_H
#define IEC60870_ASDU_H
#include "IEC60870_Codec.h"
#include "IEC60870_InformationObjects.h"
#ifdef __cplusplus
extern "C" {
#endif
/* COT首字节的低6位,不含独立的P/N和TEST标志;不是完整的COT适用规则表。 */
typedef enum {
IEC60870_ASDU_COT_PERIODIC = 1, /* 周期/循环上报。 */
IEC60870_ASDU_COT_BACKGROUND = 2, /* 背景扫描。 */
IEC60870_ASDU_COT_SPONTANEOUS = 3, /* 突发上报。 */
IEC60870_ASDU_COT_INITIALIZED = 4, /* 初始化结束。 */
IEC60870_ASDU_COT_REQUEST = 5, /* 请求或被请求的数据。 */
IEC60870_ASDU_COT_ACTIVATION = 6, /* 激活请求。 */
IEC60870_ASDU_COT_ACTIVATION_CON = 7, /* 激活确认,P/N另行指定。 */
IEC60870_ASDU_COT_DEACTIVATION = 8, /* 停止激活请求。 */
IEC60870_ASDU_COT_DEACTIVATION_CON = 9, /* 停止激活确认。 */
IEC60870_ASDU_COT_ACTIVATION_TERMINATION = 10, /* 激活终止。 */
IEC60870_ASDU_COT_RETURN_INFO_REMOTE = 11, /* 远方命令引起的返回信息。 */
IEC60870_ASDU_COT_RETURN_INFO_LOCAL = 12, /* 当地命令引起的返回信息。 */
IEC60870_ASDU_COT_FILE_TRANSFER = 13, /* 文件传输。 */
IEC60870_ASDU_COT_INTERROGATED_BY_STATION = 20, /* 响应站总召唤的数据。 */
IEC60870_ASDU_COT_COUNTER_INTERROGATION = 37, /* 响应电能脉冲总召唤的数据。 */
IEC60870_ASDU_COT_COUNTER_GROUP_1 = 38,
IEC60870_ASDU_COT_COUNTER_GROUP_2 = 39,
IEC60870_ASDU_COT_COUNTER_GROUP_3 = 40,
IEC60870_ASDU_COT_COUNTER_GROUP_4 = 41,
IEC60870_ASDU_COT_UNKNOWN_TYPE = 44, /* 未知类型标识。 */
IEC60870_ASDU_COT_UNKNOWN_CAUSE = 45, /* 未知传送原因。 */
IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS = 46, /* 未知公共地址。 */
IEC60870_ASDU_COT_UNKNOWN_IOA = 47 /* 未知信息对象地址。 */
} IEC60870_AsduCause_t;
/* 创建ASDU时仅提供公共头;Type ID由首个成功添加的对象决定,VSQ由容器维护。
* 字段赋值前不得截窄越界整数。COT/CA/IOA的业务适用性由应用层检查。 */
typedef struct {
uint8_t cause_of_transmission; /* COT低6位,0..63,不含P/N及TEST。 */
uint8_t is_negative_confirm; /* P/N:0肯定、1否定。 */
uint8_t is_test; /* TEST:0/1。 */
uint8_t originator_address; /* 显式OA;COT为1字节时忽略,解析输出0。 */
uint16_t common_address; /* 1字节CA拒绝超过255的值,不截断。 */
} IEC60870_AsduHeader_t;
/* 调用方保存的容器;控制字段由Create/Create_From_Buffer初始化,不得手工修改。
* 字节区通过Add或字段设置接口修改;parameters在使用期间必须有效且不变。
* Create_From_Buffer借用可写原文,只允许设置公共头,不允许继续追加对象。
* 同一实例由调用方串行使用;对象不分配或释放其借用的缓冲区。 */
typedef struct {
uint8_t *buffer; /* ASDU原文,不含APCI。 */
uint16_t capacity; /* min(调用方容量, maxSizeOfASDU)。 */
uint16_t length; /* 当前字节数,空容器仅有公共头。 */
uint8_t type_id; /* 空容器为0,首个对象成功加入后固定。 */
uint8_t object_count; /* 按类型限制为0..127或0..1;借用时记录原VSQ数量。 */
uint8_t is_sequence; /* SQ:0逐对象地址,1连续地址。 */
uint8_t is_from_buffer;/* 1为借用收到的原文;0为Create创建的构造容器。 */
uint32_t first_ioa; /* 构造时供SQ连续性校验;借用原文时为0且不使用。 */
const IEC60870_AppLayerParameters_t *parameters; /* 借用参数,不复制。 */
} IEC60870_Asdu_t;
/* 结果须显式比较IEC60870_ASDU_OK;FULL=0,ERROR/UNSUPPORTED为负值。
* 所有失败保留调用方输出。结构体、输出参数和字节区不得互相重叠;
* 唯一例外是Build_Response允许输入/输出字节区重叠。 */
/* 创建空容器并固定参数、SQ和公共头;参数必须非空,头长=2+COT宽度+CA宽度。
* 有效容量不足头长返回FULL;SQ=1只适用于监视信息和文件目录。
* 初始Type ID为0,首个对象失败不会改变类型;空容器不能Get_Data。 */
IEC60870_AsduResult_t IEC60870_Asdu_Create(IEC60870_Asdu_t *self,
const IEC60870_AppLayerParameters_t *parameters,
uint8_t *buffer, uint16_t capacity, const IEC60870_AsduHeader_t *header, int is_sequence);
/**
* 功能:在调用方存储中建立可修改的ASDU对象,直接借用已有报文字节。
* 输入参数:parameters——对应原文布局的非空合法参数,使用期间有效且不变。
* buffer——非空可写ASDU原文,不含APCI;length——原文完整长度。
* 输出参数:self——成功时写入容器,失败保持原值;buffer不变。
* 返回值:OK为成功,ERROR为参数或最低长度非法。
* 说明:只检查公共头和已知类型最低长度,允许未知类型回显错误应答。
* 业务执行前仍应调用Parse进行完整校验;不复制、不分配、不追加对象。
* 字段设置会修改buffer;必须保证buffer和parameters在使用期间有效。
*/
IEC60870_AsduResult_t IEC60870_Asdu_Create_From_Buffer(IEC60870_Asdu_t *self,
const IEC60870_AppLayerParameters_t *parameters, uint8_t *buffer, uint16_t length);
/**
* 功能:原地设置ASDU的传送原因。
* 输入参数:self——合法构造容器或借用原文的对象;cause——0..63。
* 输出参数:self->buffer——仅更新COT低6位,保留P/N、TEST及其余字节。
* 返回值:OK为成功,ERROR为参数非法;失败不修改原文。
* 说明:不判断传送原因的业务适用性,也不发送报文。
*/
IEC60870_AsduResult_t IEC60870_Asdu_Set_COT(IEC60870_Asdu_t *self, int cause);
/**
* 功能:原地设置ASDU的肯定或否定确认标志。
* 输入参数:self——合法ASDU对象;is_negative——0肯定、1否定。
* 输出参数:self->buffer——仅更新P/N位,保留COT、TEST及其余字节。
* 返回值:OK为成功,ERROR为参数非法;失败不修改原文。
* 说明:设置字段不代表业务已接受命令,也不提交发送。
*/
IEC60870_AsduResult_t IEC60870_Asdu_Set_Negative(IEC60870_Asdu_t *self, int is_negative);
/**
* 功能:原地设置ASDU公共地址,适用于合法构造容器或借用可写原文的对象。
* 输入参数:self——合法ASDU对象;common_address——0..65535,须适合配置的CA宽度。
* 输出参数:self->buffer——仅更新CA字段,按小端序写入1或2字节。
* 返回值:OK为成功,ERROR为对象状态或字段宽度非法;失败不修改任何原文字节。
* 说明:偏移由COT宽度确定,保留OA、TEST、P/N及对象原文;不限制业务上的站地址范围。
* 不分配、不改变容器长度、不发送;原文修改后,旧解析视图须重新解析。
*/
IEC60870_AsduResult_t IEC60870_Asdu_Set_CA(IEC60870_Asdu_t *self, uint16_t common_address);
/* 通过对象函数表编码并复制到ASDU,成功后不再借用对象本身。
* 单类型约束、SQ连续地址和IOA宽度统一检查。字节容量不足、对象达到127个,
* 或单对象类型已加入对象时返回FULL;类型不匹配、SQ或字段非法返回ERROR。
* FULL时由调用方提交当前非空ASDU,另起一条重试同一对象,不自动发送或拆包。 */
IEC60870_AsduResult_t IEC60870_Asdu_Add_InformationObject(IEC60870_Asdu_t *self,
const IEC60870_InformationObject_t *object);
/* 获取构造容器或Create_From_Buffer对象的借用原文及当前长度。
* Create创建的空容器不可导出;借用原文对象沿用公共头最低长度检查。
* 不分配、不复制、不发送;需完整检查对象值时先调用Parse。
* 缓冲区修改后原视图失效;提交104传输时应使用2/2/3配置及<=249的总长。 */
IEC60870_AsduResult_t IEC60870_Asdu_Get_Data(const IEC60870_Asdu_t *self,
const uint8_t **asdu, uint16_t *length);
/* 统一解析67种监视、命令、初始化结束、参数和文件类型。
* 验证精确长度和所有对象,文件段按LOS检查变长载荷。
* 监视与文件目录支持SQ=0/1;参数允许多个对象但要求SQ=0。
* 其余类型要求SQ=0、数量=1;未知类型返回UNSUPPORTED。
* info借用参数和输入原文,二者在取对象期间均须有效且不变。 */
IEC60870_AsduResult_t IEC60870_Asdu_Parse(const IEC60870_AppLayerParameters_t *parameters,
const uint8_t *asdu, uint16_t length, IEC60870_AsduInfo_t *info);
/* 按Type ID调用具体解析器,向对应类型的存储写入一个按值对象。
* 例如Type 1传入&point、sizeof(point),其中point为IEC60870_SinglePointInformation_t。
* 调用方必须依据info->type_id选择正确的结构类型;object_size至少为该结构尺寸,
* 尺寸检查不能替代C语言中的类型匹配。index从0开始,输出无需预先Create。
* 对象不借用原文;公共头继续从info读取,选中对象之外不再次重复解码。 */
IEC60870_AsduResult_t IEC60870_Asdu_Get_Element(const IEC60870_AsduInfo_t *info,
uint8_t index, void *object, size_t object_size);
/* 复制请求,只更新COT及P/N,保留TEST、OA、CA、VSQ和对象原文。
* cause接受7/9/10/44..47,is_negative为0/1;否定激活确认传7、1。
* 仅检查公共头及最低长度,允许未知类型回显,不要求对象解析成功。
* 参数须对应请求布局。容量不足返回FULL,所有失败保留out及out_length。
* 输入/输出字节区支持原地与部分重叠;不执行命令或自动发送。 */
IEC60870_AsduResult_t IEC60870_Asdu_Build_Response(const IEC60870_AppLayerParameters_t *parameters,
const uint8_t *request, uint16_t length,
int cause, int is_negative, uint8_t *out, uint16_t capacity, uint16_t *out_length);
#ifdef __cplusplus
}
#endif
#endif /* IEC60870_ASDU_H */