/* * 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 #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; }