1268 lines
69 KiB
C
1268 lines
69 KiB
C
/*
|
||
* 模块职责:IEC104单点/双点遥控(45/46),处理校验、选择、执行、撤销、期限及协议应答。
|
||
* 数据来源:Slave交付连接/启停通知和I帧;应用通过纯值业务事件显式决定、启动和结束操作。
|
||
* 本模块不维护设备点表、不直接控制硬件、不创建线程,也不保存应用回调或申请堆内存。
|
||
*
|
||
* 状态主线:SELECT_WAIT -> SELECTED -> EXECUTE_WAIT -> EXECUTING -> ENDED;
|
||
* 允许直接执行时从EXECUTE_WAIT开始,拒绝、撤销、超时及会话失效可提前进入ENDED。
|
||
* Accept消费工作事件后最多调用一次动作;Report_Result停止设备执行期限;Complete关闭返回信息阶段。
|
||
* ENDED只表示协议交互结束,不保证物理输出已停止,也不立即代表事务槽可以复用。
|
||
*
|
||
* 存储与所有权:一个事务槽保存工作请求、业务事件和必要回复,独立短应答使用固定池。
|
||
* 模块借用Slave;事务持有一个站点引用直到槽释放;独立短应答只保留会话身份和原文字节。
|
||
* 应用若跨循环持有站点上下文或异步设备资源,须提前取得自己的引用,并自行维护设备忙状态。
|
||
* FINISHED已消费(或无需交付)且事务自身回复全部交接/取消,才归还事务槽及模块站点引用。
|
||
*
|
||
* 发送顺序:事务确认、终止、撤销/不匹配回复与独立短应答共用递增序号,只有最早者可提交。
|
||
* 返回信息不在模块缓存,先等待模块FIFO清空,再通过原连接的Try接口固定帧序。
|
||
* 发送OK只是发送层接手,不等于socket写完或主站ACK;WAIT未接手,只可重试发送,不能重做动作。
|
||
*
|
||
* 执行约束:全部接口与Slave在同一事件循环串行调用;修改入口在动作回调期间拒绝重入。
|
||
* Handle_Event按FIFO消费通知,不受后续未读通知的门禁阻挡;动作和发送先查会话,再判期限。
|
||
* FINISHED可在离线时继续读取;取消不回滚设备,实例/事务/事件身份阻止迟到调用操作新事务。
|
||
*/
|
||
#include "IEC104_Command.h"
|
||
#include "IEC104_TcpServer_Config.h"
|
||
#include "IEC104_Net.h"
|
||
#include <string.h>
|
||
|
||
#ifndef IEC104_COMMAND_CAPACITY
|
||
/* 实例数默认与TCP服务池一致;每个Slave至多绑定一个遥控实例。 */
|
||
#define IEC104_COMMAND_CAPACITY IEC104_TCP_SERVER_CAPACITY
|
||
#endif
|
||
#ifndef IEC104_COMMAND_REPLY_CAPACITY
|
||
/* 独立短回复容量;事务自己的必要确认、终止及撤销回复不占用这些槽。 */
|
||
#define IEC104_COMMAND_REPLY_CAPACITY 4U
|
||
#endif
|
||
_Static_assert(IEC104_COMMAND_CAPACITY > 0U, "Command needs instance storage");
|
||
_Static_assert(IEC104_COMMAND_REPLY_CAPACITY > 0U &&
|
||
IEC104_COMMAND_REPLY_CAPACITY < UINT32_MAX / 2U, "Command needs bounded reply storage");
|
||
|
||
/* 唯一事务槽。槽地址固定,当前工作事件、历史已交付事件身份和待发应答分别记账。 */
|
||
typedef struct {
|
||
IEC104_SlaveEventIdentity_t identity; /* 原服务实例和连接代次;不能替换为重连后的当前身份。 */
|
||
/* request_bytes:当前选择/执行请求,供动作、事件、ACT_CON及ACT_TERM使用。
|
||
* reply_bytes:撤销或不匹配执行请求,仅供对应短回复回显,不覆盖工作请求。 */
|
||
uint8_t request_bytes[10], reply_bytes[10];
|
||
IEC104_Station_t *station; /* 本事务Acquire的原站点;删除注册后仍持有,Reap时Release。 */
|
||
/* transaction_id:实例内单调事务号;receive_serial:当前工作请求的原I帧接收号。
|
||
* accepted_ms:当前请求在Handle_Event受理的时刻;timer_start_ms:当前阶段的单调计时起点。 */
|
||
uint64_t transaction_id, receive_serial, accepted_ms, timer_start_ms;
|
||
/* event_serial:当前工作/终结事件号;accepted_event_serial:已消费工作事件号,0表示尚未消费。
|
||
* select_event_serial/execute_event_serial:本事务两个工作事件的历史身份,供只读查询验证。 */
|
||
uint64_t event_serial, accepted_event_serial, select_event_serial, execute_event_serial;
|
||
/* confirm_serial/term_serial/reply_serial:待交确认、终止及撤销/不匹配回复的全局FIFO号,0表示无。 */
|
||
uint64_t confirm_serial, term_serial, reply_serial;
|
||
/* phase:事务阶段;event_type:当前事件种类;finish_reason:首次终结原因,后续取消不得覆盖。
|
||
* result:设备执行结果,0表示尚未报告;confirm_cot/reply_cot:两类待发回复各自的传送原因。 */
|
||
uint8_t phase, event_type, finish_reason, result, confirm_cot, reply_cot;
|
||
/* occupied:槽被占用;event_taken:当前事件已交付;ever_work_delivered:曾交付过任一工作事件。
|
||
* finish_pending:仍欠应用一个FINISHED;该义务不能因创建新的EXECUTE事件而丢失。 */
|
||
bool occupied, event_taken, ever_work_delivered, finish_pending;
|
||
/* select_delivered/execute_delivered:对应历史工作事件确实交付过,避免伪造查询身份。
|
||
* selected:当前执行经过了本事务的选择匹配,直接执行时为false。 */
|
||
bool select_delivered, execute_delivered, selected;
|
||
/* confirm_negative:当前确认P/N;confirm_try:确认是否采用不排队Try路径。
|
||
* send_waiting:事务回复上次提交遇到背压;return_waiting:应用返回信息上次提交遇到背压。 */
|
||
bool confirm_negative, confirm_try, send_waiting, return_waiting;
|
||
/* confirm_submitted:当前工作请求确认已被发送层接手;completed:Complete已关闭返回阶段。
|
||
* term_submitted:ACT_TERM已被发送层接手;select_effect/execute_effect:动作可能留有设备副作用。 */
|
||
bool confirm_submitted, completed, term_submitted, select_effect, execute_effect;
|
||
} CommandTransaction;
|
||
|
||
/* 与事务槽分离的拒绝回复,不保存站点指针,也不产生应用业务事件。 */
|
||
typedef struct {
|
||
IEC104_SlaveEventIdentity_t identity; /* 请求原会话身份;断线/换代清理,不能跨连接重发。 */
|
||
uint8_t bytes[10]; /* 单个完整命令原文,VSQ已规范为1;提交时仅改COT及P/N。 */
|
||
uint64_t serial; /* 与事务内回复共用的生成序号,决定全局FIFO位置。 */
|
||
uint8_t cot; /* 错误原因或否定ACT_CON/DEACT_CON的COT;独立回复P/N固定为1。 */
|
||
bool occupied, send_waiting; /* 槽占用标志;上次发送WAIT标志,用于避免need_process空转。 */
|
||
} CommandReply;
|
||
|
||
struct IEC104_Command {
|
||
IEC104_Slave_t *slave; /* 借用的所属服务,寿命必须覆盖本实例。 */
|
||
IEC104_SlaveEventIdentity_t session; /* 已按FIFO消费的连接/启停通知所确定的会话身份。 */
|
||
IEC104_CommandConfig_t config; /* Create时按值复制的配置;不借用调用方配置指针。 */
|
||
IEC104_CommandStatus_t status; /* 累计计数及模块活动状态;调度字段由Get_Status即时计算。 */
|
||
CommandTransaction transaction; /* 固定地址的唯一事务;占用期间所有站点的新遥控均忙拒绝。 */
|
||
CommandReply replies[IEC104_COMMAND_REPLY_CAPACITY]; /* 独立短回复池;满时丢新、不覆盖。 */
|
||
/* serial:进程内唯一实例号;next_transaction/next_event/next_reply:实例内身份计数器。
|
||
* last_generation:已接手CONNECTED的最高代次,断线后保留,用于拒绝旧连接通知。 */
|
||
uint64_t serial, next_transaction, next_event, next_reply, last_generation;
|
||
bool in_use, in_action; /* 静态实例池占用标志;同步动作回调的修改入口重入保护。 */
|
||
};
|
||
|
||
/* 静态池不迁移对象、不进行堆分配;实例编号跨Destroy保留递增,旧事件不能匹配复用的池槽。 */
|
||
static IEC104_Command_t command_pool[IEC104_COMMAND_CAPACITY];
|
||
static uint64_t next_instance;
|
||
|
||
/*
|
||
* 功能:验证句柄是否指向本模块当前占用的静态池槽。
|
||
* 参数:self为待检查地址,可以是NULL或不属于池的地址。
|
||
* 返回:归属合法且in_use为true时返回true;否则false,不修改状态。
|
||
* 约束:先比较地址再解引用,所有公开入口据此拒绝无效句柄。
|
||
*/
|
||
static bool Owned(const IEC104_Command_t *self)
|
||
{
|
||
unsigned i;
|
||
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
|
||
if (self == &command_pool[i]) { return self->in_use; }
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* 功能:从固定104命令布局读取24位小端信息对象地址。
|
||
* 参数:bytes指向至少10字节的45/46 ASDU;调用方已完成长度检查。
|
||
* 返回:0..0xFFFFFF的IOA;不判断该地址是否存在或适用于遥控。
|
||
*/
|
||
static uint32_t Read_IOA(const uint8_t *bytes)
|
||
{
|
||
return (uint32_t)bytes[6] | ((uint32_t)bytes[7] << 8) | ((uint32_t)bytes[8] << 16);
|
||
}
|
||
|
||
/*
|
||
* 功能:从固定104布局读取16位小端公共地址。
|
||
* 参数:bytes为具有完整6字节公共头的ASDU,COT/CA宽度均为2。
|
||
* 返回:原CA数值;不查询站点注册表,不修改原文。
|
||
*/
|
||
static uint16_t Read_CA(const uint8_t *bytes)
|
||
{
|
||
return (uint16_t)((uint16_t)bytes[4] | ((uint16_t)bytes[5] << 8));
|
||
}
|
||
|
||
/*
|
||
* 功能:从事务内部保留值重建交给应用的纯值请求。
|
||
* 参数:task为占用且持有站点引用的事务;request为非NULL输出。
|
||
* 输出:完整初始化request,含当前原文取值、站点注册号及受理时刻。
|
||
* 返回:无;不改变事务、不分配内存。
|
||
* 约束:动作不能读取应用回传event中的可修改request,必须以本函数的内部值为准。
|
||
*/
|
||
static void Copy_Request(const CommandTransaction *task, IEC104_CommandRequest_t *request)
|
||
{
|
||
memset(request, 0, sizeof(*request));
|
||
request->generation = task->identity.generation;
|
||
request->receive_serial = task->receive_serial;
|
||
request->station_id = IEC104_Station_Get_Id(task->station);
|
||
request->common_address = Read_CA(task->request_bytes);
|
||
request->originator_address = task->request_bytes[3];
|
||
request->type_id = task->request_bytes[0];
|
||
request->ioa = Read_IOA(task->request_bytes);
|
||
request->is_select = (task->request_bytes[9] & 0x80U) != 0U;
|
||
request->selected = task->selected;
|
||
request->state = task->request_bytes[9] & (request->type_id == 45U ? 1U : 3U);
|
||
request->qualifier = (task->request_bytes[9] >> 2) & 31U;
|
||
request->accepted_ms = task->accepted_ms;
|
||
}
|
||
|
||
/*
|
||
* 功能:统计模块仍持有、尚未被发送层接手的回复数。
|
||
* 参数:self为有效实例。
|
||
* 返回:事务内最多三种回复与全部已占用独立短回复的总数。
|
||
* 约束:不包括已经进入HIGH/当前发送帧的回复,也不包括应用尚未交付的返回信息。
|
||
*/
|
||
static unsigned Pending_Replies(const IEC104_Command_t *self)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
unsigned i, count = (task->confirm_serial != 0U ? 1U : 0U) +
|
||
(task->term_serial != 0U ? 1U : 0U) + (task->reply_serial != 0U ? 1U : 0U);
|
||
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
|
||
if (self->replies[i].occupied) { ++count; }
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/*
|
||
* 功能:在应用清理通知与本事务发送收尾均完成后释放事务槽。
|
||
* 参数:self为有效实例;允许事务尚未结束或已经空闲,此时无操作。
|
||
* 输出:满足条件时Release模块自己的站点引用,并清零事务全部状态。
|
||
* 返回:无;不清理独立短应答、不关闭连接、不回滚设备动作。
|
||
* 约束:只有ENDED且FINISHED已读取(或无需生成)、本事务回复已全部交接(或取消),
|
||
* 才能回收;两个条件分别记账。独立短应答不借用事务内存,不阻止本事务回收。
|
||
*/
|
||
static void Reap(IEC104_Command_t *self)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
if (!task->occupied || task->phase != IEC104_COMMAND_PHASE_ENDED || task->finish_pending ||
|
||
task->confirm_serial != 0U || task->term_serial != 0U || task->reply_serial != 0U) { return; }
|
||
(void)IEC104_Slave_Release_Station(self->slave, task->station);
|
||
memset(task, 0, sizeof(*task));
|
||
}
|
||
|
||
/*
|
||
* 功能:结束当前应用交互,冻结首次原因并按交付历史生成一次FINISHED。
|
||
* 参数:self为有效实例;reason为本次终结原因。
|
||
* 输出:活动事务进入ENDED;未读工作事件被替换或移除,累计相应终结统计。
|
||
* 返回:无;不会自行丢弃待发回复,末尾仅尝试Reap。
|
||
* 约束:是否欠FINISHED取决于ever_work_delivered,而非当前事件是否取走。
|
||
* 已经ENDED时保持原原因和通知,不因后续STOP/Cancel重复交付。
|
||
* 结束应用交互不等于释放槽,仍须由Reap同时核对FINISHED与本事务回复的收尾条件。
|
||
*/
|
||
static void Finish(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
if (!task->occupied || task->phase == IEC104_COMMAND_PHASE_ENDED) { Reap(self); return; }
|
||
task->phase = IEC104_COMMAND_PHASE_ENDED;
|
||
task->finish_reason = (uint8_t)reason;
|
||
task->event_type = IEC104_COMMAND_FINISHED;
|
||
task->event_taken = false;
|
||
task->finish_pending = task->ever_work_delivered;
|
||
task->return_waiting = false;
|
||
/* 创建事务时为最多两个工作事件和一个FINISHED检查了序号余量。 */
|
||
if (task->finish_pending) { task->event_serial = ++self->next_event; }
|
||
switch (reason) {
|
||
case IEC104_COMMAND_FINISH_COMPLETED: ++self->status.completed; break;
|
||
case IEC104_COMMAND_FINISH_EXECUTION_FAILED: ++self->status.execution_failed; break;
|
||
case IEC104_COMMAND_FINISH_ACTION_FAILED: ++self->status.action_failed; break;
|
||
case IEC104_COMMAND_FINISH_MISMATCH: ++self->status.mismatches; break;
|
||
case IEC104_COMMAND_FINISH_DEACTIVATED: ++self->status.deactivated; break;
|
||
case IEC104_COMMAND_FINISH_SELECT_TIMEOUT: ++self->status.select_timeouts; break;
|
||
case IEC104_COMMAND_FINISH_DECISION_TIMEOUT: ++self->status.decision_timeouts; break;
|
||
case IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT: ++self->status.execute_timeouts; break;
|
||
case IEC104_COMMAND_FINISH_CANCELLED:
|
||
case IEC104_COMMAND_FINISH_STOPPED:
|
||
case IEC104_COMMAND_FINISH_DISCONNECTED:
|
||
case IEC104_COMMAND_FINISH_CONNECTION_CHANGED:
|
||
case IEC104_COMMAND_FINISH_SEND_ERROR:
|
||
case IEC104_COMMAND_FINISH_SERVICE_ERROR: ++self->status.cancelled; break;
|
||
default: break;
|
||
}
|
||
Reap(self);
|
||
}
|
||
|
||
/*
|
||
* 功能:取消模块仍持有的全部回复,并终结尚未结束的事务。
|
||
* 参数:self为有效实例;reason交给Finish记录,仅首次终结生效。
|
||
* 输出:事务回复序号及独立回复池清空;欠应用的FINISHED仍保留。
|
||
* 返回:无;不修改发送层已经接手的帧,也不负责设置模块激活标志。
|
||
*/
|
||
static void Cancel_All(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
task->confirm_serial = task->term_serial = task->reply_serial = 0U;
|
||
task->send_waiting = false;
|
||
memset(self->replies, 0, sizeof(self->replies));
|
||
Finish(self, reason);
|
||
}
|
||
|
||
/*
|
||
* 功能:动作或发送之前核对Slave会话,并按失效原因清理模块状态。
|
||
* 参数:self为有效实例,其session来自已经消费的连接通知。
|
||
* 返回:READY可推进;WAIT_EVENTS须先读空通知;STALE表示旧事务不能继续;ERROR表示查询失败。
|
||
* 输出:刷新events_pending;失效/错误时取消未提交回复、终结事务并清active。
|
||
* 约束:WAIT_EVENTS优先且不取消事务。Handle_Event消费通知时不能调用本门禁,
|
||
* 否则队列还有后续通知时,当前已经出队的通知会被阻挡或遗漏。
|
||
*/
|
||
static IEC104_SlaveRequestState_t Check_Session(IEC104_Command_t *self)
|
||
{
|
||
IEC104_SlaveRequestState_t state = IEC104_Slave_Check_Request(self->slave, &self->session);
|
||
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
|
||
if (state == IEC104_SLAVE_REQUEST_ERROR) {
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
||
self->status.active = false;
|
||
} else if (state != IEC104_SLAVE_REQUEST_WAIT_EVENTS &&
|
||
(!self->status.active || state != IEC104_SLAVE_REQUEST_READY)) {
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
|
||
self->status.active = false;
|
||
state = IEC104_SLAVE_REQUEST_STALE;
|
||
}
|
||
return state;
|
||
}
|
||
|
||
/*
|
||
* 功能:根据当前事务阶段选择唯一生效的时长限制。
|
||
* 参数:self为有效实例。
|
||
* 返回:决定、选择或执行期限的毫秒值;无计时阶段、已报告结果、回调中或配置禁用时为0。
|
||
* 约束:只读,不判定到期;0不屏蔽Clock_Reversed单独检查的单调时钟异常。
|
||
*/
|
||
static uint32_t Timer_Limit(const IEC104_Command_t *self)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
if (!task->occupied || self->in_action) { return 0U; }
|
||
switch (task->phase) {
|
||
case IEC104_COMMAND_PHASE_SELECT_WAIT:
|
||
case IEC104_COMMAND_PHASE_EXECUTE_WAIT: return self->config.decision_timeout_ms;
|
||
case IEC104_COMMAND_PHASE_SELECTED: return self->config.select_timeout_ms;
|
||
case IEC104_COMMAND_PHASE_EXECUTING:
|
||
return task->result == 0U ? self->config.execute_timeout_ms : 0U;
|
||
default: return 0U;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 功能:计算当前阶段距离启用的期限还剩多少毫秒。
|
||
* 参数:task提供timer_start_ms;limit为非零期限;now是本次统一采样的单调时间。
|
||
* 返回:剩余毫秒;恰好到期、已经超期或时钟倒退均为0。
|
||
* 约束:先比较64位差值再截窄,不使用可能溢出的起点加期限计算。
|
||
*/
|
||
static uint32_t Remaining(const CommandTransaction *task, uint32_t limit, uint64_t now)
|
||
{
|
||
if (now < task->timer_start_ms || now - task->timer_start_ms >= limit) { return 0U; }
|
||
return limit - (uint32_t)(now - task->timer_start_ms);
|
||
}
|
||
|
||
/*
|
||
* 功能:检查仍等待决定、执行命令或设备结果的阶段是否遇到单调时间倒退。
|
||
* 参数:self为有效实例;now为本次单调时钟采样。
|
||
* 返回:等待阶段中now早于计时起点时为true;空闲、ENDED、已报告结果或回调中为false。
|
||
* 约束:期限0仅关闭经过时长限制,不允许等待状态依赖倒退的时钟继续执行。
|
||
*/
|
||
static bool Clock_Reversed(const IEC104_Command_t *self, uint64_t now)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
return !self->in_action && task->occupied && now < task->timer_start_ms &&
|
||
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
|
||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT ||
|
||
(task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->result == 0U));
|
||
}
|
||
|
||
/*
|
||
* 功能:分配模块内所有回复共用的递增FIFO序号。
|
||
* 参数:self为有效实例。
|
||
* 返回:成功为非零新序号;耗尽返回0,并以SERVICE_ERROR取消事务及回复、清active。
|
||
* 约束:0是未排队哨兵;计数不回绕,避免新回复与历史排序身份重合。
|
||
*/
|
||
static uint64_t Reply_Serial(IEC104_Command_t *self)
|
||
{
|
||
if (self->next_reply == UINT64_MAX) {
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
||
self->status.active = false;
|
||
return 0U;
|
||
}
|
||
return ++self->next_reply;
|
||
}
|
||
|
||
/*
|
||
* 功能:使用事务自己的存储生成一条确认,分配全局FIFO位置。
|
||
* 参数:self持有当前事务;cause为确认/错误COT;negative为P/N;try_send选择不排队Try路径。
|
||
* 输出:成功时写确认序号和策略,清confirm_submitted及send_waiting。
|
||
* 返回:true已准备确认;false为序号耗尽,Reply_Serial已执行服务错误清理。
|
||
* 约束:动作调用前必须准备必要确认资源,不能执行后再依赖独立回复池空位。
|
||
*/
|
||
static bool Set_Confirm(IEC104_Command_t *self, uint8_t cause, bool negative, bool try_send)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
uint64_t serial = Reply_Serial(self);
|
||
if (serial == 0U) { return false; }
|
||
task->confirm_serial = serial;
|
||
task->confirm_cot = cause;
|
||
task->confirm_negative = negative;
|
||
task->confirm_try = try_send;
|
||
task->confirm_submitted = false;
|
||
task->send_waiting = false;
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 功能:按统一采样时刻执行当前阶段的到期转换,不调用应用动作。
|
||
* 参数:self为有效实例;now同时用于回退检测和期限比较。
|
||
* 输出:决定到期生成否定确认;选择到期丢弃本模块尚持有的选择确认;
|
||
* 执行到期保留原肯定确认但不发终止;对应事务进入ENDED并按需产生FINISHED。
|
||
* 返回:无;未到期且时钟未回退时不改变状态,时钟回退单独计clock_errors。
|
||
* 期限为0时仍执行回退检测,不将失效的单调时钟视为无限期有效。
|
||
* 约束:动作/提交入口先通过会话门禁再调用;有效I帧按FIFO受理时也须在匹配前调用,
|
||
* 防止迟到执行先停掉选择计时、使已过期选择重新生效。
|
||
*/
|
||
static void Expire(IEC104_Command_t *self, uint64_t now)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
uint32_t limit = Timer_Limit(self);
|
||
bool reversed = Clock_Reversed(self, now);
|
||
if (!reversed && (limit == 0U || Remaining(task, limit, now) != 0U)) { return; }
|
||
if (reversed) { ++self->status.clock_errors; }
|
||
switch (task->phase) {
|
||
case IEC104_COMMAND_PHASE_SELECT_WAIT:
|
||
case IEC104_COMMAND_PHASE_EXECUTE_WAIT:
|
||
if (Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
|
||
Finish(self, IEC104_COMMAND_FINISH_DECISION_TIMEOUT);
|
||
}
|
||
break;
|
||
case IEC104_COMMAND_PHASE_SELECTED:
|
||
task->confirm_serial = 0U; /* 只能撤回模块尚持有的确认。 */
|
||
Finish(self, IEC104_COMMAND_FINISH_SELECT_TIMEOUT);
|
||
break;
|
||
case IEC104_COMMAND_PHASE_EXECUTING:
|
||
/* 动作已启动,不能因结果超时把原肯定确认改成否定。 */
|
||
Finish(self, IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT);
|
||
break;
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 功能:识别发送入口处理新网络输入后遇到的正常STOP,保留回复等待通知收尾。
|
||
* 参数:self为有效实例;identity是本次提交的原身份;submitted是发送入口返回值。
|
||
* 返回:仅ERROR且同一连接健康、已停用、仍有未读通知时为true;其他错误及FATAL均false。
|
||
* 输出:匹配正常STOP时置events_pending,不计发送背压次数,不直接改变事务阶段。
|
||
* 约束:FATAL可能已经写出字节,绝不能借此入口解释成可重试WAIT。
|
||
*/
|
||
static bool Defer_For_Stop(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
|
||
IEC104_TcpSubmitResult_t submitted)
|
||
{
|
||
IEC104_TcpServerStatus_t status;
|
||
if (submitted != IEC104_TCP_SUBMIT_ERROR || identity->slave != self->slave ||
|
||
identity->instance_id != self->session.instance_id || identity->generation != self->session.generation ||
|
||
IEC104_Slave_Get_Status(self->slave, &status, NULL) != IEC104_TCP_OK ||
|
||
status.generation != identity->generation || !status.connected || !status.connection_status_valid ||
|
||
!status.asdu_queue_status_valid || status.connection.phase != IEC104_TCP_SESSION_ACTIVE ||
|
||
status.connection.fault != IEC104_TCP_FAULT_NONE || status.connection.data_transfer_started ||
|
||
status.pending_event_count == 0U) { return false; }
|
||
self->status.events_pending = true;
|
||
return true;
|
||
}
|
||
|
||
/* 队首种类:NONE无待发;CONFIRM/TERM/REPLY对应事务内三份排序记录;SHORT对应独立回复池。
|
||
* 三种事务内回复和独立回复共享一个序号域,不另建可溢出的指针FIFO。 */
|
||
enum { COMMAND_HEAD_NONE, COMMAND_HEAD_CONFIRM, COMMAND_HEAD_TERM, COMMAND_HEAD_REPLY, COMMAND_HEAD_SHORT };
|
||
/*
|
||
* 功能:只读查找所有待提交回复中生成序号最小的一项。
|
||
* 参数:self为有效实例;slot为非NULL输出,仅队首为SHORT时写入独立槽下标。
|
||
* 返回:COMMAND_HEAD_*种类;没有待发回复时返回NONE。
|
||
* 约束:不弹出、不预留发送资源;序号0表示无回复,不能参与排序。
|
||
*/
|
||
static unsigned Reply_Head(const IEC104_Command_t *self, unsigned *slot)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
uint64_t first = 0U;
|
||
unsigned head = COMMAND_HEAD_NONE, i;
|
||
if (task->confirm_serial != 0U) { first = task->confirm_serial; head = COMMAND_HEAD_CONFIRM; }
|
||
if (task->term_serial != 0U && (first == 0U || task->term_serial < first)) {
|
||
first = task->term_serial; head = COMMAND_HEAD_TERM;
|
||
}
|
||
if (task->reply_serial != 0U && (first == 0U || task->reply_serial < first)) {
|
||
first = task->reply_serial; head = COMMAND_HEAD_REPLY;
|
||
}
|
||
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
|
||
if (self->replies[i].occupied && (first == 0U || self->replies[i].serial < first)) {
|
||
first = self->replies[i].serial; head = COMMAND_HEAD_SHORT; *slot = i;
|
||
}
|
||
}
|
||
return head;
|
||
}
|
||
|
||
/*
|
||
* 功能:在Complete关闭返回阶段后,检查协议收尾是否已经全部交接。
|
||
* 参数:self为有效实例。
|
||
* 输出:EXECUTING且completed、确认已交接、无确认/终止待发时,按设备结果产生
|
||
* FINISHED(COMPLETED/EXECUTION_FAILED);其余状态只尝试Reap。
|
||
* 返回:无;既不发送报文也不回调应用。
|
||
* 约束:无ACT_TERM策略仍须等待原确认交接;发送交接不表示主站ACK或设备成功。
|
||
*/
|
||
static void Complete_If_Sent(IEC104_Command_t *self)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
if (task->occupied && task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->completed &&
|
||
task->confirm_submitted && task->confirm_serial == 0U && task->term_serial == 0U) {
|
||
Finish(self, task->result == IEC104_COMMAND_RESULT_SUCCEEDED ?
|
||
IEC104_COMMAND_FINISH_COMPLETED : IEC104_COMMAND_FINISH_EXECUTION_FAILED);
|
||
}
|
||
Reap(self);
|
||
}
|
||
|
||
/*
|
||
* 功能:按全局FIFO向发送层交付至多一条回复。
|
||
* 参数:self为有效实例;内部先核对会话,再读取当前队首。
|
||
* 输出:OK后移除对应待交标记并计数,随后检查完成/释放;WAIT只保留原回复;
|
||
* 正常STOP先让出,其他发送错误终结事务并清回复池。
|
||
* 返回:1表示有一条回复已交接;0表示无回复、等待或已处理发送失败;
|
||
* -1表示服务查询/组包错误,调用方应报告本轮推进失败。
|
||
* 约束:局部10字节副本承载COT/P/N改写,原工作请求和撤销回显来源不会被修改。
|
||
*/
|
||
static int Submit_Head(IEC104_Command_t *self)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
const IEC104_SlaveEventIdentity_t *identity;
|
||
const uint8_t *source;
|
||
uint8_t bytes[10], cause;
|
||
IEC60870_Asdu_t asdu;
|
||
IEC104_TcpSubmitResult_t submitted;
|
||
IEC104_SlaveRequestState_t state = Check_Session(self);
|
||
unsigned slot = 0U, head;
|
||
bool negative, try_send;
|
||
if (state == IEC104_SLAVE_REQUEST_ERROR) { return -1; }
|
||
if (state != IEC104_SLAVE_REQUEST_READY) { return 0; }
|
||
head = Reply_Head(self, &slot);
|
||
if (head == COMMAND_HEAD_NONE) { return 0; }
|
||
identity = &task->identity;
|
||
source = task->request_bytes;
|
||
cause = IEC60870_ASDU_COT_ACTIVATION_TERMINATION;
|
||
negative = false;
|
||
try_send = true;
|
||
/* 默认分支为ACT_TERM;即使配置允许失败后终止,P/N=0也只表示流程结束。
|
||
* 普通确认可进入HIGH,执行后确认和终止必须走Try,防止越过既有过程信息。 */
|
||
if (head == COMMAND_HEAD_CONFIRM) {
|
||
cause = task->confirm_cot; negative = task->confirm_negative; try_send = task->confirm_try;
|
||
} else if (head == COMMAND_HEAD_REPLY) {
|
||
source = task->reply_bytes; cause = task->reply_cot;
|
||
negative = cause != IEC60870_ASDU_COT_DEACTIVATION_CON; try_send = false;
|
||
} else if (head == COMMAND_HEAD_SHORT) {
|
||
identity = &self->replies[slot].identity;
|
||
source = self->replies[slot].bytes; cause = self->replies[slot].cot;
|
||
negative = true; try_send = false;
|
||
}
|
||
memcpy(bytes, source, sizeof(bytes));
|
||
if (IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(self->slave),
|
||
bytes, sizeof(bytes)) != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Set_COT(&asdu, cause) != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Set_Negative(&asdu, negative) != IEC60870_ASDU_OK) {
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
||
return -1;
|
||
}
|
||
submitted = try_send ? IEC104_Slave_Try_Send_Response(self->slave, identity, &asdu) :
|
||
IEC104_Slave_Send_Response(self->slave, identity, &asdu);
|
||
if (submitted == IEC104_TCP_SUBMIT_OK) {
|
||
/* 发送层已复制/接手字节,立即撤销模块待发标记;不能等ACK再重复提交。 */
|
||
++self->status.submitted;
|
||
if (negative) { ++self->status.negative_submitted; }
|
||
if (head == COMMAND_HEAD_SHORT) { memset(&self->replies[slot], 0, sizeof(self->replies[slot])); }
|
||
else {
|
||
task->send_waiting = false;
|
||
if (head == COMMAND_HEAD_CONFIRM) { task->confirm_serial = 0U; task->confirm_submitted = true; }
|
||
else if (head == COMMAND_HEAD_TERM) { task->term_serial = 0U; task->term_submitted = true; }
|
||
else { task->reply_serial = 0U; }
|
||
}
|
||
Complete_If_Sent(self);
|
||
return 1;
|
||
}
|
||
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
|
||
/* WAIT未接手,不改变FIFO号;仅标记等待,使状态查询不会要求无网络推进的忙轮询。 */
|
||
if (head == COMMAND_HEAD_SHORT) { self->replies[slot].send_waiting = true; }
|
||
else { task->send_waiting = true; }
|
||
++self->status.waits;
|
||
return 0;
|
||
}
|
||
if (Defer_For_Stop(self, identity, submitted)) { return 0; }
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
|
||
self->status.active = false;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 功能:为校验失败、实例忙或不允许撤销的请求保存一条独立否定回复。
|
||
* 参数:self为有效实例;identity为原会话身份;data至少包含完整10字节命令;cause为回复COT。
|
||
* 输出:有空槽时复制原文、规范VSQ并分配FIFO号;池满计overloads/dropped_requests。
|
||
* 返回:true表示已处理(含丢新或发送等待);false表示序号/服务/组包内部错误。
|
||
* 约束:此前没有模块回复时才尝试当场提交;新短回复不能抢占事务必要确认存储,
|
||
* 也不能覆盖旧回复或因过载断开连接。
|
||
*/
|
||
static bool Short_Reply(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
|
||
const uint8_t *data, uint8_t cause)
|
||
{
|
||
unsigned slot;
|
||
bool empty = Pending_Replies(self) == 0U;
|
||
uint64_t serial;
|
||
for (slot = 0U; slot < IEC104_COMMAND_REPLY_CAPACITY; ++slot) {
|
||
if (!self->replies[slot].occupied) { break; }
|
||
}
|
||
if (slot == IEC104_COMMAND_REPLY_CAPACITY) {
|
||
++self->status.overloads;
|
||
++self->status.dropped_requests;
|
||
return true;
|
||
}
|
||
serial = Reply_Serial(self);
|
||
if (serial == 0U) { return false; }
|
||
self->replies[slot].identity = *identity;
|
||
memcpy(self->replies[slot].bytes, data, sizeof(self->replies[slot].bytes));
|
||
self->replies[slot].bytes[1] = 1U;
|
||
self->replies[slot].serial = serial;
|
||
self->replies[slot].cot = cause;
|
||
self->replies[slot].occupied = true;
|
||
self->replies[slot].send_waiting = false;
|
||
return !empty || Submit_Head(self) >= 0;
|
||
}
|
||
|
||
/*
|
||
* 功能:在已占用的事务槽中建立SELECT或EXECUTE工作事件。
|
||
* 参数:self已持有事务/站点;data为通过校验的命令原文;receive_serial为原I帧编号;
|
||
* now为受理时刻;selected说明本执行是否已经匹配本事务选择。
|
||
* 输出:替换工作原文,生成新事件号,重置当前交付/消费和确认标志,开始决定期限并计accepted。
|
||
* 返回:无;不调用应用、不提交确认。
|
||
* 约束:初始事务或匹配执行才可调用;保留事务身份、选择副作用和ever_work_delivered,
|
||
* 因此SELECT已交付而EXECUTE尚未交付时,取消仍须通知应用清理选择资源。
|
||
*/
|
||
static void Work_Event(IEC104_Command_t *self, const uint8_t *data, uint64_t receive_serial,
|
||
uint64_t now, bool selected)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
memcpy(task->request_bytes, data, sizeof(task->request_bytes));
|
||
task->request_bytes[1] = 1U;
|
||
task->receive_serial = receive_serial;
|
||
task->accepted_ms = task->timer_start_ms = now;
|
||
task->event_type = (data[9] & 0x80U) != 0U ? IEC104_COMMAND_SELECT : IEC104_COMMAND_EXECUTE;
|
||
task->phase = task->event_type == IEC104_COMMAND_SELECT ?
|
||
IEC104_COMMAND_PHASE_SELECT_WAIT : IEC104_COMMAND_PHASE_EXECUTE_WAIT;
|
||
task->event_serial = ++self->next_event;
|
||
task->accepted_event_serial = 0U;
|
||
if (task->event_type == IEC104_COMMAND_SELECT) { task->select_event_serial = task->event_serial; }
|
||
else { task->execute_event_serial = task->event_serial; }
|
||
task->event_taken = false;
|
||
task->selected = selected;
|
||
task->confirm_submitted = false;
|
||
task->send_waiting = false;
|
||
++self->status.accepted;
|
||
}
|
||
|
||
/*
|
||
* 功能:使用事务自带回复存储接受撤销或处理同点执行不匹配,然后结束应用交互。
|
||
* 参数:self持有待终结事务;data为本次撤销/不匹配请求;cause为9或7;reason为终结原因。
|
||
* 输出:丢弃模块尚持有的旧确认/终止,保存本次请求回显及新的FIFO号,生成必要FINISHED。
|
||
* 返回:true已处理(可仍等待发送);false为序号/服务/组包错误。
|
||
* 约束:不依赖独立回复池空闲;原工作请求不覆盖,已提交给发送层的旧确认无法撤回。
|
||
*/
|
||
static bool End_With_Reply(IEC104_Command_t *self, const uint8_t *data, uint8_t cause,
|
||
IEC104_CommandFinishReason_t reason)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
uint64_t serial = Reply_Serial(self);
|
||
if (serial == 0U) { return false; }
|
||
task->confirm_serial = task->term_serial = 0U;
|
||
memcpy(task->reply_bytes, data, sizeof(task->reply_bytes));
|
||
task->reply_bytes[1] = 1U;
|
||
task->reply_cot = cause;
|
||
task->reply_serial = serial;
|
||
task->send_waiting = false;
|
||
Finish(self, reason);
|
||
return Submit_Head(self) >= 0;
|
||
}
|
||
|
||
/*
|
||
* 功能:受理一个当前代次的I帧,完成遥控校验、撤销、选择执行匹配和新事务建立。
|
||
* 参数:self为有效实例;event是Slave交付且归属已核验的事件;identity为其原会话身份。
|
||
* 输出:有效命令生成工作事件或改变匹配事务;可回复错误生成否定;决定期间同点激活静默丢弃计数。
|
||
* 返回:IGNORED为非本模块类型或当前不能接手;HANDLED包括保存、拒绝和丢弃;
|
||
* ERROR为内部序号、引用或发送准备错误,调用方不能继续按未知类型兜底。
|
||
* 约束:先确认完整单对象,再按COT、CA、IOA、对象字段的优先级判错;
|
||
* 完整10字节可以规范回显,截短或多对象不能截取/补零伪造单命令。
|
||
*/
|
||
static IEC104_CommandEventResult_t Request(IEC104_Command_t *self, const IEC104_SlaveEvent_t *event,
|
||
const IEC104_SlaveEventIdentity_t *identity)
|
||
{
|
||
CommandTransaction *task = &self->transaction;
|
||
IEC60870_AsduInfo_t info;
|
||
IEC104_Station_t *station;
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
uint8_t cause = 0U, state;
|
||
uint32_t ioa;
|
||
uint64_t now;
|
||
bool same_point, is_select;
|
||
if (!self->status.active || event->asdu_result != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK || length == 0U ||
|
||
(data[0] != 45U && data[0] != 46U) ||
|
||
(self->config.type_mask & IEC104_COMMAND_TYPE_BIT(data[0])) == 0U ||
|
||
!IEC60870_Parse_ASDU_Header(event->asdu.parameters, data, length, &info)) {
|
||
return IEC104_COMMAND_EVENT_IGNORED;
|
||
}
|
||
now = IEC104_Net_Now_Ms();
|
||
/* 已经按FIFO消费到当前I帧;先转换期限,随后匹配不得重新激活已过期选择。 */
|
||
Expire(self, now);
|
||
if (info.object_count != 1U || info.information_objects_len < 4U) {
|
||
++self->status.rejected;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
ioa = Read_IOA(data);
|
||
station = IEC104_Slave_Get_Station(self->slave, info.common_address);
|
||
state = data[9] & 3U;
|
||
is_select = (data[9] & 0x80U) != 0U;
|
||
if (info.cause_of_transmission != IEC60870_ASDU_COT_ACTIVATION &&
|
||
info.cause_of_transmission != IEC60870_ASDU_COT_DEACTIVATION) {
|
||
cause = IEC60870_ASDU_COT_UNKNOWN_CAUSE;
|
||
} else if (station == NULL) { cause = IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS; }
|
||
else if (ioa == 0U) { cause = IEC60870_ASDU_COT_UNKNOWN_IOA; }
|
||
else if (info.is_sequence || length != 10U || info.is_negative_confirm || info.is_test ||
|
||
(data[0] == 45U ? (data[9] & 2U) != 0U : (state == 0U || state == 3U))) {
|
||
cause = info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION ?
|
||
IEC60870_ASDU_COT_DEACTIVATION_CON : IEC60870_ASDU_COT_ACTIVATION_CON;
|
||
}
|
||
if (cause != 0U) {
|
||
++self->status.rejected;
|
||
return Short_Reply(self, identity, data, cause) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
same_point = task->occupied && task->identity.generation == identity->generation &&
|
||
IEC104_Station_Get_Id(task->station) == IEC104_Station_Get_Id(station) &&
|
||
Read_IOA(task->request_bytes) == ioa;
|
||
/* 站点注册编号参与匹配,同CA删除再注册不能接管旧选择;撤销不要求状态/QU相同。 */
|
||
if (info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION) {
|
||
if (same_point && task->request_bytes[0] == data[0] && task->request_bytes[3] == data[3] &&
|
||
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
|
||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT)) {
|
||
return End_With_Reply(self, data, IEC60870_ASDU_COT_DEACTIVATION_CON,
|
||
IEC104_COMMAND_FINISH_DEACTIVATED) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
++self->status.rejected;
|
||
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_DEACTIVATION_CON) ?
|
||
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
if (same_point && (task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT ||
|
||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT)) {
|
||
/* 原请求尚待应用决定,先回忙否定可能被主站误认为原请求的结果。
|
||
* 只对通过上述校验的同会话/站点注册号/IOA激活静默丢弃;不要求命令字段相同。
|
||
* 不占应答槽,不覆盖原请求/事件,不刷新决定期限;撤销已在前面独立处理。 */
|
||
++self->status.dropped_requests;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
/* SELECTED阶段选择确认尚未交接时忙拒绝;交接后同点执行先匹配,其余新激活不影响选择。 */
|
||
if (same_point && !is_select && task->phase == IEC104_COMMAND_PHASE_SELECTED && task->confirm_submitted) {
|
||
if (task->request_bytes[0] != data[0] || task->request_bytes[3] != data[3] ||
|
||
(task->request_bytes[9] & 0x7FU) != (data[9] & 0x7FU)) {
|
||
++self->status.rejected;
|
||
return End_With_Reply(self, data, IEC60870_ASDU_COT_ACTIVATION_CON,
|
||
IEC104_COMMAND_FINISH_MISMATCH) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, true);
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
if (task->occupied || (!is_select && (self->config.select_required_mask & IEC104_COMMAND_TYPE_BIT(data[0])) != 0U)) {
|
||
/* ENDED但尚欠FINISHED/事务回复也算忙;其他点的新请求不能破坏当前选择。 */
|
||
++self->status.rejected;
|
||
if (task->occupied) { ++self->status.busy_rejected; }
|
||
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_ACTIVATION_CON) ?
|
||
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
if (self->next_transaction == UINT64_MAX || self->next_event > UINT64_MAX - 3U) {
|
||
/* 每事务最多SELECT、EXECUTE、FINISHED三次生成,开始前保留足够事件身份余量。 */
|
||
++self->status.overloads;
|
||
++self->status.dropped_requests;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
if (IEC104_Slave_Acquire_Station(self->slave, station) != IEC104_STATION_OK) { return IEC104_COMMAND_EVENT_ERROR; }
|
||
/* Acquire成功后才占事务槽;独立短回复中的数值CA不借用这个引用。 */
|
||
memset(task, 0, sizeof(*task));
|
||
task->occupied = true;
|
||
task->identity = *identity;
|
||
task->station = station;
|
||
task->transaction_id = ++self->next_transaction;
|
||
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, false);
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
|
||
/*
|
||
* 功能:填写第一版遥控默认配置,不创建任何运行对象。
|
||
* 参数:config为输出结构;NULL时无操作。
|
||
* 输出:启用45/46且默认均先选择,选择/决定/执行期限为30000/15000/60000毫秒,
|
||
* 成功发送ACT_TERM,失败默认不发送ACT_TERM。
|
||
* 返回:无;调用方可在Create之前修改配置。
|
||
*/
|
||
void IEC104_Command_Config_Default(IEC104_CommandConfig_t *config)
|
||
{
|
||
if (config == NULL) { return; }
|
||
memset(config, 0, sizeof(*config));
|
||
config->type_mask = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
|
||
config->select_required_mask = config->type_mask;
|
||
config->select_timeout_ms = 30000U;
|
||
config->decision_timeout_ms = 15000U;
|
||
config->execute_timeout_ms = 60000U;
|
||
config->send_act_term = true;
|
||
}
|
||
|
||
/*
|
||
* 功能:从静态池创建一个与Slave独占绑定的遥控实例。
|
||
* 参数:slave为有效服务句柄,寿命覆盖本实例;config为非NULL且按值复制的配置。
|
||
* 返回:成功为池内不透明句柄;类型位/期限配置非法、重复绑定、池满或实例号耗尽返回NULL。
|
||
* 输出:成功时初始化空事务、空回复池和零统计,不建立网络监听,也不调用应用代码。
|
||
* 约束:必须早于消费Slave通知创建;select_required_mask须为type_mask子集,
|
||
* 当前仅允许45/46位,后续修改调用方config不会影响实例。
|
||
*/
|
||
IEC104_Command_t *IEC104_Command_Create(IEC104_Slave_t *slave, const IEC104_CommandConfig_t *config)
|
||
{
|
||
const uint16_t supported = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
|
||
IEC104_Command_t *free_slot = NULL;
|
||
unsigned i;
|
||
if (config == NULL || config->type_mask == 0U || (config->type_mask & (uint16_t)~supported) != 0U ||
|
||
(config->select_required_mask & (uint16_t)~config->type_mask) != 0U || config->select_timeout_ms == 0U ||
|
||
next_instance == UINT64_MAX || IEC104_Slave_Get_Application_Layer_Parameters(slave) == NULL) { return NULL; }
|
||
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
|
||
if (command_pool[i].in_use && command_pool[i].slave == slave) { return NULL; }
|
||
if (!command_pool[i].in_use && free_slot == NULL) { free_slot = &command_pool[i]; }
|
||
}
|
||
if (free_slot == NULL) { return NULL; }
|
||
memset(free_slot, 0, sizeof(*free_slot));
|
||
free_slot->slave = slave;
|
||
free_slot->config = *config;
|
||
free_slot->serial = ++next_instance;
|
||
free_slot->in_use = true;
|
||
return free_slot;
|
||
}
|
||
|
||
/*
|
||
* 功能:归还空闲实例的静态池槽。
|
||
* 参数:self为待销毁句柄。
|
||
* 返回:true成功;无效句柄、动作回调中、事务仍占用或独立回复未清空时false。
|
||
* 输出:成功清零实例;不隐式Cancel、不读取FINISHED、不关闭Slave。
|
||
* 约束:先显式取消并完成事件/应答清理;成功后不得继续使用旧句柄。
|
||
*/
|
||
bool IEC104_Command_Destroy(IEC104_Command_t *self)
|
||
{
|
||
if (!Owned(self) || self->in_action || self->transaction.occupied || Pending_Replies(self) != 0U) { return false; }
|
||
memset(self, 0, sizeof(*self));
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 功能:本地取消事务和模块尚未交接的回复,保留必要的应用清理通知。
|
||
* 参数:self为实例句柄;无效句柄或动作回调中调用时无操作。
|
||
* 输出:活动事务以CANCELLED结束,所有独立回复丢弃;会话激活状态保持,后续仍可接新请求。
|
||
* 返回:无;不撤回发送层已接手的字节,不回滚输出;已ENDED事务不改首次终结原因。
|
||
*/
|
||
void IEC104_Command_Cancel(IEC104_Command_t *self)
|
||
{
|
||
if (Owned(self) && !self->in_action) { Cancel_All(self, IEC104_COMMAND_FINISH_CANCELLED); }
|
||
}
|
||
|
||
/*
|
||
* 功能:按Slave FIFO顺序分派当前通知或I帧,不调用应用动作。
|
||
* 参数:self为实例;event须来自所属Slave的NextEvent或合法Copy_Event副本。
|
||
* 返回:HANDLED包括会话通知和已识别遥控请求;IGNORED为旧代次/未接手事件;
|
||
* ERROR为归属、实例、参数或重入错误,不可继续生成未知类型兜底回复。
|
||
* 输出:连接/启停通知更新会话并按原因取消旧事务;I帧交Request处理。
|
||
* 约束:状态通知先处理,不能统一套Check_Request;后续未出队通知不应阻挡当前通知消费。
|
||
*/
|
||
IEC104_CommandEventResult_t IEC104_Command_Handle_Event(IEC104_Command_t *self,
|
||
const IEC104_SlaveEvent_t *event)
|
||
{
|
||
IEC104_SlaveEventIdentity_t identity;
|
||
if (!Owned(self) || self->in_action ||
|
||
!IEC104_Slave_Get_Event_Identity(self->slave, event, &identity) ||
|
||
(self->session.instance_id != 0U && self->session.instance_id != identity.instance_id)) {
|
||
return IEC104_COMMAND_EVENT_ERROR;
|
||
}
|
||
self->session.slave = identity.slave;
|
||
self->session.instance_id = identity.instance_id;
|
||
if (event->transport.type == IEC104_TCP_EVENT_CONNECTED) {
|
||
/* 最高代次跨断线保留,历史CONNECTED副本不能重新绑定已经退役的连接。 */
|
||
if (identity.generation <= self->last_generation) { return IEC104_COMMAND_EVENT_IGNORED; }
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
|
||
self->session.generation = self->last_generation = identity.generation;
|
||
self->status.active = false;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
}
|
||
if (identity.generation != self->session.generation || identity.generation == 0U) {
|
||
return IEC104_COMMAND_EVENT_IGNORED;
|
||
}
|
||
switch (event->transport.type) {
|
||
case IEC104_TCP_EVENT_ACTIVATED:
|
||
self->status.active = true;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
case IEC104_TCP_EVENT_DEACTIVATED:
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_STOPPED);
|
||
self->status.active = false;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
case IEC104_TCP_EVENT_DISCONNECTED:
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_DISCONNECTED);
|
||
self->session.generation = 0U;
|
||
self->status.active = false;
|
||
return IEC104_COMMAND_EVENT_HANDLED;
|
||
case IEC104_TCP_EVENT_I_FRAME: return Request(self, event, &identity);
|
||
default: return IEC104_COMMAND_EVENT_IGNORED;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 功能:有界推进会话检查、期限转换及全局FIFO队首发送。
|
||
* 参数:self为实例句柄。
|
||
* 返回:true表示本轮已处理,可仍等待发送/应用或刚刚取消;false为参数、重入、服务或组包错误。
|
||
* 输出:每轮至多推进2*(1+独立回复容量)步;队首WAIT或有待消费通知时立即让出。
|
||
* 约束:不调用应用;每轮网络推进并分派通知后仍须调用,不能只在need_process为true时调用。
|
||
*/
|
||
bool IEC104_Command_Process(IEC104_Command_t *self)
|
||
{
|
||
unsigned step;
|
||
if (!Owned(self) || self->in_action) { return false; }
|
||
for (step = 0U; step < 2U * (1U + IEC104_COMMAND_REPLY_CAPACITY); ++step) {
|
||
IEC104_SlaveRequestState_t state = Check_Session(self);
|
||
int submitted;
|
||
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
|
||
if (state != IEC104_SLAVE_REQUEST_READY) { return true; }
|
||
Expire(self, IEC104_Net_Now_Ms());
|
||
Complete_If_Sent(self);
|
||
submitted = Submit_Head(self);
|
||
if (submitted <= 0) { return submitted == 0; }
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 功能:交付当前最早且可读取的工作/终结事件。
|
||
* 参数:self为实例;event为非NULL输出结构,只有READY时改写。
|
||
* 返回:READY交付一个纯值事件;EMPTY为无事件或需先消费Slave通知;ERROR为参数/重入/服务错误。
|
||
* 输出:工作事件交付记录当前和历史交付标志;FINISHED交付清通知义务并尝试释放事务槽。
|
||
* 约束:已有FINISHED不受离线门禁阻挡;工作事件先检查会话与期限,过期事件不会交给动作。
|
||
* 返回FINISHED时模块引用可能已释放,应用跨循环使用站点必须提前取得自己的引用。
|
||
*/
|
||
IEC104_CommandNextResult_t IEC104_Command_NextEvent(IEC104_Command_t *self, IEC104_CommandEvent_t *event)
|
||
{
|
||
CommandTransaction *task;
|
||
IEC104_CommandEvent_t result;
|
||
IEC104_SlaveRequestState_t state = IEC104_SLAVE_REQUEST_READY;
|
||
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_NEXT_ERROR; }
|
||
task = &self->transaction;
|
||
/* 已存在的FINISHED不受离线或未读通知门禁阻挡;它只清理已结束的协议交互。 */
|
||
if (!task->finish_pending) {
|
||
state = Check_Session(self);
|
||
if (state == IEC104_SLAVE_REQUEST_READY) { Expire(self, IEC104_Net_Now_Ms()); }
|
||
}
|
||
if (!task->finish_pending && state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_NEXT_ERROR; }
|
||
if (!task->occupied || (!task->finish_pending &&
|
||
(state != IEC104_SLAVE_REQUEST_READY || task->event_taken ||
|
||
(task->phase != IEC104_COMMAND_PHASE_SELECT_WAIT && task->phase != IEC104_COMMAND_PHASE_EXECUTE_WAIT)))) {
|
||
return IEC104_COMMAND_NEXT_EMPTY;
|
||
}
|
||
memset(&result, 0, sizeof(result));
|
||
result.type = (IEC104_CommandEventType_t)task->event_type;
|
||
result.instance_id = self->serial;
|
||
result.transaction_id = task->transaction_id;
|
||
result.event_serial = task->event_serial;
|
||
Copy_Request(task, &result.request);
|
||
if (task->finish_pending) {
|
||
result.reason = (IEC104_CommandFinishReason_t)task->finish_reason;
|
||
result.select_effect = task->select_effect;
|
||
result.execute_effect = task->execute_effect;
|
||
task->finish_pending = false;
|
||
} else {
|
||
/* 分开记录当前事件和事务历史:生成后续EXECUTE不能抹掉已交付SELECT的清理义务。 */
|
||
task->ever_work_delivered = true;
|
||
if (result.type == IEC104_COMMAND_SELECT) { task->select_delivered = true; }
|
||
else { task->execute_delivered = true; }
|
||
}
|
||
task->event_taken = true;
|
||
*event = result;
|
||
Reap(self);
|
||
return IEC104_COMMAND_NEXT_READY;
|
||
}
|
||
|
||
/*
|
||
* 功能:检查事件是否为当前事务当前阶段已经交付的那一个事件。
|
||
* 参数:self为有效实例;event可为NULL,其他情况下仅读取身份和类型。
|
||
* 返回:实例号、事务号、当前事件号/种类和已交付标志全部匹配时true。
|
||
* 约束:只读,不检查会话/期限;Slave身份不能代替这里的应用事务与工作事件身份。
|
||
*/
|
||
static bool Current_Event(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
return event != NULL && task->occupied && event->instance_id == self->serial &&
|
||
event->transaction_id == task->transaction_id && event->event_serial == task->event_serial &&
|
||
event->type == task->event_type && task->event_taken;
|
||
}
|
||
|
||
/*
|
||
* 功能:核对应用事件是否允许用于本类应答接口。
|
||
* 参数:self为有效实例;event为待应答事件;work=true要求已由NextEvent取走、
|
||
* 尚未由Accept/Reject消费的SELECT/EXECUTE,
|
||
* work=false要求已Accept且动作启动成功、仍处于EXECUTING的原EXECUTE令牌。
|
||
* 返回:身份、交付/消费状态及阶段相符时true,否则false;不改变状态。
|
||
* 约束:执行令牌可供报告结果、逐帧返回信息和Complete使用,各接口再检查自己的一次性条件。
|
||
*/
|
||
static bool Reply_Stage(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event, bool work)
|
||
{
|
||
const CommandTransaction *task = &self->transaction;
|
||
if (!Current_Event(self, event)) { return false; }
|
||
if (work) {
|
||
return task->accepted_event_serial == 0U &&
|
||
((event->type == IEC104_COMMAND_SELECT && task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT) ||
|
||
(event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT));
|
||
}
|
||
return event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTING &&
|
||
task->accepted_event_serial == event->event_serial && task->execute_effect;
|
||
}
|
||
|
||
/*
|
||
* 功能:统一执行应用应答入口的参数、事件、会话及期限检查。
|
||
* 参数:self/event为调用方传回的实例和纯值事件;work选择工作决定或已启动执行的校验模式。
|
||
* 返回:OK可继续本次应答;WAIT须先消费Slave通知且本次未消费事件;STALE为身份/阶段失效;
|
||
* ERROR为参数、重入或服务查询错误。
|
||
* 输出:仅会话失效或期限到期时进行统一收尾;成功检查本身不消费事件、不调用动作。
|
||
* 约束:先查会话再取一次now检查期限,之后再次校验阶段,避免到期转换后仍使用旧事件。
|
||
*/
|
||
static IEC104_CommandReplyResult_t Check_Reply(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, bool work)
|
||
{
|
||
IEC104_SlaveRequestState_t state;
|
||
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
if (!Reply_Stage(self, event, work)) { return IEC104_COMMAND_REPLY_STALE; }
|
||
state = Check_Session(self);
|
||
if (state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
if (state == IEC104_SLAVE_REQUEST_WAIT_EVENTS) { return IEC104_COMMAND_REPLY_WAIT; }
|
||
if (state != IEC104_SLAVE_REQUEST_READY) { return IEC104_COMMAND_REPLY_STALE; }
|
||
Expire(self, IEC104_Net_Now_Ms());
|
||
return Reply_Stage(self, event, work) ? IEC104_COMMAND_REPLY_OK : IEC104_COMMAND_REPLY_STALE;
|
||
}
|
||
|
||
/*
|
||
* 功能:接受已交付的SELECT/EXECUTE,在事件有效时最多调用一次同步应用动作。
|
||
* 参数:self/event为实例及原工作事件;action/context仅在本次调用借用,不保存。
|
||
* SELECT允许action=NULL,表示逻辑选择生效而无硬件副作用;EXECUTE必须提供action。
|
||
* 返回:OK仅表示工作交互已消费,动作可能NOT_STARTED/FAILED,确认也可能仍待发送;
|
||
* WAIT未调用动作、未消费事件;STALE不得执行;ERROR为参数/重入/服务或资源错误。
|
||
* 输出:准备事务自带确认资源后先消费事件,再调用动作;按结果进入选择保持、执行等待结果或ENDED。
|
||
* 约束:回调期间只允许只读查询;返回后重新采样阶段计时起点,不把回调耗时追认为决定超时。
|
||
*/
|
||
IEC104_CommandReplyResult_t IEC104_Command_Accept(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, IEC104_CommandAction_t action, void *context)
|
||
{
|
||
CommandTransaction *task;
|
||
IEC104_CommandRequest_t request;
|
||
IEC104_CommandActionResult_t action_result;
|
||
IEC104_CommandReplyResult_t checked;
|
||
bool selecting;
|
||
if (event != NULL && event->type == IEC104_COMMAND_EXECUTE && action == NULL) {
|
||
return IEC104_COMMAND_REPLY_ERROR;
|
||
}
|
||
checked = Check_Reply(self, event, true);
|
||
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
||
task = &self->transaction;
|
||
selecting = task->event_type == IEC104_COMMAND_SELECT;
|
||
/* 确认资源和序号在动作前准备。事务自己的回复不会被独立短回复洪泛挤占。 */
|
||
if (!Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, false, !selecting)) {
|
||
return IEC104_COMMAND_REPLY_ERROR;
|
||
}
|
||
Copy_Request(task, &request);
|
||
/* 先记录已消费的事件身份,回调即使经应用代码再次进入也不能重做同一动作。 */
|
||
task->accepted_event_serial = task->event_serial;
|
||
self->in_action = true;
|
||
action_result = action == NULL ? IEC104_COMMAND_ACTION_STARTED : action(context, &request);
|
||
self->in_action = false;
|
||
/* 非法动作返回值按可能有副作用的失败处理;已消费的动作绝不重试。 */
|
||
if (action_result != IEC104_COMMAND_ACTION_STARTED && action_result != IEC104_COMMAND_ACTION_NOT_STARTED) {
|
||
action_result = IEC104_COMMAND_ACTION_FAILED;
|
||
}
|
||
if (selecting) { task->select_effect = action != NULL && action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
|
||
else { task->execute_effect = action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
|
||
if (action_result == IEC104_COMMAND_ACTION_STARTED) {
|
||
task->phase = selecting ? IEC104_COMMAND_PHASE_SELECTED : IEC104_COMMAND_PHASE_EXECUTING;
|
||
task->timer_start_ms = IEC104_Net_Now_Ms();
|
||
if (selecting) { ++self->status.selected; }
|
||
else { ++self->status.executed; }
|
||
} else {
|
||
task->confirm_negative = true;
|
||
task->confirm_try = !selecting && action_result == IEC104_COMMAND_ACTION_FAILED;
|
||
if (action_result == IEC104_COMMAND_ACTION_NOT_STARTED) {
|
||
++self->status.app_rejected;
|
||
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
|
||
} else { Finish(self, IEC104_COMMAND_FINISH_ACTION_FAILED); }
|
||
}
|
||
/* 提交可能接收STOP或产生故障;OK仍只描述动作交互已消费,应用继续读取FINISHED。 */
|
||
(void)Submit_Head(self);
|
||
return IEC104_COMMAND_REPLY_OK;
|
||
}
|
||
|
||
/*
|
||
* 功能:拒绝已交付且尚未消费的选择/执行工作事件。
|
||
* 参数:self/event为实例和原事件;cause只接受否定ACT_CON或未知IOA两种原因。
|
||
* 返回:OK交互已消费、否定回复曾安排并尝试提交;WAIT未消费;STALE为旧事件;ERROR为非法参数或内部错误。
|
||
* 输出:以当前工作请求回显7/47,计app_rejected并产生FINISHED(REJECTED)。
|
||
* 约束:仅改变协议状态,不调用设备动作;回复使用事务自己的存储,不能因独立池满丢失。
|
||
* OK不保证回复仍在队列或已经发出:本次发送若失败,待交回复可能已被取消,须继续读取FINISHED。
|
||
*/
|
||
IEC104_CommandReplyResult_t IEC104_Command_Reject(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, IEC104_CommandRejectCause_t cause)
|
||
{
|
||
IEC104_CommandReplyResult_t checked;
|
||
if (cause != IEC104_COMMAND_REJECT_NEGATIVE && cause != IEC104_COMMAND_REJECT_UNKNOWN_IOA) {
|
||
return IEC104_COMMAND_REPLY_ERROR;
|
||
}
|
||
checked = Check_Reply(self, event, true);
|
||
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
||
if (!Set_Confirm(self, cause == IEC104_COMMAND_REJECT_UNKNOWN_IOA ?
|
||
IEC60870_ASDU_COT_UNKNOWN_IOA : IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
|
||
return IEC104_COMMAND_REPLY_ERROR;
|
||
}
|
||
self->transaction.accepted_event_serial = self->transaction.event_serial;
|
||
++self->status.app_rejected;
|
||
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
|
||
(void)Submit_Head(self);
|
||
return IEC104_COMMAND_REPLY_OK;
|
||
}
|
||
|
||
/*
|
||
* 功能:一次性记录已启动设备动作的最终成功/失败结果,停止执行期限。
|
||
* 参数:self/event为实例及启动成功的原EXECUTE事件;result为SUCCEEDED或FAILED。
|
||
* 返回:OK已记录;WAIT结果未记录、先消费通知;STALE表示事务或事件已失效;
|
||
* ERROR为结果值非法、重复报告、已经Complete或参数/服务错误。
|
||
* 输出:写入事务result;不发送确认/终止、不关闭返回信息阶段、不调用应用。
|
||
* 约束:允许早于ACT_CON交接,设备执行时长和发送拥塞等待必须分开记账。
|
||
*/
|
||
IEC104_CommandReplyResult_t IEC104_Command_Report_Result(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, IEC104_CommandResult_t result)
|
||
{
|
||
IEC104_CommandReplyResult_t checked;
|
||
if (result != IEC104_COMMAND_RESULT_SUCCEEDED && result != IEC104_COMMAND_RESULT_FAILED) {
|
||
return IEC104_COMMAND_REPLY_ERROR;
|
||
}
|
||
checked = Check_Reply(self, event, false);
|
||
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
||
if (self->transaction.result != 0U || self->transaction.completed) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
self->transaction.result = (uint8_t)result;
|
||
return IEC104_COMMAND_REPLY_OK;
|
||
}
|
||
|
||
/*
|
||
* 功能:沿原事务连接直接提交一帧命令返回信息,并保持模块与传输层既有帧序。
|
||
* 参数:self/event为实例和启动成功的EXECUTE令牌;asdu为调用方持有的完整监视ASDU。
|
||
* 返回:OK发送层已接手,不能重交;WAIT未接手且不缓存,应用保留或重建后重试;
|
||
* STALE为事务结束/发送故障已取消;ERROR为ASDU、阶段或参数错误。
|
||
* 正常STOP且通知尚未读取的发送ERROR先返回WAIT,保留由会话通知收尾的机会。
|
||
* 输出:完整验证104布局、对象、类型、COT、CA及标志;OK后累计发送和返回信息计数。
|
||
* 约束:只能在肯定确认交接之后、Complete之前;模块内有任何更早回复时也必须WAIT。
|
||
* 返回信息不进入NORMAL、没有跨重连缓存;WAIT不取得模块内持久FIFO位置。
|
||
*/
|
||
IEC104_CommandReplyResult_t IEC104_Command_Submit_Return_Info(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, const IEC60870_Asdu_t *asdu)
|
||
{
|
||
CommandTransaction *task;
|
||
IEC60870_AsduInfo_t info;
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
IEC104_TcpSubmitResult_t submitted;
|
||
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
|
||
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
||
task = &self->transaction;
|
||
if (task->completed || asdu == NULL || !IEC60870_AppLayerParameters_Valid(asdu->parameters) ||
|
||
asdu->parameters->sizeOfCOT != 2 || asdu->parameters->sizeOfCA != 2 || asdu->parameters->sizeOfIOA != 3 ||
|
||
asdu->parameters->maxSizeOfASDU > IEC104_MAX_ASDU_LEN ||
|
||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK || length > IEC104_MAX_ASDU_LEN ||
|
||
IEC60870_Asdu_Parse(IEC104_Slave_Get_Application_Layer_Parameters(self->slave), data, length, &info) !=
|
||
IEC60870_ASDU_OK ||
|
||
!((info.type_id >= 1U && info.type_id <= 21U) || (info.type_id >= 30U && info.type_id <= 40U)) ||
|
||
info.object_count == 0U ||
|
||
(info.cause_of_transmission != IEC60870_ASDU_COT_RETURN_INFO_REMOTE &&
|
||
info.cause_of_transmission != IEC60870_ASDU_COT_SPONTANEOUS) ||
|
||
info.is_negative_confirm || info.is_test != ((task->request_bytes[2] >> 7) & 1U) ||
|
||
info.common_address != Read_CA(task->request_bytes)) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
/* 底层Try只知道传输队列;这里还必须检查模块内更早的独立/事务应答。 */
|
||
if (!task->confirm_submitted || Pending_Replies(self) != 0U) { return IEC104_COMMAND_REPLY_WAIT; }
|
||
submitted = IEC104_Slave_Try_Send_Response(self->slave, &task->identity, asdu);
|
||
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
|
||
task->return_waiting = true;
|
||
++self->status.waits;
|
||
return IEC104_COMMAND_REPLY_WAIT;
|
||
}
|
||
if (submitted == IEC104_TCP_SUBMIT_OK) {
|
||
task->return_waiting = false;
|
||
++self->status.submitted;
|
||
++self->status.return_info_submitted;
|
||
return IEC104_COMMAND_REPLY_OK;
|
||
}
|
||
if (Defer_For_Stop(self, &task->identity, submitted)) { return IEC104_COMMAND_REPLY_WAIT; }
|
||
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
|
||
self->status.active = false;
|
||
return IEC104_COMMAND_REPLY_STALE;
|
||
}
|
||
|
||
/*
|
||
* 功能:声明本事务不再提交返回信息,并按配置启动协议终止收尾。
|
||
* 参数:self/event为实例及启动成功的原EXECUTE事件;设备结果必须已经Report_Result。
|
||
* 返回:OK已关闭返回阶段,后续ACT_TERM由Process推进;WAIT未消费,先处理通知;
|
||
* STALE为事务失效;ERROR为未报告结果、重复调用或参数/内部错误。
|
||
* 输出:必要时分配终止FIFO序号,置completed;无终止且确认已交接时可立即产生FINISHED。
|
||
* 约束:OK不保证ACT_TERM已经发送;不能因等待终止而再次Complete或再提交返回信息。
|
||
*/
|
||
IEC104_CommandReplyResult_t IEC104_Command_Complete(IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event)
|
||
{
|
||
CommandTransaction *task;
|
||
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
|
||
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
||
task = &self->transaction;
|
||
if (task->result == 0U || task->completed) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
if (self->config.send_act_term &&
|
||
(task->result == IEC104_COMMAND_RESULT_SUCCEEDED || self->config.act_term_on_failure)) {
|
||
uint64_t serial = Reply_Serial(self);
|
||
if (serial == 0U) { return IEC104_COMMAND_REPLY_ERROR; }
|
||
task->term_serial = serial;
|
||
}
|
||
task->completed = true;
|
||
task->return_waiting = false;
|
||
/* 无TERM且确认已交接可立即结束;否则Process只重试协议收尾,不要求应用重复Complete。 */
|
||
Complete_If_Sent(self);
|
||
return IEC104_COMMAND_REPLY_OK;
|
||
}
|
||
|
||
/*
|
||
* 功能:通过该事务任一已经交付的事件,只读查询仍保留的事务状态。
|
||
* 参数:self为实例;event为本事务历史SELECT/EXECUTE或已取FINISHED;info为非NULL输出。
|
||
* 返回:true成功;句柄、身份、交付状态非法或槽已回收时false,失败保持info原值。
|
||
* 输出:当前阶段、是否尚未ENDED、确认/结果/Complete/终止状态;不推进会话或期限。
|
||
* 约束:可在动作回调中查询;active只表示本地事务阶段,不保证当前连接仍允许操作。
|
||
* 不保存历史事务,槽回收后旧事件不能再查询,即使其字节副本仍由应用持有。
|
||
*/
|
||
bool IEC104_Command_Get_Transaction(const IEC104_Command_t *self,
|
||
const IEC104_CommandEvent_t *event, IEC104_CommandTransactionInfo_t *info)
|
||
{
|
||
const CommandTransaction *task;
|
||
IEC104_CommandTransactionInfo_t result;
|
||
bool delivered;
|
||
if (!Owned(self) || event == NULL || info == NULL) { return false; }
|
||
task = &self->transaction;
|
||
if (!task->occupied || event->instance_id != self->serial || event->transaction_id != task->transaction_id) {
|
||
return false;
|
||
}
|
||
delivered = (event->type == IEC104_COMMAND_SELECT && task->select_delivered &&
|
||
event->event_serial == task->select_event_serial) ||
|
||
(event->type == IEC104_COMMAND_EXECUTE && task->execute_delivered &&
|
||
event->event_serial == task->execute_event_serial) ||
|
||
(event->type == IEC104_COMMAND_FINISHED && task->event_type == IEC104_COMMAND_FINISHED &&
|
||
task->event_taken && event->event_serial == task->event_serial);
|
||
if (!delivered) { return false; }
|
||
memset(&result, 0, sizeof(result));
|
||
result.active = task->phase != IEC104_COMMAND_PHASE_ENDED;
|
||
result.phase = (IEC104_CommandPhase_t)task->phase;
|
||
result.confirm_submitted = task->confirm_submitted;
|
||
result.result_reported = task->result != 0U;
|
||
result.completed = task->completed;
|
||
result.term_submitted = task->term_submitted;
|
||
*info = result;
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 功能:生成实例累计统计和当前调度条件的只读快照。
|
||
* 参数:self为实例;status为非NULL输出,可在动作回调内使用。
|
||
* 返回:true成功;无效参数或Slave查询失败时false,失败不改输出。
|
||
* 输出:在累计统计副本上即时计算会话通知、事务占用、待交回复/事件及期限剩余量。
|
||
* 约束:不触发到期转换、不取消事务、不预留发送容量;发送WAIT不要求持续空转,
|
||
* 但每轮网络推进后仍须调用Process重试。已有FINISHED由NextEvent读取,不靠Process清理。
|
||
*/
|
||
bool IEC104_Command_Get_Status(const IEC104_Command_t *self, IEC104_CommandStatus_t *status)
|
||
{
|
||
const CommandTransaction *task;
|
||
IEC104_CommandStatus_t result;
|
||
IEC104_SlaveRequestState_t state;
|
||
uint32_t limit;
|
||
uint64_t now;
|
||
unsigned slot = 0U, head;
|
||
bool waiting;
|
||
if (!Owned(self) || status == NULL) { return false; }
|
||
state = IEC104_Slave_Check_Request(self->slave, &self->session);
|
||
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
|
||
task = &self->transaction;
|
||
result = self->status;
|
||
result.generation = self->session.generation;
|
||
result.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
|
||
result.busy = task->occupied;
|
||
result.pending_replies = Pending_Replies(self);
|
||
result.pending_events = task->occupied && (task->finish_pending ||
|
||
(!task->event_taken && (task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT ||
|
||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT))) ? 1U : 0U;
|
||
limit = Timer_Limit(self);
|
||
now = IEC104_Net_Now_Ms();
|
||
result.timer_active = limit != 0U;
|
||
result.timeout_remaining_ms = limit != 0U ? Remaining(task, limit, now) : 0U;
|
||
result.need_process = false;
|
||
head = Reply_Head(self, &slot);
|
||
waiting = head == COMMAND_HEAD_SHORT ? self->replies[slot].send_waiting : task->send_waiting;
|
||
if (!self->in_action && !result.events_pending) {
|
||
/* 有通知时先交回Slave;离线仅有FINISHED时无需反复推进,让应用直接读清理事件。 */
|
||
if (state != IEC104_SLAVE_REQUEST_READY || !result.active) {
|
||
result.need_process = result.pending_replies != 0U ||
|
||
(task->occupied && task->phase != IEC104_COMMAND_PHASE_ENDED);
|
||
} else {
|
||
/* 到期/回退或尚未尝试的队首需要立即推进;已WAIT的队首等待网络进展再重试。 */
|
||
result.need_process = Clock_Reversed(self, now) ||
|
||
(result.timer_active && result.timeout_remaining_ms == 0U) ||
|
||
(head != COMMAND_HEAD_NONE && !waiting);
|
||
}
|
||
}
|
||
*status = result;
|
||
return true;
|
||
}
|