Files
STM32F4-Base/IEC60870-Slave/104/business/IEC104_ClockSynchronization.c
T
STF 7596f3371a
CI / L0 静态门禁 (pre-commit) (push) Failing after 44s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
补充总召唤的广播地址,电度召唤+时钟同步业务的实现
2026-10-29 03:20:19 -07:00

560 lines
29 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.
/*
* IEC104时钟同步业务(Type 103,C_CS_NA_1)。
* 用途:将主站给出的时间交给应用校准设备时钟,使故障事件、SOE和日志采用一致时间。
* 本模块只处理协议及时间换算,不直接写系统时钟或RTC;实际设时由应用提供的action完成。
*
* 推进顺序:Handle_Event校验并保存 -> Execute_Next调用一次action -> Process续交应答。
* 校验失败直接进入待应答;校验成功先等应用执行。两类任务共用固定槽,回复严格按受理顺序。
* 时间计算使用Net单调毫秒,不能用正在被校准的日历时钟计算期限和站内等待时间。
* 所有接口由同一事件循环串行调用;STOP/断线取消未交接任务,不回滚已完成的设时。
*/
#include "IEC104_ClockSynchronization.h"
#include "IEC104_TcpServer_Config.h"
#include "IEC104_Net.h"
#include "IEC60870_Codec.h"
#include <string.h>
#ifndef IEC104_CLOCK_SYNCHRONIZATION_CAPACITY
#define IEC104_CLOCK_SYNCHRONIZATION_CAPACITY IEC104_TCP_SERVER_CAPACITY
#endif
#ifndef IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY
#define IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY 4U
#endif
_Static_assert(IEC104_CLOCK_SYNCHRONIZATION_CAPACITY > 0U, "Clock needs instance storage");
_Static_assert(IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY > 0U &&
IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY <= UINT32_MAX / 2U, "Clock needs bounded task storage");
/* EXECUTE等待应用显式设时;REPLY等待提交确认,发送WAIT不会退回EXECUTE。 */
enum { CLOCK_EXECUTE = 1, CLOCK_REPLY = 2 };
typedef struct {
IEC104_SlaveEventIdentity_t identity; /* 原Slave实例及连接代次,防止旧确认发到新连接。 */
uint8_t bytes[16]; /* 6字节ASDU头+3字节IOA+7字节CP56;仅规范VSQ及改写应答头。 */
IEC104_StationSnapshot_t stations; /* 广播受理时固定目标集合;含自引用,禁止移动槽。 */
IEC104_Station_t *station; /* 定向Acquire;广播由活动快照保护当前节点。 */
uint64_t receive_serial, accepted_ms; /* 原I帧编号;Handle_Event受理时的单调毫秒。 */
uint16_t response_ca; /* 定向原CA;广播当前应答站的CA,WAIT期间保持目标不变。 */
uint8_t phase, cause; /* EXECUTE/REPLY;待回传的COT,0表示尚未生成应答。 */
bool negative, executed, send_waiting, occupied; /* P/N、动作已消费、上次提交WAIT、槽已占用。 */
} ClockTask;
struct IEC104_ClockSynchronization {
IEC104_Slave_t *slave; /* 借用服务,必须先销毁本模块再销毁Slave。 */
IEC104_SlaveEventIdentity_t session; /* 已消费通知所对应的服务身份和连接代次。 */
IEC104_ClockSynchronizationConfig_t config;
IEC104_ClockSynchronizationStatus_t status; /* 累计计数和活动状态;调度字段由查询即时计算。 */
ClockTask tasks[IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY];
unsigned order[IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY], count; /* FIFO槽下标;占用数量。 */
uint64_t serial; /* 进程内递增的实例编号,销毁后不回收编号。 */
bool in_use, in_action; /* 实例池占用;动作期间阻止修改模块或递归执行其他任务。 */
};
static IEC104_ClockSynchronization_t clock_pool[IEC104_CLOCK_SYNCHRONIZATION_CAPACITY];
static uint64_t next_serial;
/* 先比对池内地址再解引用,NULL或不属于本池的句柄均返回false。 */
static bool Owned(const IEC104_ClockSynchronization_t *self)
{
unsigned i;
for (i = 0U; i < IEC104_CLOCK_SYNCHRONIZATION_CAPACITY; ++i) {
if (self == &clock_pool[i]) { return self->in_use; }
}
return false;
}
/* 结束一个任务:广播End快照,定向Release持有的站点,再归还固定槽。
* 正常提交、超时应答完成和所有取消路径均使用此入口,避免遗留站点引用。
* 活动快照含自引用,任务永不搬移;仅压缩顺序索引。 */
static void Remove_Task(IEC104_ClockSynchronization_t *self, ClockTask *task)
{
unsigned index, slot = (unsigned)(task - self->tasks);
if (task->stations._owner != NULL) {
(void)IEC104_Slave_StationSnapshot_End(&task->stations);
} else if (task->station != NULL) {
(void)IEC104_Slave_Release_Station(self->slave, task->station);
}
for (index = 0U; index < self->count; ++index) {
if (self->order[index] == slot) {
memmove(self->order + index, self->order + index + 1U,
(self->count - index - 1U) * sizeof(self->order[0]));
--self->count;
break;
}
}
memset(task, 0, sizeof(*task));
}
/* 内部统一清理:cancelled按任务数累计,不按广播站数累计。
* 只清理本模块仍持有的任务;发送层已接手的字节和应用已改过的时钟不受影响。 */
static void Cancel(IEC104_ClockSynchronization_t *self)
{
while (self->count != 0U) {
++self->status.cancelled;
Remove_Task(self, &self->tasks[self->order[0]]);
}
}
/* 从空闲固定槽取一个并追加FIFO下标;满槽返回NULL,既不覆盖也不移动旧任务。 */
static ClockTask *Allocate_Task(IEC104_ClockSynchronization_t *self)
{
unsigned slot;
for (slot = 0U; slot < IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY; ++slot) {
if (!self->tasks[slot].occupied) {
ClockTask *task = &self->tasks[slot];
memset(task, 0, sizeof(*task));
task->occupied = true;
self->order[self->count++] = slot;
return task;
}
}
return NULL;
}
/* UTC纪元毫秒上限:Seconds_To_Local还要加本站UTC+8,须预留这8小时。
* 先转64位再乘1000;尾部999允许最后一个合法整秒内的全部毫秒。 */
static uint64_t Limit_Ms(void)
{
return ((uint64_t)IEC60870_TIME_MAX_SECONDS -
(uint64_t)IEC60870_LOCAL_UTC_OFFSET_MINUTES * 60U) * 1000U + 999U;
}
/* 将完整16字节请求中的CP56换算为UTC毫秒,输出仅在全部检查成功时更新。
* 对象解码检查日期及保留位;CP56_To_Seconds检查IV、源时区并扣除SU对应的一小时。
* UTC合法之后还须确认固定UTC+8的本站日历没有越界,此处尚不补偿站内等待时间。
* 错误回复仍使用原文字节,不要求非法时标重新编码,也不会用换算值改写确认时标。 */
static bool Decode_Time(const IEC104_ClockSynchronization_t *self, const uint8_t *bytes,
IEC60870_CP56Time2a_t *time, uint64_t *utc_ms)
{
IEC60870_ClockSynchronizationCommand_t command;
IEC60870_WallTime_t local;
uint32_t seconds;
uint64_t converted;
if (IEC60870_ClockSynchronizationCommand_Get_From_Buffer(&command, 0U, bytes + 9U, 7U) !=
IEC60870_ASDU_OK ||
!IEC60870_Time_CP56_To_Seconds(&command.time, self->config.master_utc_offset_minutes, &seconds)) {
return false;
}
converted = (uint64_t)seconds * 1000U + command.time.time.ms;
if (converted > Limit_Ms() ||
!IEC60870_Time_Seconds_To_Local(seconds, command.time.time.ms, &local)) { return false; }
*time = command.time;
*utc_ms = converted;
return true;
}
/* 查询当前身份是否还可执行/提交,不推进网络I/O。
* WAIT_EVENTS先交回应用读通知:即使最终链路又已START,也不能跳过中间的STOP。
* 查询失败或通知读空后发现会话失效,统一取消任务;通知优先于业务期限归因。 */
static IEC104_SlaveRequestState_t Check_Session(IEC104_ClockSynchronization_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 ||
(state != IEC104_SLAVE_REQUEST_WAIT_EVENTS && (!self->status.active || state != IEC104_SLAVE_REQUEST_READY))) {
Cancel(self);
self->status.active = false;
return state == IEC104_SLAVE_REQUEST_ERROR ? state : IEC104_SLAVE_REQUEST_STALE;
}
return state;
}
/* 尚未执行的任务因期限或时间错误改为否定ACT_CON,保留原槽和FIFO位置。
* 不置executed,因此它走普通应答路径;后续Execute_Next不会再选中此任务。 */
static void Negative_Reply(ClockTask *task)
{
task->phase = CLOCK_REPLY;
task->cause = IEC60870_ASDU_COT_ACTIVATION_CON;
task->negative = true;
task->send_waiting = false;
}
/* 返回启用中的执行期限剩余毫秒;0为到期(含单调时间倒退)。
* 同一采样供到期判断与执行补偿使用;倒退显式归因,不依赖无符号下溢。 */
static uint32_t Remaining(const IEC104_ClockSynchronization_t *self, const ClockTask *task, uint64_t now)
{
if (now < task->accepted_ms || now - task->accepted_ms >= self->config.execute_timeout_ms) { return 0U; }
return self->config.execute_timeout_ms - (uint32_t)(now - task->accepted_ms);
}
/* Process和Execute_Next共用的期限处理;就地转换,不调用应用、不直接发包。
* 只约束尚未执行的任务;action一旦调用,其确认等待不再受此期限约束。 */
static void Expire(IEC104_ClockSynchronization_t *self, uint64_t now)
{
unsigned index;
if (self->config.execute_timeout_ms == 0U) { return; }
for (index = 0U; index < self->count; ++index) {
ClockTask *task = &self->tasks[self->order[index]];
if (task->phase == CLOCK_EXECUTE && !task->executed && Remaining(self, task, now) == 0U) {
Negative_Reply(task);
++self->status.timeouts;
}
}
}
/* 提交入口可能先处理网络输入,导致前一次检查READY、实际提交却遇到STOP。
* 仅当同一连接仍健康、传输已停用且有未读通知时,将ERROR归为等待STOP通知。
* 此路径保留任务,不计发送WAIT;应用消费DEACTIVATED后统一取消。
* 其他ERROR不能冒充STOP,FATAL可能已写出字节,始终不可重试。 */
static bool Defer_For_Stop(IEC104_ClockSynchronization_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;
}
/* 一步最多提交一个目标,0表示让出,1表示有进展,-1表示查询/组包失败。
* 每次先查会话,不能跨过发送入口顺带交付的新通知;任务头之外的应答不能抢先。 */
static int Submit_Head(IEC104_ClockSynchronization_t *self)
{
ClockTask *task;
IEC60870_Asdu_t reply;
IEC104_TcpSubmitResult_t submitted;
IEC104_SlaveRequestState_t state = Check_Session(self);
if (state == IEC104_SLAVE_REQUEST_ERROR) { return -1; }
if (state != IEC104_SLAVE_REQUEST_READY || self->count == 0U) { return 0; }
task = &self->tasks[self->order[0]];
if (task->phase != CLOCK_REPLY) { return 0; }
if (task->stations._owner != NULL) {
/* WAIT期间删除当前目标也须跳过;快照保护节点存储,不重新按CA绑定替代站。 */
if (task->station != NULL && !IEC104_Station_Is_Registered(task->station)) { task->station = NULL; }
if (task->station == NULL) { task->station = IEC104_Slave_StationSnapshot_Next(&task->stations); }
if (task->station == NULL) { Remove_Task(self, task); return 1; }
task->response_ca = IEC104_Station_Get_Common_Address(task->station);
}
if (IEC60870_Asdu_Create_From_Buffer(&reply, IEC104_Slave_Get_Application_Layer_Parameters(self->slave),
task->bytes, sizeof(task->bytes)) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_COT(&reply, task->cause) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_Negative(&reply, task->negative) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_CA(&reply, task->response_ca) != IEC60870_ASDU_OK) { return -1; }
/* 调用过action的肯定/否定确认都走Try,等HIGH/NORMAL及当前帧让出位置,
* 防止确认越过已排队的旧时标数据;未执行动作的校验/期限错误可进入HIGH。 */
submitted = task->executed ? IEC104_Slave_Try_Send_Response(self->slave, &task->identity, &reply) :
IEC104_Slave_Send_Response(self->slave, &task->identity, &reply);
if (submitted == IEC104_TCP_SUBMIT_OK) {
/* OK只表示发送层接手,不等于socket写完或主站ACK;此时不可再次提交同一目标。 */
++self->status.submitted;
if (task->negative) { ++self->status.negative_submitted; }
task->send_waiting = false;
if (task->stations._owner != NULL) { task->station = NULL; }
else { Remove_Task(self, task); }
return 1;
}
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
/* 保留当前站及已构造的字节,网络推进后重试提交,绝不重做设时动作。 */
++self->status.waits;
task->send_waiting = true;
return 0;
}
if (Defer_For_Stop(self, &task->identity, submitted)) { return 0; }
Cancel(self);
self->status.active = false;
return 0;
}
/* 受理一条103:先确认能对应完整对象,再按COT -> CA -> IOA -> 其他原因判错。
* 非本模块请求交后续处理器;已识别但畸形/满槽的请求返回HANDLED,不能再走44兜底。
* 本函数不调用action,合法请求只进入EXECUTE,其他可应答请求进入REPLY。 */
static IEC104_ClockSynchronizationEventResult_t Request(IEC104_ClockSynchronization_t *self,
const IEC104_SlaveEvent_t *event, const IEC104_SlaveEventIdentity_t *identity)
{
IEC60870_AsduInfo_t info;
IEC60870_CP56Time2a_t time;
IEC104_Station_t *station;
ClockTask *task;
const uint8_t *data;
uint16_t length;
uint32_t ioa;
uint64_t utc_ms;
uint8_t cause = 0U;
bool broadcast, valid_time;
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] != 103U ||
!IEC60870_Parse_ASDU_Header(event->asdu.parameters, data, length, &info)) {
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_IGNORED;
}
if (info.object_count != 1U || info.information_objects_len < 10U) {
/* 无法保留唯一完整的IOA+CP56,不能靠截取或补零伪造可回显的请求。 */
++self->status.rejected;
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
}
ioa = (uint32_t)data[6] | ((uint32_t)data[7] << 8) | ((uint32_t)data[8] << 16);
station = IEC104_Slave_Get_Station(self->slave, info.common_address);
broadcast = info.common_address == IEC104_CA_BROADCAST && IEC104_Slave_Get_Station_Count(self->slave) != 0U;
valid_time = Decode_Time(self, data, &time, &utc_ms);
/* 即使时间非法也保持错误优先级;例如COT=5必须回45,不能被非法日期改成7。 */
if (info.cause_of_transmission != IEC60870_ASDU_COT_ACTIVATION) { cause = IEC60870_ASDU_COT_UNKNOWN_CAUSE; }
else if (!broadcast && 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 != 16U || info.is_negative_confirm || info.is_test || !valid_time) {
cause = IEC60870_ASDU_COT_ACTIVATION_CON;
}
if (cause != 0U) { ++self->status.rejected; }
if (self->count == IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY) {
/* 既有任务尚未交完,满槽时立即回复新请求会插队;只丢新并分别计数。 */
++self->status.overloads;
++self->status.dropped_requests;
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
}
task = Allocate_Task(self);
if (task == NULL) { return IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR; }
task->identity = *identity;
/* 原事件仍由调用方持有,只保存对象完整的前16字节;SQ/尾字节错误也可规范回显。 */
memcpy(task->bytes, data, sizeof(task->bytes));
task->bytes[1] = 1U;
task->response_ca = info.common_address;
task->receive_serial = event->transport.data.i_frame.receive_serial;
/* 事件没有网络接收时刻,补偿从此处受理开始,无法补上此前FIFO及网络等待。 */
task->accepted_ms = IEC104_Net_Now_Ms();
task->phase = cause == 0U ? CLOCK_EXECUTE : CLOCK_REPLY;
task->cause = cause;
task->negative = cause != 0U;
/* 无站广播没有快照,按COT优先级用原CA单帧回45或46。 */
if (broadcast) {
if (IEC104_Slave_StationSnapshot_Begin(self->slave, &task->stations) != IEC104_STATION_OK) {
Remove_Task(self, task);
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR;
}
} else if (cause == 0U) {
if (IEC104_Slave_Acquire_Station(self->slave, station) != IEC104_STATION_OK) {
Remove_Task(self, task);
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR;
}
task->station = station;
}
if (cause == 0U) { ++self->status.accepted; }
/* 只有唯一的单目标错误应答可尝试当场提交;Submit_Head仍先检查所有未读通知。 */
if (cause != 0U && !broadcast && self->count == 1U && Submit_Head(self) < 0) {
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR;
}
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
}
/* 默认主站使用UTC+8标准时,受理后最多等待15秒执行;NULL配置指针不作处理。 */
void IEC104_ClockSynchronization_Config_Default(IEC104_ClockSynchronizationConfig_t *config)
{
if (config != NULL) {
memset(config, 0, sizeof(*config));
config->master_utc_offset_minutes = IEC60870_LOCAL_UTC_OFFSET_MINUTES;
config->execute_timeout_ms = 15000U;
}
}
/* 创建只分配静态实例并复制配置,不初始化Slave、不消费通知,也不设置设备时间。
* 同一Slave不能同时绑定两个校时实例;编号耗尽时拒绝创建,避免回绕复用编号。 */
IEC104_ClockSynchronization_t *IEC104_ClockSynchronization_Create(IEC104_Slave_t *slave,
const IEC104_ClockSynchronizationConfig_t *config)
{
unsigned i;
IEC104_ClockSynchronization_t *free_slot = NULL;
if (config == NULL || config->master_utc_offset_minutes < -1439 ||
config->master_utc_offset_minutes > 1439 || next_serial == UINT64_MAX ||
IEC104_Slave_Get_Application_Layer_Parameters(slave) == NULL) { return NULL; }
for (i = 0U; i < IEC104_CLOCK_SYNCHRONIZATION_CAPACITY; ++i) {
if (clock_pool[i].in_use && clock_pool[i].slave == slave) { return NULL; }
if (!clock_pool[i].in_use && free_slot == NULL) { free_slot = &clock_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_serial;
free_slot->in_use = true;
return free_slot;
}
/* 有未完成任务时由调用方先Cancel;回调期间不能释放action正在使用的实例。 */
bool IEC104_ClockSynchronization_Destroy(IEC104_ClockSynchronization_t *self)
{
if (!Owned(self) || self->in_action || self->count != 0U) { return false; }
memset(self, 0, sizeof(*self));
return true;
}
/* 本地取消保持会话激活状态,后续新请求仍可受理;回调内调用无操作。 */
void IEC104_ClockSynchronization_Cancel(IEC104_ClockSynchronization_t *self)
{
if (Owned(self) && !self->in_action) { Cancel(self); }
}
/* 与其他业务模块一同按Slave FIFO顺序接收连接及启停通知,再分派I帧。
* 服务实例身份一旦绑定不得替换;CONNECTED重置连接代次,旧代次通知不推进任务。
* STOP/断线清理待执行及待确认任务,已调用action的设备时间不会被回滚。 */
IEC104_ClockSynchronizationEventResult_t IEC104_ClockSynchronization_Handle_Event(
IEC104_ClockSynchronization_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_CLOCK_SYNCHRONIZATION_EVENT_ERROR;
}
self->session.slave = identity.slave;
self->session.instance_id = identity.instance_id;
if (event->transport.type == IEC104_TCP_EVENT_CONNECTED) {
Cancel(self);
self->session.generation = identity.generation;
self->status.active = false;
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
}
if (identity.generation != self->session.generation) { return IEC104_CLOCK_SYNCHRONIZATION_EVENT_IGNORED; }
switch (event->transport.type) {
case IEC104_TCP_EVENT_ACTIVATED:
self->status.active = true;
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
case IEC104_TCP_EVENT_DEACTIVATED:
case IEC104_TCP_EVENT_DISCONNECTED:
Cancel(self);
self->status.active = false;
if (event->transport.type == IEC104_TCP_EVENT_DISCONNECTED) { self->session.generation = 0U; }
return IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED;
case IEC104_TCP_EVENT_I_FRAME: return Request(self, event, &identity);
default: return IEC104_CLOCK_SYNCHRONIZATION_EVENT_IGNORED;
}
}
/* 有界推进:每步先核对会话,再处理期限、尝试交接队首回复,绝不调用action。
* 广播每交接一个站或结束一次遍历均消耗一步;预算耗尽交回应用,避免独占事件循环。
* ERROR/FATAL已按提交结果清理任务时返回true,false仅表示接口/查询/组包故障。 */
bool IEC104_ClockSynchronization_Process(IEC104_ClockSynchronization_t *self)
{
unsigned step;
if (!Owned(self) || self->in_action) { return false; }
for (step = 0U; step < 2U * IEC104_CLOCK_SYNCHRONIZATION_TASK_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 || self->count == 0U) { return true; }
Expire(self, IEC104_Net_Now_Ms());
if (self->tasks[self->order[0]].phase == CLOCK_EXECUTE) { return true; }
submitted = Submit_Head(self);
if (submitted <= 0) { return submitted == 0; }
}
return true;
}
/* 唯一的设时动作入口:每次最多调用一次action,不保存回调和context。
* 可以越过前面的REPLY寻找最早EXECUTE,但只允许队首提交应答,回复顺序不变。
* 已过期或时间不可用的请求转成否定回复后继续寻找,最终可能EMPTY且仍有回复待交。 */
IEC104_ClockSynchronizationExecuteResult_t IEC104_ClockSynchronization_Execute_Next(
IEC104_ClockSynchronization_t *self, IEC104_ClockSynchronizationAction_t action, void *context)
{
unsigned index;
if (!Owned(self) || self->in_action || action == NULL) { return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR; }
for (;;) {
IEC104_ClockSynchronizationRequest_t request;
IEC104_SlaveRequestState_t state;
ClockTask *task = NULL;
uint64_t now, utc_ms;
bool synchronized;
for (index = 0U; index < self->count; ++index) {
if (self->tasks[self->order[index]].phase == CLOCK_EXECUTE) {
task = &self->tasks[self->order[index]];
break;
}
}
if (task == NULL) { return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY; }
state = Check_Session(self);
if (state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR; }
if (state == IEC104_SLAVE_REQUEST_WAIT_EVENTS) { return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_WAIT; }
if (state != IEC104_SLAVE_REQUEST_READY) { return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY; }
/* 期限与设时基准使用同一采样;到期转换保持原槽及顺序,不调用应用。 */
now = IEC104_Net_Now_Ms();
Expire(self, now);
if (task->phase != CLOCK_EXECUTE) { continue; }
memset(&request, 0, sizeof(request));
/* Decode_Time成功保证utc_ms不大于上限;先比较剩余空间,再加等待毫秒,
* 同时防止64位溢出及本站日历越2099年。期限关闭也不能跳过这些检查。 */
if (now < task->accepted_ms || !Decode_Time(self, task->bytes, &request.master_time, &utc_ms) ||
now - task->accepted_ms > Limit_Ms() - utc_ms) {
Negative_Reply(task);
++self->status.time_errors;
continue;
}
utc_ms += now - task->accepted_ms;
/* 只将补偿后的时间交给action;task->bytes中的主站CP56保持原样供确认回显。 */
if (!IEC60870_Time_Seconds_To_Local((uint32_t)(utc_ms / 1000U),
(uint16_t)(utc_ms % 1000U), &request.local_time)) {
Negative_Reply(task);
++self->status.time_errors;
continue;
}
request.generation = task->identity.generation;
request.receive_serial = task->receive_serial;
request.station_id = task->stations._owner != NULL ? 0U : IEC104_Station_Get_Id(task->station);
request.common_address = (uint16_t)((uint16_t)task->bytes[4] | ((uint16_t)task->bytes[5] << 8));
request.originator_address = task->bytes[3];
request.utc_ms = utc_ms;
request.accepted_ms = task->accepted_ms;
request.base_ms = now;
/* 先消费,再开放只读查询:回调内已不计执行期限,且confirm_pending为true。
* in_action阻止递归执行下一槽或释放当前槽,回调返回后不重新判定业务超时。 */
task->executed = true;
task->phase = CLOCK_REPLY;
task->cause = IEC60870_ASDU_COT_ACTIVATION_CON;
task->send_waiting = false;
self->in_action = true;
synchronized = action(context, &request);
self->in_action = false;
task->negative = !synchronized;
++self->status.executed;
if (synchronized) { ++self->status.synchronized; }
/* 动作结果与传输结果分开:此后无论等待、STOP或故障均不会重复动作。 */
if (&self->tasks[self->order[0]] == task) { (void)Submit_Head(self); }
return IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_OK;
}
}
/* 只读快照:重算未读通知、待执行/确认以及最近期限,不触发Expire或取消任务。
* 回调中也允许查询;此时当前动作已消费,confirm_pending为true,且不再计它的期限。 */
bool IEC104_ClockSynchronization_Get_Status(const IEC104_ClockSynchronization_t *self,
IEC104_ClockSynchronizationStatus_t *status)
{
IEC104_SlaveRequestState_t state;
uint64_t now;
unsigned index;
if (!Owned(self) || status == NULL) { return false; }
state = IEC104_Slave_Check_Request(self->slave, &self->session);
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
*status = self->status;
status->generation = self->session.generation;
status->pending_requests = self->count;
status->events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
status->execute_pending = false;
status->confirm_pending = false;
status->timer_active = false;
status->timeout_remaining_ms = 0U;
status->need_process = false;
now = IEC104_Net_Now_Ms();
for (index = 0U; index < self->count; ++index) {
const ClockTask *task = &self->tasks[self->order[index]];
if (task->executed) { status->confirm_pending = true; }
else if (task->phase == CLOCK_EXECUTE) {
status->execute_pending = true;
if (self->config.execute_timeout_ms != 0U) {
uint32_t remaining = Remaining(self, task, now);
if (!status->timer_active || remaining < status->timeout_remaining_ms) {
status->timer_active = true;
status->timeout_remaining_ms = remaining;
}
}
}
}
if (self->count != 0U && !self->in_action && !status->events_pending) {
const ClockTask *head = &self->tasks[self->order[0]];
/* 发送WAIT不要求空转;应用每次Poll并读空通知后仍必须Process重试。
* 需要清理的失效会话、到期任务或尚未等待发送的队首回复则应立即推进。 */
status->need_process = state != IEC104_SLAVE_REQUEST_READY || !status->active ||
(status->timer_active && status->timeout_remaining_ms == 0U) ||
(head->phase == CLOCK_REPLY && !head->send_waiting);
}
return true;
}