/* * 104时钟同步(C_CS_NA_1):接收主站校时请求,帮助应用校准供SOE、日志等使用的设备时间。 * 本模块负责校验、时区换算、站内滞留补偿及ACT_CON;实际设时由应用提供的action完成。 * Handle_Event/Process不调用应用代码;Execute_Next每次至多同步执行一个action,不保存回调。 * 设备日历与协议单调计时分开:设置设备时间不应改变Net提供的单调毫秒基准。 * 所有接口与Slave在同一线程串行调用;模块内应答严格按请求顺序。 * 执行后的ACT_CON使用Try路径,不越过HIGH/NORMAL待发原文;确认只回显请求时标。 * 退出顺序:Cancel -> Slave_Close -> 读空并分派通知 -> Destroy -> Remove_Station -> Slave_Destroy。 */ #ifndef IEC104_CLOCK_SYNCHRONIZATION_H #define IEC104_CLOCK_SYNCHRONIZATION_H #include "IEC104_Slave.h" #include "IEC60870_Time.h" #ifdef __cplusplus extern "C" { #endif typedef struct IEC104_ClockSynchronization IEC104_ClockSynchronization_t; typedef struct { int16_t master_utc_offset_minutes; /* 主站标准当地时间减UTC的分钟数,-1439..1439;默认480,不含SU。 */ uint32_t execute_timeout_ms; /* 受理至执行检查的期限,单位ms,默认15000;0禁用期限,仍检查时间范围。 */ } IEC104_ClockSynchronizationConfig_t; /* 交给action的只读纯值请求,指针仅在本次回调期间有效。 * utc_ms/local_time已补偿受理至base_ms的等待;应用不再叠加时区或同一段等待。 * 不补偿网络、Slave通知FIFO及action内部耗时;base_ms不是硬件实际生效时刻。 */ typedef struct { uint64_t generation, receive_serial; /* generation:原TCP会话代次;receive_serial:原I帧接收流水号。 */ uint64_t station_id; /* 定向请求的原站点注册编号,不是CA;广播为0,不绑定某个目标站。 */ uint16_t common_address; /* 请求原CA,广播保留65535;逐站确认的CA由模块另行填写。 */ uint8_t originator_address; /* 请求原OA,供应用识别发起方;本字段不是权限校验结果。 */ IEC60870_CP56Time2a_t master_time; /* 原CP56解码值,IV=0,保留主站日历和SU;未作时区换算及滞留补偿。 */ IEC60870_WallTime_t local_time; /* 对应base_ms的固定UTC+8标准日历,可直接交给Clock_Set或RTC适配。 */ uint64_t utc_ms; /* 对应base_ms的UTC纪元毫秒,自1970-01-01 00:00:00 UTC起算。 */ /* accepted_ms:Handle_Event受理时刻;base_ms:执行检查时刻。均为Net单调毫秒,允许为0。 */ uint64_t accepted_ms, base_ms; } IEC104_ClockSynchronizationRequest_t; /* 应用同步设时动作:context由Execute_Next原样传入,允许NULL;request为本次校验后的只读请求。 * true报告设置成功,false未报告成功;false不保证时钟未变,模块不提供硬件回滚。 * 须短时完成,不保留request指针等待异步执行;权限、允许回拨幅度和设备操作由应用判断。 * 不得重入本模块的修改入口或推进Slave事件循环;回调内仅允许查询本模块Get_Status。 * 每条通过执行检查的新请求各调用一次,不按内容去重;主站重发旧时标仍可能再次回拨。 */ typedef bool (*IEC104_ClockSynchronizationAction_t)(void *context, const IEC104_ClockSynchronizationRequest_t *request); typedef enum { IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR = -1, /* 参数、事件归属、重入或内部准备失败。 */ IEC104_CLOCK_SYNCHRONIZATION_EVENT_IGNORED = 0, /* 非103 I帧或本模块当前不能接手的通知。 */ IEC104_CLOCK_SYNCHRONIZATION_EVENT_HANDLED = 1 /* 含拒绝和丢新,不表示已应答。 */ } IEC104_ClockSynchronizationEventResult_t; typedef enum { IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR = -1, /* 参数、重入或服务状态查询失败;未调用action。 */ IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY = 0, /* 无可执行请求;仍可能有待提交应答。 */ IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_WAIT = 1, /* 先处理未读Slave通知。 */ IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_OK = 2 /* 已调用一次动作;确认可能因后续STOP/故障取消。 */ } IEC104_ClockSynchronizationExecuteResult_t; /* 只读调度快照;计数从Create起累计,Cancel及连接启停不清零,不代表对端已经收到应答。 */ typedef struct { uint64_t generation; /* 已分派给模块的会话代次;消费DISCONNECTED后为0。 */ bool active; /* 模块根据已分派通知及推进结果维护的激活状态,不替代当前网络状态查询。 */ bool events_pending; /* Slave尚有未读通知;应先NextEvent并按FIFO分派,再执行或续发。 */ bool execute_pending; /* 仍有占槽的待执行请求;不保证当前会话、期限已允许调用action。 */ bool confirm_pending; /* action已开始且确认尚未全部交接,含回调期间;不含仅校验失败的回复。 */ bool need_process; /* 当前有状态清理、到期或待尝试回复;false仍不能跳过每轮Process的WAIT重试。 */ bool timer_active; /* 存在尚未执行且启用了执行期限的请求。 */ uint32_t timeout_remaining_ms; /* 所有执行期限的最小剩余ms;到期为0,无计时任务时也为0。 */ unsigned pending_requests; /* 占用任务槽数,含待执行、回调中及待回复;一条广播始终只占一槽。 */ /* accepted:合法且成功入槽的请求数,含后来超时/取消者。 * executed:action已返回的次数;synchronized:其中返回true的次数。 */ unsigned long accepted, executed, synchronized; /* submitted:发送层返回OK的ASDU数,广播逐目标计数;negative_submitted为其中P/N=1的数量。 */ unsigned long submitted, negative_submitted; /* rejected:受理校验拒绝数,含无法应答的畸形请求,不含后续超时或action失败。 * waits:发送返回WAIT的次数,重试可重复累计;不计等待Slave通知的次数。 * cancelled:取消的占槽请求数,广播部分确认后取消也只计一次,不保证设备时间未变。 * overloads/dropped_requests:槽满丢新的次数/请求数,同时增加;对象数错误/对象截短只计rejected。 * timeouts:启用期限时,执行前到期或单调倒退而转否定的请求数。 * time_errors:执行检查的换算/范围失败,以及禁用期限时的单调倒退,不含受理校验拒绝。 */ unsigned long rejected, waits, cancelled, overloads, dropped_requests, timeouts, time_errors; } IEC104_ClockSynchronizationStatus_t; /* 向config写默认源时区480分钟、执行期限15000ms;config为NULL时无操作。 * 只填写配置,不创建实例;调用方可修改后交给Create。 */ void IEC104_ClockSynchronization_Config_Default(IEC104_ClockSynchronizationConfig_t *config); /* 为有效slave创建独立校时实例;config须非NULL,按值复制,返回后不再借用配置指针。 * slave由应用拥有,寿命须覆盖本实例;可在Slave_Init前创建,但必须早于消费该服务的通知。 * 每服务只允许一个实例,按共用站点表路由;不分配堆内存、不建立监听、不保存应用action。 * 成功返回池内句柄;参数/配置非法、重复绑定或池/身份资源耗尽返回NULL。 * IEC104_CLOCK_SYNCHRONIZATION_CAPACITY默认等于TCP服务容量; * IEC104_CLOCK_SYNCHRONIZATION_TASK_CAPACITY默认4,限定每实例的任务槽数。 */ IEC104_ClockSynchronization_t *IEC104_ClockSynchronization_Create(IEC104_Slave_t *slave, const IEC104_ClockSynchronizationConfig_t *config); /* 归还self占用的静态池槽;true成功,成功后必须停止使用旧句柄。 * 句柄无效、仍有占槽任务或处于action回调中返回false;不隐式Cancel,也不关闭Slave。 */ bool IEC104_ClockSynchronization_Destroy(IEC104_ClockSynchronization_t *self); /* 取消self的全部占槽任务并归还站点引用/快照,每条请求增加一次cancelled。 * 不停用连接,可继续接手后续请求;不撤回已交接帧、不回滚设备时间。 * 无返回值;无效句柄或action回调中调用均无操作。 */ void IEC104_ClockSynchronization_Cancel(IEC104_ClockSynchronization_t *self); /* 将event按Slave FIFO次序交给self;event须来自所属Slave的NextEvent或合法Copy_Event副本。 * 所有连接/启停通知都须分派,103 I帧由本模块接手;不修改输入事件,不调用action。 * 合法请求保存为待执行任务;错误回复可当场提交。COT/CA/IOA优先回45/46/47, * 其余可对应的非法请求否定7;不能保留唯一完整对象时仅计拒绝,槽满丢新并计数、不断链。 * 广播只执行一次动作,按受理时的快照逐站确认;已删除的待提交目标跳过,新注册站不参与。 * 返回HANDLED包含保存、拒绝、丢新和状态通知处理,不表示已应答;IGNORED表示未接手; * ERROR表示参数/归属/重入或内部准备失败,不可作为IGNORED继续生成兜底应答。 */ IEC104_ClockSynchronizationEventResult_t IEC104_ClockSynchronization_Handle_Event( IEC104_ClockSynchronization_t *self, const IEC104_SlaveEvent_t *event); /* 为self有界推进执行期限及队首回复,不调用action;须在每轮网络推进、通知分派后调用。 * true表示完成本轮检查,可能仍在等待、已取消任务或没有任务,不表示应答成功。 * 参数、重入、查询或组包错误返回false。发送ERROR/FATAL取消任务后返回true, * 正常STOP待通知的ERROR先让出;应用继续消费Slave通知处理后续断线/启停。 * 即使need_process为false也须每轮调用,以重试发送WAIT;不能以忙循环代替网络推进。 */ bool IEC104_ClockSynchronization_Process(IEC104_ClockSynchronization_t *self); /* 为self执行最早的待执行请求:action须非NULL,context原样传给它,本函数不保存两者。 * 先检查会话、未读通知、期限及补偿范围;每次至多调用一次action,过期请求只转为否定回复。 * 可越过较早的待回复任务执行后续请求,但确认仍按模块内请求顺序提交,不插队。 * OK表示action已经返回,可能报告失败,确认也可能待发或被后续STOP/故障取消; * WAIT表示先处理Slave通知;EMPTY表示已无可执行请求,仍可能有待回复任务; * ERROR为参数、重入或服务查询失败。WAIT/EMPTY/ERROR均未调用action。 * 动作开始后不再判该任务的执行期限;确认发送WAIT只重试确认,绝不重复动作。 */ IEC104_ClockSynchronizationExecuteResult_t IEC104_ClockSynchronization_Execute_Next( IEC104_ClockSynchronization_t *self, IEC104_ClockSynchronizationAction_t action, void *context); /* 将self的当前只读快照写入非NULL的status;true成功,句柄/参数或服务查询失败返回false且不改输出。 * 可在action回调内查询,不推进任务、不预留发送资源;正在执行的任务不计期限,confirm_pending为true。 * 执行后的确认走Try,已有HIGH/NORMAL待发内容会阻止提交;持续发布可能推迟确认。 * 需要确认先于新的带时标上报时,由发布方在confirm_pending期间暂停此类新上报并继续推进网络。 */ bool IEC104_ClockSynchronization_Get_Status(const IEC104_ClockSynchronization_t *self, IEC104_ClockSynchronizationStatus_t *status); #ifdef __cplusplus } #endif #endif /* IEC104_CLOCK_SYNCHRONIZATION_H */