560 lines
29 KiB
C
560 lines
29 KiB
C
/*
|
||
* 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;
|
||
}
|