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

498 lines
26 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,并把底层I帧通知转换为ASDU视图和应答接口。
* 职责分工:Slave管理业务入口和事件绑定;TcpServer管理socket、调度、队列及协议会话。
* 本层只保存不透明的TcpServer指针,通过公开函数操作它,不访问其成员或复制底层对象。
*
* 生命周期:Create -> Config_Default/Init -> Poll或Prepare/Process -> Close -> 读空事件 -> Destroy。
* 接收路径:TcpServer_NextEvent -> 复制原文及来源身份 -> Bind_Event -> 应用处理。
* 应答路径:IEC60870_MasterConnection_Send_* -> 本文件的发送适配 -> TcpServer_Send_Response。
* 主动上报:Slave_Enqueue_Event -> 服务级NORMAL队列,不借用某个请求的连接身份。
* 本层不自动执行业务命令或发送确认;ASDU检查及业务许可仍由应用负责。
*
* 两种身份分别解决不同的复用问题:serial标识本次Slave_Create,generation标识原TCP会话。
* 事件保存两者,防止延迟应答误发给重连的主站或静态槽复用后的另一服务。
* 所有调用在所属事件循环线程串行执行;静态池没有锁,Create/Destroy也须跨服务串行。
*/
#include "IEC104_Slave.h"
#include "IEC104_TcpServer.h"
#include "IEC104_TcpServer_Config.h"
#include <string.h>
/* 104专用默认布局来自IEC104_Types.h;共用ASDU模块通过参数工作,不硬编码104布局。
* 静态只读存储供接收视图和主动上报共用,返回的参数指针不依赖某个服务或事件的寿命。 */
static const IEC60870_AppLayerParameters_t parameters104 = IEC104_APP_LAYER_PARAMETERS_DEFAULT;
/* 完整类型仅在本文件可见;业务只保存Create返回的指针。 */
struct IEC104_Slave {
IEC104_TcpServer_t *server; /* 私有TCP服务;NULL表示该Slave槽空闲,Close不会清除此成员。 */
uint64_t serial; /* 每次成功Create递增,Destroy保留;到UINT64_MAX后不再复用该槽。 */
};
_Static_assert(IEC104_TCP_SERVER_CAPACITY > 0U, "Slave needs at least one service slot");
/* Slave和TcpServer各有对象池;每个Slave占用一个底层名额,直接创建TCP服务也占该名额。 */
static IEC104_Slave_t slave_pool[IEC104_TCP_SERVER_CAPACITY];
static bool Send_ASDU(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu);
/**
* 功能:判断句柄是否指向当前占用的Slave静态槽,NULL和池外地址返回false。
* 说明:先按地址查池再解引用;server非空只表示拥有底层对象,不代表已Init或已连接。
* 槽复用后,单独的旧句柄副本无法与新句柄区分;Destroy成功后业务必须停止使用旧句柄。
*/
static bool Slave_Owned(const IEC104_Slave_t *self)
{
size_t index;
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
if (self == &slave_pool[index]) { return self->server != NULL; }
}
return false;
}
/**
* 功能:返回本层校验失败的结果,同时完整填写可选的error。
* 输入参数:result——本层检测出的错误;error——调用方诊断存储,允许NULL。
* 说明:阶段统一为VALIDATE,socket_error为0;底层操作的诊断直接透传,不在这里改写。
*/
static IEC104_TcpResult_t Slave_Error(IEC104_TcpResult_t result, IEC104_TcpError_t *error)
{
if (error != NULL) {
error->result = result;
error->stage = IEC104_TCP_STAGE_VALIDATE;
error->socket_error = 0;
}
return result;
}
/**
* 功能:从共用连接接口取回它所属的Slave事件;接口不匹配时返回NULL。
* 说明:同时核对发送函数和事件内接口的地址,拒绝赋值/memcpy产生的未修复副本。
* 此处只检查接口绑定,服务所有权、serial和连接代次由发送路径继续检查。
* connection及其object必须仍指向有效存储,不能用此检查代替业务的对象寿命管理。
*/
static IEC104_SlaveEvent_t *Get_Context(const IEC60870_MasterConnection_t *connection)
{
IEC104_SlaveEvent_t *event;
if (connection == NULL || connection->send_asdu != Send_ASDU || connection->object == NULL) {
return NULL;
}
event = (IEC104_SlaveEvent_t *)connection->object;
return &event->connection == connection ? event : NULL;
}
/**
* 功能:确认待发送ASDU使用可由本接入层处理的104应用层布局。
* 返回值:基础参数合法、COT/CA/IOA为2/2/3字节且ASDU上限不超过249时为true。
* 说明:允许业务采用更小的最大长度或不同默认OA;这里不检查具体信息对象和业务权限。
*/
static bool Parameters_Compatible(const IEC60870_AppLayerParameters_t *parameters)
{
return IEC60870_AppLayerParameters_Valid(parameters) &&
parameters->sizeOfCOT == 2 && parameters->sizeOfCA == 2 && parameters->sizeOfIOA == 3 &&
parameters->maxSizeOfASDU <= IEC104_MAX_ASDU_LEN;
}
/**
* 功能:实现共用连接的send_asdu,把应答交给产生该事件的原服务、原会话。
* 输入参数:connection——事件内已绑定的接口;asdu——布局兼容的完整ASDU,可独立于接收视图。
* 返回值:仅底层SUBMIT_OK转换为true;WAIT/ERROR/FATAL返回false并记录在原事件中。
* 说明:先检查Slave所有权和serial,再由TcpServer检查generation、启停状态及发送容量。
* OK表示已复制接手,不代表写完或收到ACK;WAIT未接手,业务保存原事件/ASDU后择机重试。
* 不查找当前新连接来替换旧身份;FATAL可能已经写出字节,不能作为未发送直接重放。
*/
static bool Send_ASDU(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu)
{
IEC104_SlaveEvent_t *event = Get_Context(connection);
const uint8_t *data;
uint16_t length;
if (event == NULL) { return false; }
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
if (!Slave_Owned(event->_owner) || event->_owner->serial != event->_owner_serial ||
asdu == NULL || !Parameters_Compatible(asdu->parameters) ||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK) {
return false;
}
event->_last_submit_result = IEC104_TcpServer_Send_Response(
event->_owner->server, event->_generation, data, length);
return event->_last_submit_result == IEC104_TCP_SUBMIT_OK;
}
/**
* 功能:修改ASDU的传送原因和否定标志,再沿原连接提交确认或终止。
* 输入参数:cause——目标COT;negative——目标P/N;asdu——允许原地修改的ASDU。
* 返回值:true已接手;false表示字段修改或提交失败,详细提交结果保存在事件中。
* 说明:按COT、P/N顺序修改,后续失败不回滚已经成功的修改;不执行业务,也不自动重试。
*/
static bool Send_Confirmation(IEC60870_MasterConnection_t *connection,
IEC60870_Asdu_t *asdu, int cause, bool negative)
{
IEC104_SlaveEvent_t *event = Get_Context(connection);
if (event == NULL) { return false; }
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
if (IEC60870_Asdu_Set_COT(asdu, cause) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_Negative(asdu, negative ? 1 : 0) != IEC60870_ASDU_OK) {
return false;
}
return Send_ASDU(connection, asdu);
}
/* 共用激活确认入口:COT=7,P/N由业务指定;沿用Send_Confirmation的修改及提交契约。 */
static bool Send_ACT_CON(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu, bool negative)
{
return Send_Confirmation(connection, asdu, IEC60870_ASDU_COT_ACTIVATION_CON, negative);
}
/* 共用激活终止入口:COT=10、P/N=0;由业务处理完成后调用,不自动跟随ACT_CON发送。 */
static bool Send_ACT_TERM(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu)
{
return Send_Confirmation(connection, asdu, IEC60870_ASDU_COT_ACTIVATION_TERMINATION, false);
}
/* 共用参数查询入口:有效事件接口返回静态只读参数;不要求原连接仍在线,也不表示可以发送。 */
static const IEC60870_AppLayerParameters_t *Get_Parameters(IEC60870_MasterConnection_t *connection)
{
return Get_Context(connection) != NULL ? &parameters104 : NULL;
}
/**
* 功能:让目标事件的ASDU视图和共用发送接口指向目标自身的存储。
* 前提:transport原文已复制完毕,事件地址在后续使用期间保持稳定。
* 输出参数:event->asdu/connection/asdu_result;不改来源身份、原文和历史提交结果。
* 说明:非I帧不提供ASDU接口;I帧视图构造失败时保留原通知供诊断,connection保持清零。
* 视图借用本事件中APCI之后的ASDU字节,不再借用TcpServer或Receiver的内部缓冲。
*/
static void Bind_Event(IEC104_SlaveEvent_t *event)
{
uint16_t length;
memset(&event->asdu, 0, sizeof(event->asdu));
memset(&event->connection, 0, sizeof(event->connection));
event->asdu_result = IEC60870_ASDU_UNSUPPORTED;
if (event->transport.type != IEC104_TCP_EVENT_I_FRAME) { return; }
event->asdu_result = IEC60870_ASDU_ERROR;
length = event->transport.data.i_frame.apdu_length;
if (length < IEC104_APCI_LEN || length > IEC104_MAX_APDU_LEN) { return; }
event->asdu_result = IEC60870_Asdu_Create_From_Buffer(&event->asdu, &parameters104,
event->transport.data.i_frame.apdu + IEC104_APCI_LEN, (uint16_t)(length - IEC104_APCI_LEN));
if (event->asdu_result != IEC60870_ASDU_OK) { return; }
event->connection.object = event;
event->connection.send_asdu = Send_ASDU;
event->connection.send_act_con = Send_ACT_CON;
event->connection.send_act_term = Send_ACT_TERM;
event->connection.get_application_layer_parameters = Get_Parameters;
}
/**
* 功能:从静态池取得一个Slave,并为它取得一个私有TcpServer。
* 返回值:成功返回不透明句柄;任一池无可用槽或身份编号耗尽时返回NULL。
* 说明:不创建socket、不分配堆内存;只有取得底层服务后才占用Slave槽并递增serial。
* 成功后由业务配对Destroy;不能自行分配、复制、清零或free句柄指向的对象。
*/
IEC104_Slave_t *IEC104_Slave_Create(void)
{
size_t index;
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
IEC104_Slave_t *self = &slave_pool[index];
if (self->server == NULL && self->serial != UINT64_MAX) {
IEC104_TcpServer_t *server = IEC104_TcpServer_Create();
if (server == NULL) { return NULL; }
self->server = server;
++self->serial;
return self;
}
}
return NULL;
}
/**
* 功能:归还已关闭且通知已读空的Slave及其底层服务。
* 输入参数:self——Create取得且未Destroy的句柄;error——可选的本次调用诊断。
* 返回值:OK时句柄失效;运行中为INVALID_STATE,通知未读空为EVENTS_PENDING,失败保留对象。
* 说明:先由TcpServer检查并归还底层槽,再释放Slave槽;保留serial隔离旧事件。
* 常规顺序为Close -> NextEvent读空 -> Destroy;尚未Init或Init失败后也可直接归还。
*/
IEC104_TcpResult_t IEC104_Slave_Destroy(IEC104_Slave_t *self, IEC104_TcpError_t *error)
{
IEC104_TcpResult_t result;
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
result = IEC104_TcpServer_Destroy(self->server, error);
if (result == IEC104_TCP_OK) { self->server = NULL; }
return result;
}
/**
* 功能:填写默认监听地址0.0.0.0、端口2404及协议参数。
* 输出参数:config——调用方可继续调整的配置;NULL时无操作。
* 说明:复用TcpServer默认值,不占用服务槽;ASDU布局另由parameters104提供。
*/
void IEC104_Slave_Config_Default(IEC104_SlaveConfig_t *config)
{
IEC104_TcpServer_Config_Default(config);
}
/**
* 功能:初始化所拥有的TCP服务,开始监听并建立服务级发送队列。
* 输入参数:self——有效Slave句柄;config——本次监听和协议配置,底层复制后使用。
* 输出参数:error——本次诊断,允许NULL;底层返回的阶段和网络错误保持原样。
* 返回值:OK已监听,其他结果区分参数、配置、生命周期或网络失败。
* 说明:成功不表示已有主站,之后必须驱动Poll或Prepare/Process;失败不销毁句柄。
* 再次Init前须Close并读空旧通知,旧事件的连接身份不会因重新初始化而更新。
*/
IEC104_TcpResult_t IEC104_Slave_Init(IEC104_Slave_t *self,
const IEC104_SlaveConfig_t *config, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Init(self->server, config, error);
}
/**
* 功能:为外部事件循环查询socket关注集合、等待期限及本地待处理工作。
* 输入参数:self——已Init的服务;info——非NULL快照存储;error——可选诊断。
* 返回值:OK时写出info;失败保持info不变,不进行收发或协议推进。
* 说明:有待消费事件或need_process时不得阻塞等待;状态改变后须重新Prepare。
* 快照中的socket仅供注册,所有权仍属于内部TcpServer,业务不能直接读写或关闭。
*/
IEC104_TcpResult_t IEC104_Slave_Prepare(const IEC104_Slave_t *self,
IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Prepare(self->server, info, error);
}
/**
* 功能:把外部事件循环的一批就绪、定时或本地通知交给底层有界推进。
* 输入参数:self——已Init的服务;ready——必填就绪批次,身份来自对应Prepare/注册快照。
* 输出参数:error——可选诊断;返回值与TcpServer_Process一致,OK也包括没有进展。
* 说明:不等待;返回后消费NextEvent,再Prepare更新关注集合。旧通知不得替换为新token。
* 客户端故障通过收尾事件交付,服务级失败则由调用方保存诊断后Close。
*/
IEC104_TcpResult_t IEC104_Slave_Process(IEC104_Slave_t *self,
const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Process(self->server, ready, error);
}
/**
* 功能:独立主循环使用的准备、至多一次网络等待和有界推进组合入口。
* 输入参数:self——已Init的服务;max_wait_ms——本轮网络等待上限,0不等待;error可为NULL。
* 返回值:OK表示服务可继续;客户端断线不一定使本函数失败,须读取NextEvent。
* 说明:等待还受协议期限和本地工作约束;max_wait_ms不保证整个函数的执行耗时。
* 不创建后台线程、不调用业务处理函数;每轮返回后由应用消费通知。
*/
IEC104_TcpResult_t IEC104_Slave_Poll(IEC104_Slave_t *self,
uint32_t max_wait_ms, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Poll(self->server, max_wait_ms, error);
}
/**
* 功能:取出最早的TCP通知,并为有效I帧建立ASDU视图和原连接的发送接口。
* 输入参数:self——有效句柄,Close后仍可读取;event——地址稳定的非NULL输出存储。
* 返回值:READY替换整个event;EMPTY/ERROR保持上次事件及其视图不变。
* 说明:先读入临时量,只有取得通知后才覆盖输出;保存原服务serial和连接generation。
* READY仅表示取得事件,I帧还须检查asdu_result;视图失败仍保留原文,不自动确认。
* 不进行I/O;下一次READY会覆盖同一event,延迟处理须另存并调用Copy_Event修复自引用。
*/
IEC104_TcpEventResult_t IEC104_Slave_NextEvent(IEC104_Slave_t *self, IEC104_SlaveEvent_t *event)
{
IEC104_TcpEvent_t received;
IEC104_TcpEventResult_t result;
if (!Slave_Owned(self) || event == NULL) { return IEC104_TCP_EVENT_ERROR; }
result = IEC104_TcpServer_NextEvent(self->server, &received);
if (result != IEC104_TCP_EVENT_READY) { return result; }
memset(event, 0, sizeof(*event));
event->transport = received;
event->_owner = self;
event->_owner_serial = self->serial;
event->_generation = received.generation;
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
Bind_Event(event);
return IEC104_TCP_EVENT_READY;
}
/* 只校验事件自身布局和自引用,不要求原服务或TCP会话仍存活。
* Copy_Event允许保存历史原文;Get_Event_Identity在此基础上额外校验当前服务实例。
* 解析失败的I帧仍是有效通知,由业务处理其asdu_result,不在此丢弃诊断信息。 */
static bool Event_Valid(const IEC104_SlaveEvent_t *event)
{
const uint8_t *data;
uint16_t length;
if (event == NULL || event->_owner == NULL || event->_owner_serial == 0U ||
event->_generation == 0U || event->_generation != event->transport.generation ||
event->transport.type < IEC104_TCP_EVENT_CONNECTED ||
event->transport.type > IEC104_TCP_EVENT_DEACTIVATED) {
return false;
}
if (event->transport.type == IEC104_TCP_EVENT_I_FRAME && event->asdu_result == IEC60870_ASDU_OK) {
if (event->connection.object != event || Get_Context(&event->connection) != event ||
event->asdu.parameters != &parameters104 ||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK ||
data != event->transport.data.i_frame.apdu + IEC104_APCI_LEN ||
(uint32_t)length + IEC104_APCI_LEN != event->transport.data.i_frame.apdu_length) {
return false;
}
}
return true;
}
/**
* 功能:把事件复制到稳定的目标存储,同时重建目标ASDU视图及发送接口的自引用。
* 输入参数:source——NextEvent或本函数产生的有效事件;destination——目标事件存储。
* 返回值:true已复制或源/目标为同一合法对象;false校验失败且不修改目标。
* 说明:先核对源视图仍借用源原文,避免传播普通赋值形成的悬空绑定。
* 保留原服务、代次和最近提交结果,不查询当前主站、不发送;历史事件也可复制保存。
* 复制只延长原文和接口存储的寿命,不能使已关闭或已被替换的原连接重新可用。
*/
bool IEC104_Slave_Copy_Event(IEC104_SlaveEvent_t *destination, const IEC104_SlaveEvent_t *source)
{
if (destination == NULL || !Event_Valid(source)) { return false; }
if (destination != source) { *destination = *source; Bind_Event(destination); }
return true;
}
/**
* 功能:验证业务收到的Slave事件,交付可保存的服务实例和原连接身份。
* 参数:self为所属服务;event为原事件或Copy_Event副本;identity为非NULL输出。
* 返回:true写出身份;false为非法来源/布局/视图或参数,保持输出不变。
* 说明:只匹配服务实例,不用当前TCP代次拒绝历史通知;是否仍可推进另由Check_Request判断。
*/
bool IEC104_Slave_Get_Event_Identity(const IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, IEC104_SlaveEventIdentity_t *identity)
{
IEC104_SlaveEventIdentity_t copy;
if (!Slave_Owned(self) || identity == NULL || !Event_Valid(event) ||
event->_owner != self || event->_owner_serial != self->serial) {
return false;
}
memset(&copy, 0, sizeof(copy));
copy.slave = self;
copy.instance_id = event->_owner_serial;
copy.generation = event->_generation;
*identity = copy;
return true;
}
/**
* 功能:统一判断业务是否应先处理通知、放弃失效请求,或继续当前会话的工作。
* 参数:identity为Get_Event_Identity的只读结果,允许零初始化表示尚未绑定会话。
* 返回:ERROR为参数/查询失败;WAIT_EVENTS优先于会话判断;STALE为原身份不再可用;READY可继续。
* 说明:检查通知FIFO、服务实例、TCP代次和传输激活状态,不执行I/O或保留发送窗口。
* 先读空通知可避免同一代次STOP后又START时,仅凭最终激活状态误续旧业务。
*/
IEC104_SlaveRequestState_t IEC104_Slave_Check_Request(const IEC104_Slave_t *self,
const IEC104_SlaveEventIdentity_t *identity)
{
IEC104_TcpServerStatus_t status;
if (!Slave_Owned(self) || identity == NULL ||
IEC104_TcpServer_Get_Status(self->server, &status, NULL) != IEC104_TCP_OK) {
return IEC104_SLAVE_REQUEST_ERROR;
}
if (status.pending_event_count != 0U) { return IEC104_SLAVE_REQUEST_WAIT_EVENTS; }
if (identity->slave != self || identity->instance_id != self->serial ||
identity->generation == 0U || identity->generation != status.generation ||
!status.connected || !status.connection_status_valid || !status.connection.data_transfer_started) {
return IEC104_SLAVE_REQUEST_STALE;
}
return IEC104_SLAVE_REQUEST_READY;
}
/**
* 功能:补充共用发送接口bool结果,查询这个事件连接最近一次提交的具体状态。
* 输入参数:connection——event.connection的地址;不是Slave句柄,也不是底层socket。
* 返回值:OK/WAIT/ERROR/FATAL;尚未发送或接口绑定无效时返回ERROR。
* 说明:记录只在提交时更新,不随后续Poll或主站ACK更新;发送完成事实应查看RECORD事件。
*/
IEC104_TcpSubmitResult_t IEC104_Slave_Get_Last_Submit_Result(
const IEC60870_MasterConnection_t *connection)
{
const IEC104_SlaveEvent_t *event = Get_Context(connection);
return event != NULL ? event->_last_submit_result : IEC104_TCP_SUBMIT_ERROR;
}
/**
* 功能:取得本接入使用的104应用层参数,供业务构造主动上报或独立应答ASDU。
* 输入参数:self——有效Slave句柄,不要求已Init或当前在线。
* 返回值:静态只读参数地址;无效句柄返回NULL,调用方不能修改或释放该参数。
*/
const IEC60870_AppLayerParameters_t *IEC104_Slave_Get_Application_Layer_Parameters(
const IEC104_Slave_t *self)
{
return Slave_Owned(self) ? &parameters104 : NULL;
}
/**
* 功能:校验ASDU布局后复制到服务级NORMAL队列,用于主动上报。
* 输入参数:self——已Init的服务;asdu——合法且与104布局兼容的ASDU,不包含APCI。
* 返回值:OK已复制,WAIT未接手可重试,ERROR参数/状态非法,FATAL底层队列异常。
* 说明:允许首次接入前及断线期间缓存,本次不直接写socket;空间不足可覆盖最旧NORMAL。
* Close清空缓存;重连恢复可能重复,业务应按自身语义处理。请求应答使用事件connection。
*/
IEC104_TcpSubmitResult_t IEC104_Slave_Enqueue_Event(IEC104_Slave_t *self, IEC60870_Asdu_t *asdu)
{
const uint8_t *data;
uint16_t length;
if (!Slave_Owned(self) || asdu == NULL || !Parameters_Compatible(asdu->parameters) ||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK) {
return IEC104_TCP_SUBMIT_ERROR;
}
return IEC104_TcpServer_Enqueue_Event(self->server, data, length);
}
/**
* 功能:读取当前主站的IPv4、源端口和连接代次副本,不执行网络查询。
* 输入参数:self——已Init的服务;peer——非NULL输出存储;error——可选诊断。
* 返回值:仅OK写peer;无当前连接为NOT_CONNECTED,其他失败也保持peer原值。
* 说明:这是当前状态;历史事件的地址应从事件自身读取,不能用新主站地址替代旧来源。
*/
IEC104_TcpResult_t IEC104_Slave_Get_Remote_Address(const IEC104_Slave_t *self,
IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Get_Remote_Address(self->server, peer, error);
}
/**
* 功能:按事件捕获的原连接身份主动断开主站,保留监听和服务级NORMAL。
* 输入参数:self——所属Slave;event——本服务事件或Copy_Event副本;error——可选诊断。
* 返回值:OK已断开;其他服务/已复用Slave的事件为INVALID_ARGUMENT;当前无连接为NOT_CONNECTED;
* 仍有连接但代次不同为STALE_GENERATION;底层服务异常通过原结果和error返回。
* 说明:来源serial先由本层检查,原generation再由TcpServer核对;不用当前代次代替旧身份。
* 不等待STOPDT或ACK,成功后继续读取收尾事件,再Prepare更新监听条件。
*/
IEC104_TcpResult_t IEC104_Slave_Close_Connection(IEC104_Slave_t *self,
const IEC104_SlaveEvent_t *event, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
if (event == NULL || event->_owner != self || event->_owner_serial != self->serial ||
event->_generation == 0U) {
return Slave_Error(IEC104_TCP_INVALID_ARGUMENT, error);
}
return IEC104_TcpServer_Close_Connection(self->server, event->_generation, error);
}
/**
* 功能:取得内部TCP服务、队列和最近会话的状态副本,不推进协议或消费队列。
* 输入参数:self——有效句柄;status——非NULL输出存储;error——可选诊断。
* 返回值:OK写入status,失败保持输出;Create后、Init失败后和Close后均可查询。
* 说明:会话和队列各自有有效标志,connected与data_transfer_started含义不同。
*/
IEC104_TcpResult_t IEC104_Slave_Get_Status(const IEC104_Slave_t *self,
IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Get_Status(self->server, status, error);
}
/**
* 功能:关闭监听和当前连接,清空待发ASDU,但保留已生成的业务及收尾通知。
* 输入参数:self——尚未Destroy的句柄;error——可选的本次调用诊断。
* 返回值:OK已收尾或本来未初始化;无效句柄、重入按底层约定返回错误。
* 说明:不等待业务发送/ACK,不归还对象池名额;读空事件后可重新Init或Destroy。
* 自建事件循环还须撤销旧socket注册和定时器,不能将其通知用于下一次Init。
*/
IEC104_TcpResult_t IEC104_Slave_Close(IEC104_Slave_t *self, IEC104_TcpError_t *error)
{
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
return IEC104_TcpServer_Close(self->server, error);
}