DLMS/COSEM 蓝皮书解读(十九):Single action schedule 类(class_id = 22)—— 轻量级“到点执行“:和费率无关的那些周期动作 DLMS/COSEM 蓝皮书解读十九Single action schedule 类class_id 22—— 轻量级到点执行和费率无关的那些周期动作系列说明本系列基于 DLMS UA《Blue Book蓝皮书第 16 版 · 第 2 部分》一个接口类一篇。第 18 篇讲了Register monitorclass_id 21它解决的是数据驱动的触发——值越线就跑脚本过压、过流、反向潮流告警。本篇的Single action scheduleclass_id 22回到时间驱动阵营但定位比Schedule10和Activity calendar20更轻它只管在某个或某组时刻执行一个固定脚本而且明确不绑定费率。上篇回顾Register monitor让表计量到了就动。但还有一类需求是到了某个时刻就动却和费率切换毫无关系——比如每天凌晨 2 点做一次数据冻结、每月 1 号做一次自检、每周日做一次时钟校准。这些动作如果塞进Activity calendar一套完整的费率日历或Schedule带星期/例外日/有效窗口的复杂调度那是杀鸡用牛刀。蓝皮书为此单独给了一个更轻的接口类。0. 为什么需要 Single action schedule先对比一下已有的三个时间/调度类搞清楚它的位置类适用场景复杂度Activity calendar(20)费率日历season → week → day 三层到点切费率/切季节最高Schedule(10)复杂周期调度schedule_table_entry含星期、例外日、有效窗口、起止日期中Single action schedule(22)单一脚本 一组执行时刻与费率解耦最低蓝皮书原文Single action schedule, Overview“This IC allows modelling the execution of periodic actions within a meter. Such actions are not necessarily linked to tariffication (see ‘Activity calendar’ or ‘Schedule’).”原文一句话就定位清楚了它执行的周期动作不一定和费率相关。这是和第 10、20 篇最本质的区别——Activity calendar天生为费率生Schedule也常被拿来配合费率日程而Single action schedule把调度这件事剥离了费率语义变成纯粹的时刻 → 脚本。典型用途每日定时冻结、每月定时自诊断、定时清需量、定时向主站Push一个心跳。这些都不该去污染费率日历用Single action schedule最干净。1. 类蓝图Single action schedule 0...n class_id 22, version 0属性静态/动态数据类型MinMaxDefShort namelogical_namestaticoctet-string———xexecuted_scriptstaticscript———x 0x08typestaticenum———x 0x10execution_timestaticarray———x 0x18该类无方法Specific methods 一栏为空。配置好属性后由表计按execution_time自行触发客户端不主动调用。Single action schedule只有version 0无版本分裂。2. 属性逐条解读2.1 logical_name老规矩octet-string6 字节 OBIS。例如每日冻结定时任务可挂0-0:15.0.0.255示例 OBIS实际以设备对象列表为准。2.2 executed_script要执行的脚本蓝皮书原文Single action schedule, executed_script“Contains the logical name of the ‘Script table’ object and the script selector of the script to be executed.”注意它是单个脚本不是脚本数组——这就是Single action的Single这个 schedule 实例一辈子只对应Script table里的一条脚本。结构script :: structure { script_logical_name: octet-string, -- 指向某个 Script table 实例的 OBIS script_selector: long-unsigned -- 该 Script table 里的第几条脚本 }Script_logical_name和script_selector合起来唯一定位一条脚本详见第 9 篇Script table。想让一个 schedule 在不同日期执行不同脚本不行——那是Schedule10的活它的每个 entry 可带自己的script_logical_name/script_selector。这里只能一条。2.3 type调度模式enum 1~5这是本类最关键、也最容易被配错的一个属性。type决定了execution_time数组里元素的个数和通配符能不能用。原文给的 5 种取值蓝皮书原文Single action schedule, type“(1) size of execution_time 1; wildcard in date allowed,”“(2) size of execution_time n; all time values are the same, wildcards in date not allowed,”“(3) size of execution_time n; all time values are the same, wildcards in date are allowed,”“(4) size of execution_time n; time values may be different, wildcards in date not allowed,”“(5) size of execution_time n; time values may be different, wildcards in date are allowed”翻译成人话date 可通配指日期字段可用0xFF表示任意见 2.4 节typeexecution_time 元素数各元素 time 是否相同日期能否通配典型用途1恰好 1只有一个无所谓能每天同一时刻日期通配2n必须相同不能在 n 个具体日期的同一时刻日期全列出3n必须相同能在 n 个可通配表达的日期的同一时刻如每周一周五 02:004n可不同不能在 n 个具体日期的不同具体时刻日期全列出5n可不同能最灵活n 个可通配日期的不同时刻一句话记忆type的个位管日期能不能通配1/3/5 能2/4 不能十位管时刻能不能各异4/5 能1/2/3 必须相同。五种模式各配一个最小execution_time示意仅展示数量与约束字段含义见 2.4type1[ {t, date通配} ]—— 1 个元素type2[ {t, d1}, {t, d2}, ... ]—— n 个time 全 tdate 为具体值type3[ {t, d1*}, {t, d2*}, ... ]—— n 个time 全 tdate 可含通配type4[ {t1, d1}, {t2, d2}, ... ]—— n 个time 可不同date 为具体值type5[ {t1, d1*}, {t2, d2*}, ... ]—— n 个time 可不同date 可含通配2.4 execution_time执行时刻数组蓝皮书原文Single action schedule, execution_time“Specifies the time and the date when the script is executed.”array execution_time_date execution_time_date :: structure { time: octet-string, -- 时间 date: octet-string -- 日期 }蓝皮书原文Single action schedule, execution_time“The two octet-strings contain time and date, in this order; time and date are formatted as specified in [date-time]. Hundredths of seconds shall be zero.”关键点每个元素是一个{time, date}结构time 在前、date 在后两个都是octet-string编码按date-time 规范IEC 62056-6-2 里的time/date类型。百分秒必须为 0Hundredths of seconds shall be zero即 time 的第 4 字节固定为 0。按 date-time 规范time与date两个 octet-string 的字段布局如下具体字节按 A-XDR 传是带 type tag 的 octet-stringoctet-string字节序字段含义time4 字节时, 分, 秒, 百分秒百分秒恒为 0date5 字节年高, 年低, 月, 日, 星期任意字段可用0xFF通配通配符规则与 date-time 规范一致0xFF在该字段表示任意。date FF FF FF FF FF 每天date FF FF 01 01 FF 每月 1 号date FF FF FF FF 01 每周一weekday 编码1周一 … 7周日注意具体编号以蓝皮书 date-time 定义为准。想表达每周一用 weekday具体值 日0xFF想表达每月 1 号用 日具体值 weekday0xFF。两者都填具体值则退化为单个具体日期。2.5 A-XDR 字节示意time / date 怎么落地成 octet-string以每天 02:00:00.00为例time的 A-XDR 编码按第 1、2 篇讲的 A-XDR 规则octet-string tag 0x09, length 4, value 02 00 00 00 → time 段 09 04 02 00 00 00 octet-string tag 0x09, length 5, value FF FF FF FF FF (每天) → date 段 09 05 FF FF FF FF FF两者合进execution_time_date这个structuretag 0x02structure { time 09 04 02 00 00 00, date 09 05 FF FF FF FF FF } → 整段 02 0D 09 04 02 00 00 00 09 05 FF FF FF FF FF 0x0D 13 字节 time 段 6 date 段 7execution_time本身是array外面再套array的 tag0x01 长度 上面这段。这是示意真实报文还可能因选择性访问/编码变体略有差异但 time/date 两个 octet-string 的内部字节布局就是上表那套。3. 方法没有方法。客户端只SET上述属性来定义规则触发由表计内部时钟到达execution_time时自动执行executed_script指向的那条脚本。这和Schedule有enable/disable/insert/delete四个 method 不同——Single action schedule想停用只能把属性SET成不触发的状态例如type配成不期望的模式或直接改写execution_time没有专门开关 method。4. 【实战举例】下面 OBIS、通配符字节、配置值均为帮助理解而构造的示例非蓝皮书原文实际以设备对象列表为准。示例 1每天 02:00 做一次日冻结type 1需求每天凌晨 2 点整执行日冻结脚本。用最省事的type 1size1日期可通配executed_script { script_logical_name 0-0:10.0.108.255, script_selector 5 }脚本表里第 5 条是日冻结type 1execution_time [ { time 02 00 00 00, date FF FF FF FF FF } ]其中date FF FF FF FF FF表示年/月/日/星期全部任意 每天time 02 00 00 00是 02:00:00.00。表计每天 2 点自动调第 5 条脚本做冻结。示例 2每月 1 号 03:00 自诊断type 3单个通配日期需求每月 1 号 3 点做自检。每月 1 号用通配日期表达时刻固定 →type 3size1time 相同date 可通配type 3execution_time [ { time 03 00 00 00, date FF FF 01 01 FF } ]date FF FF 01 01 FF年任意、月任意、日01、星期任意 → 每月 1 号。示例 3每周一和周五 22:00 推送心跳type 3两个通配日期、time 相同需求每周一、周五晚 10 点各推一次心跳。两个元素、time 相同(22:00)、date 可通配 →type 3type 3execution_time [{ time 22 00 00 00, date FF FF FF FF 01 },– 每周一(weekday1){ time 22 00 00 00, date FF FF FF FF 05 }– 每周五(weekday5)]注意两个元素的time必须完全相同type3 的约束这里都是 22:00:00合规。示例 4元旦、五一、国庆 0 点切标志type 4具体日期、time 相同需求三个固定节日的 0 点执行节假日标志置位脚本日期是具体年份不能通配因节日可能调休→type 4time 相同date 不可通配故列出具体日期type 4execution_time [{ time 00 00 00 00, date 07 E9 01 01 FF },– 2025-01-01{ time 00 00 00 00, date 07 E9 05 01 FF },– 2025-05-01{ time 00 00 00 00, date 07 E9 0A 01 FF }– 2025-10-01]年份07 E9 0x07E9 2025。这类具体日期场景用 type4若想每年同月同日而非固定年则需通配年份 → 改用 type3。示例 5一天两冻08:00 / 20:00type 5需求每天早 8 点、晚 8 点各冻结一次时刻不同、日期通配 →type 5type 5execution_time [{ time 08 00 00 00, date FF FF FF FF FF },{ time 20 00 00 00, date FF FF FF FF FF }]示例 6与 Schedule10/ Activity calendar20的选型对照需求用哪个理由每天 2 点冻结Single action schedule(22)单一脚本、与费率无关、最轻工作日 8 点、周末不开、节假日跳过Schedule(10)需要exec_weekdays/exec_specdays/validity_window峰谷季节费率切换Activity calendar(20)需要 season/week/day 三层 关联Register activation每月 1 号 3 点自检Single action schedule(22)单脚本周期动作不沾费率结论先问和费率有关吗、需要星期/例外日吗。都不沾 → 用本类要星期/例外日 →Schedule要完整费率日历 →Activity calendar。示例 7用 SN短名方式读取 / 配置若现场用 SN 寻址见第 1 篇 LN vs SNbase_name 设为某值x各属性落在x 偏移属性short namelogical_namexexecuted_scriptx 0x08typex 0x10execution_timex 0x18例如想GET当前type发 SN 读请求 base_name x 0x10表计返回一个enum如enum(1)表示每天同刻想SET把它改成每天两冻写 base_name x 0x10value enum(5)并同步把execution_timex 0x18改成两个{time, date}元素。注意 SN 下数据类型仍按属性表type是 enum、execution_time是 array。示例 8type 配错导致的真实故障复盘某工程想做每天 8 点、20 点各抄一次错把type设成3要求所有元素 time 相同却配了不同时刻type 3execution_time [ { time08 00 00 00, date每天 }, { time20 00 00 00, date每天 } ]结果表计只认第一个元素或SET直接被拒20:00 那次永远不跑。根因就是 type3 的all time values are the same约束被违反。正确写法应是type 5time 可不同、date 可通配。记忆口诀再强调一遍type 的个位管日期能否通配1/3/5 能2/4 不能十位管时刻能否各异4/5 能1/2/3 必须相同。凡是一天里多个不同时刻必选 4 或 5绝不能选 1/2/3。示例 9通配符的边界 —— 每月最后一天怎么表达date-time的通配符只能表达绝对字段的任意年/月/日/星期 各自可0xFF于是能拼出每天“每月 N 号”“每周 X”。但它表达不了每月最后一天“每季度末”闰年 2 月 29这类相对日期——因为没有月末通配符。需求若是每月最后一天 0 点做月结用通配符做不到没有最后一天通配。退路一显式列出未来若干年的每月最后一天type2/4date 全列出12 个元素/年维护成本高。退路二改用Schedule(10) 或在应用层用Activity calendar的 day profile 表达它同样受 date-time 通配符限制但对月末也需显式日。退路三把月结挂到每月 1 号 00:00 对上月数据做结每月 1 号是能通配的绕开月末问题。教训设计调度前先确认目标日期能否用 date-time 通配符表达不能就提前规划替代方案别等配到execution_time才发现无法通配。5. 工程上容易踩的坑type 与 execution_time 的约束必须自洽。原文对每种type的 size、time 是否相同、date 能否通配都有硬性规定。配type2却给 date 写了通配符、或type1却塞了 3 个元素——SET很可能被拒或表计只认第一个元素。配之前先按第 2.3 节的表核对一遍。type 的相同 time是逐字节相同。type2/3 要求所有元素的 time 完全一致。想每天 8 点和 20 点这种不同时刻必须用 type4/5而不是 type2/3。这是最常见的误用。被执行的只能是一条脚本。本类叫Single actionexecuted_script是单个脚本引用不是数组。需要不同时刻执行不同脚本请改用Schedule10。别试图在一个Single action schedule里塞多脚本。百分秒必须为 0。time 的第 4 字节固定 0。写成02 00 00 5050 百分秒不符合规范部分严格实现的表计会拒绝或忽略。通配符语义以 date-time 规范为准。0xFF 表示任意但星期通配和日通配要配对使用想表达每周一用 weekday1 日0xFF想表达每月 1 号用 日01 weekday0xFF。两个都写具体值会退化成单个具体日期。date 是 5 字节、time 是 4 字节别写反长度。A-XDR 里octet-string带长度前缀time 长度应为 4、date 长度应为 5长度字段填错会导致整帧解析错位。组包时按 date-time 规范严格对齐字节数。和Schedule/Activity calendar选型别乱。纯周期、与费率无关、单一脚本 → 用本类需要按星期/例外日/有效窗口精细排程、或与费率日历联动 → 用Schedule(10)要组织完整的峰谷季节费率年历 → 用Activity calendar(20)。三者职责不同混用会让对象列表难维护。没有 method停用要改属性。不像Schedule有enable/disable本类想临时停掉只能改写execution_time如改成空数组或不可能命中的时刻。规划时要考虑后续如何软关闭。掉电/时间回拨的处理原文未展开。本类Overview只定义在 execution_time 执行脚本原文未提供像Schedule那样的掉电恢复补执行 / 时间被向前向后设置如何处理细则。工程上若对错过时刻是否补执行有强需求需查设备厂商实现或改用Schedule其原文对此有专门说明见第 10 篇。时区与夏令时。execution_time 里的时间按表内Clock的本地时间解释若表计跑在夏令时切换环境跨切换的那一刻可能多/少触发一次配置整点动作时要意识到这一点必要时用Activity calendar的 daylight saving 处理。6. 小结 下期预告本篇要点Single action scheduleclass_id 22做轻量、与费率解耦的周期动作调度一个固定脚本 一组执行时刻。四个属性logical_name、executed_script单个script引用、typeenum 1~5决定 execution_time 的个数/时刻是否相同/日期能否通配、execution_timearray of{time, date}octet-string按 date-time 规范编码百分秒为 0。无 method触发纯表内自治。与Schedule、Activity calendar分工明确本类最轻、最纯调度。下一篇第 20 篇IEC HDLC setupclass_id 23—— 从调度逻辑跳到通信链路。前面讲了那么多对象模型它们最终都要通过物理信道传出去。本篇讲 IEC 62056-3-1 的 HDLC 链路层参数怎么在 COSEM 里建模comm_speed300 baud ~ 115200 baud 的 10 档、window_size收发窗口、max_info_field_length信息字段长度v1 从 128 放宽到 2030、inter_octet_time_out/inactivity_time_out超时、device_address单/双字节 HDLC 地址空间。我们会讲清version 0 与 version 1 的差异信息字段上限与超时范围的放宽以及它和上一篇IEC local port setup(19) 怎么配合——一个管用什么协议/本地口令一个管HDLC 帧层怎么协商。参考资料DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》Single action schedule (class_id 22, version 0) 章节。文中属性、数据类型、Short name 偏移x / x0x08 / x0x10 / x0x18、script与execution_time_date结构、type 的 5 种取值及引文均与原文一致示例中的 OBIS、通配符字节、配置值与脚本 selector 为帮助理解而构造实际以设备对象列表为准。