尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DLMS/COSEM 蓝皮书解读(八):Clock 类(class_id = 8)—— 全表的时间基准,一切时间戳的源头

DLMS/COSEM 蓝皮书解读(八):Clock 类(class_id = 8)—— 全表的时间基准,一切时间戳的源头 DLMS/COSEM 蓝皮书解读八Clock 类class_id 8—— 全表的时间基准一切时间戳的源头系列说明本系列基于 DLMS UA《Blue Book蓝皮书第 16 版 · 第 2 部分 —— COSEM interface classes》一个接口类一篇。第 7 篇讲了Profile genericclass_id 7它解决的是负荷曲线、事件日志这类历史序列怎么存、怎么只切一段读回来——而曲线里几乎总有时间这一列。本篇的Clock解决的是更上游的问题那列时间是从哪来的。冻结值的时刻、曲线的时间戳、Activity calendar判费率时段、Register activation切换费率全都依赖它。上篇回顾Profile generic把数据组织成二维表行由capture()或capture_period自动产生。可是这一行是几点几分采的这个信息不是Profile generic自己编出来的而是采集时从 Clock 对象里取出来的。所以一旦时钟跑偏、失效或夏令时算错曲线上的每一条记录都会错时段 —— 数据本身没错但全都挂在了错误的时间点上。版本说明Clock在蓝皮书里只有version 0一个版本原文未给出其他 version故本篇不设版本对比章节。0. 为什么时钟是全表的地基先想象一个没有可靠时钟的表计会发生什么业务动作依赖时钟的地方时钟错了的后果日冻结billing每天 00:00 冻结一次电量冻结点在 23:58 或 00:03 → 跨日电量归属错负荷曲线每 15 分钟一行行首是时间时间戳错 → 曲线错位、与对端系统对不上费率切换Activity calendar判断当前处于峰/谷/平切换提前/滞后 → 电费结算错误用户投诉最大需量周期需量周期起点由 measuring period 对齐需量窗口与电网考核窗口不一致事件日志每条事件记一个发生时刻无法与停电记录、主站日志做因果关联这些故障有个共同特征电表本身的计量是准的错的全是什么时候。这类问题最难排查 —— 你在实验室里复现不出来它只在时钟漂移几个月后、或每年两次夏令时切换的那一夜爆发。蓝皮书对Clock的定位原文是这样的蓝皮书原文Clock, Overview“This IC models the device clock, managing all information related to date and time including deviations of the local time to a generalized time reference (UTC) due to time zones and daylight saving time schemes. The IC also offers various methods to adjust the clock.”拆成三件事① 管日期和时间year / month / day of month / day of weekhour / minute / second / hundredths② 管本地时间与 UTC 的偏差deviation这个偏差由时区和夏令时两部分决定③ 提供校时手段6 个method。Overview 第二段的原文把夏令时的机制讲得很清楚“The daylight saving time function modifies the deviation of local time to UTC depending on the attributes; see … The start and end point of that function is normally set once. An internal algorithm calculates the real switch point depending on these settings.”关键理解夏令时的起止时刻不是两个写死的日期而是一组带通配符的规则参数表内有一个算法每年自己算出真实的切换点。所以你写进去的是三月最后一个周日 02:00而不是2024-03-31 02:00。这一点直接决定了daylight_savings_begin/daylight_savings_end的编码方式见示例 2。一句话定位Clock表计里唯一的时间权威。它不是一个存时间的格子而是一套本地时间 时区 夏令时规则 可信度标志 校时接口的完整时间系统。1. 类蓝图Clock 0...n class_id 8, version 0基数是0...n—— 一台设备里原则上可以有多个Clock实例比如主时钟与备用时钟但工程上绝大多数表只有一个OBIS 固定为0-0:1.0.0。属性静态/动态数据类型MinMaxDefShort namelogical_name(static)octet-stringxtime(dyn.)octet-stringx 0x08time_zone(static)long-840720x 0x10status(dyn.)unsignedx 0x18daylight_savings_begin(static)octet-stringx 0x20daylight_savings_end(static)octet-stringx 0x28daylight_savings_deviation(static)integer-120120x 0x30daylight_savings_enabled(static)booleanx 0x38clock_base(static)enumx 0x40方法必选/可选(m/o)Short nameadjust_to_quarter (data)ox 0x60adjust_to_measuring_period (data)ox 0x68adjust_to_minute (data)ox 0x70adjust_to_preset_time (data)ox 0x78preset_adjusting_time (data)ox 0x80shift_time (data)ox 0x88三个必须记住的结构性事实9 个属性里只有 2 个是 dyn.动态time和status。其余 7 个全是 static一次会话读一次缓存即可。6 个方法全是o可选没有任何一个校时方法是强制实现的主站调用前必须确认设备是否支持。方法区从x 0x60起COSEM 给方法短名预留的区间与属性区末尾x 0x40并不连续SN 寻址别按 0x08 无脑顺推。2. 属性逐条解读2.1logical_namestatic, octet-string“Identifies the ‘Clock’ object instance.”6 字节 OBIS。Clock的行业标准 OBIS 是0-0:1.0.0示例值实际以设备对象列表为准。注意 A 组 0表示这是抽象/公用对象不属于某个具体的电量。2.2timedyn., octet-string—— 本篇的核心12 个字节“Contains the meter’s local date and time, its deviation to UTC and the status. octet-string, formatted as specified in … for date-time.”date-time是蓝皮书定义的一个12 字节 octet-string结构如下这是理解全篇的基础务必记牢date-time :: OCTET STRING (SIZE(12)) year highbyte, year lowbyte, -- 0xFFFF not specified month, -- 1..12另有 0xFD dst_end, 0xFE dst_begin, 0xFF n.s. day of month, -- 1..310xFD 2nd last day, 0xFE last day, 0xFF n.s. day of week, -- 1..7 (1 Monday), 0xFF n.s. hour, minute, second, hundredths of second, -- 0xFF n.s. deviation highbyte, deviation lowbyte, -- long, -720..720 min, 0x8000 n.s. clock status -- unsigned见 2.4这里要纠正一个常见误解很多人以为deviation是Clock的一个独立 attribute。不是—— 9 个属性里没有deviation它是time这个 12 字节字符串的第 10–11 字节与第 12 字节的clock_status一起打包在时间里传输clock_status虽非独立属性但它的值被镜像到了status属性上。写time时有两条原文规则非常关键“When this attribute is set, all the fields in the octet-string representing date-time shall be evaluated and the local date and time in the meter shall be set according to the rules defined in …”“Only specified fields of the date-time are changed.”“EXAMPLE For setting the date without changing the time, all time-relevant octets in the octet string representing date-time shall be set to ‘not specified’.”即写时间时不想改的字段必须填not specified0xFF / 0xFFFF / 0x8000表计只改你填了的那些字段。这是 DLMS 校时最容易写错的地方见示例 4。deviation的取值范围是-720…720 分钟即 ±12 小时含义是本地时间相对 UTC 的偏移。正值表示东半球如北京 480。2.3time_zonestatic, long, -840…720“The deviation of local, normal time to UTC in minutes. The value depends on the geographical location of the meter.”三个细节① 单位是分钟不是小时② 它是正常时间normal time即冬令时的偏移不含夏令时增量③ 范围-840 … 720分钟-14:00 … 12:00留了余量以覆盖印度330、尼泊尔345这类非整小时时区。典型取值0UTC、60CET欧洲中部冬令时、480CST 北京、-300北美东部冬令时 EST。2.4statusdyn., unsigned“The clock_status maintained by the meter. The clock_status element indicated in the time attribute is equal to the value of this attribute.”这一句是全篇最重要的一致性约束time里第 12 字节的clock_status必须等于status属性的值。主站可以做交叉校验 —— 两者不一致说明表计实现有问题。clock_status是 8 个 bitbit名称含义原文 NOTE0 (LSB)invalid“Time could not be recovered after an incident.”事故后时间未能恢复如时钟供电中断。有效性约束bit 0 置位时bit 1 不应置位。1doubtful“Time could be recovered after an incident but the value cannot be guaranteed.”时间恢复了但不可保证准确。bit 1 置位时bit 0 不应置位。2different clock base“Bit is set if the basic timing information for the clock at the actual moment is taken from a timing source different from the source specified in clock_base.”当前实际时钟源与clock_base声明的不一致如 GPS 失锁后回落到晶振。3invalid clock status“This bit indicates that at least one bit of the clock status is invalid. Some bits may be correct.”状态本身不可信。4–6reserved保留7 (MSB)daylight saving active置 1the transmitted time contains the daylight saving deviation (summer time)置 0normal time。—0xFFnot specified工程用法读回time后先判断statusbit 0 或 bit 1 置位的时间戳不该被当作可信时间写入结算系统。这是很多主站漏掉的检查。2.5 / 2.6daylight_savings_begin/daylight_savings_endstatic, octet-string“Defines the local switch date and time when the local time starts to deviate from the normal time. For generic definitions, wildcards are allowed. octet-string, formatted as specified in … for date-time.”“Defines the local switch date and time when the local time ends to deviate from the local normal time.”要点数据类型和time一样是12 字节date-time同样带deviation与clock_status字段配置时通常置为 not specified支持通配符这是一次配置、每年自动生效的关键begin 是本地正常时间下的时刻end 是本地夏令时下的时刻 —— 两者不对称见示例 3。month字段还有两个特殊值0xFD daylight_savings_end、0xFE daylight_savings_begin可用于Activity calendar中定义与夏/冬令时挂钩的季节day of month的0xFE last day of month、0xFD 2nd last day of month配合day of week就能表达某月最后一个周日。2.7daylight_savings_deviationstatic, integer, -120…120“Contains the number of minutes by which the deviation in generalized time shall be corrected at daylight savings begin.”“Deviation in the range of ± 120 min”夏令时生效时deviation 要修正的分钟数。绝大多数国家是601 小时少数地区如历史上的 Lord Howe Island是 30。范围 ±120 分钟。2.8daylight_savings_enabledstatic, boolean“boolean: TRUE: DST enabled, FALSE: DST disabled”“NOTE This is a parameter to enable and disable the daylight savings time feature. The current status is indicated in the status attribute.”原文这条 NOTE 务必读两遍这个 boolean 是功能开关配置不是当前状态运行值。当前是否正处于夏令时要看status的bit 7。主站别拿daylight_savings_enabled去判断现在是夏天还是冬天。2.9clock_basestatic, enum“Defines where the basic timing information comes from.”枚举值0not defined、1internal crystal、2mains frequency 50 Hz、3mains frequency 60 Hz、4GPS (global positioning system)、5radio controlled。这个属性是**“声明”**表计声称自己用什么维持时基。它与status的bit 2different clock base配合使用 —— 声明 GPS4但当前实际跑在内部晶振上bit 2 就会置位等于表计在告诉你我现在的时间质量降级了。clock_base 1内部晶振的表长期漂移是必然的典型 ±5 ppm 约合每月 ±13 秒差的晶振可达每月数分钟这就是主站必须周期性对时的根本原因。3. 方法详解6 个校时动作全部是o可选。参数统一叫data绝大多数是integer (0)占位。方法data 类型作用原文adjust_to_quarterinteger (0)Sets the meter’s time to the nearest (/-) quarter of an hour value (:00, *:15, *:30,:45).adjust_to_measuring_periodinteger (0)“Sets the meter’s time to the nearest (/-) starting point of a measuring period.”adjust_to_minuteinteger (0)“Sets the meter’s time to the nearest minute.”adjust_to_preset_timeinteger (0)“This method is used in conjunction with the preset_adjusting_time method. If the meter’s time lies between validity_interval_start and validity_interval_end, then time is set to preset_time.”preset_adjusting_timestructure“Presets the time to a new value (preset_time) and defines a validity_interval within which the new time can be activated.”shift_timelong“Shifts the time by n (-900 n 900) s.”几个要点adjust_to_minute的四舍五入规则原文写得最细“If second_counter 30 s, second_counter is set to 0. If second_counter [≥] 30 s, second_counter is set to 0, and minute_counter and all depending clock values are incremented if necessary.”方括号内为按语义补全原文该处符号在提取时丢失即秒 30 直接抹零时钟后退秒 ≥ 30 抹零并进位一分钟时钟前进进位可能连锁触发小时/日/月/年。注意depending clock values包含日期—— 23:59:31 调一次会跳到次日 00:00:00。preset_adjusting_time是唯一带真实参数的方法三个字段全是date-time格式的 octet-stringdata :: structure { preset_time: octet-string, validity_interval_start: octet-string, validity_interval_end: octet-string }它不立即改时间只是把目标时间 生效窗口预置进去真正的切换要靠随后的adjust_to_preset_time触发。这个两步设计专为批量对时而生见示例 5。shift_time只能小步调整long范围-900 … 900 秒±15 分钟超过就得走 SETtime或preset_adjusting_time。adjust_to_quarter/adjust_to_measuring_period把时间对齐到整刻钟或测量周期起点让表内周期窗口与电网/主站对齐。注意(/-)表示可能向前也可能向后取整不是只往前。4. 【实战举例】以下示例中的 OBIS、配置取值与报文字节均为帮助理解而构造非蓝皮书原文实际以设备对象列表与厂商文档为准。示例 1读一次时钟把 12 个字节拆开GET (class_id8, logical_name0-0:1.0.0, attribute_index2)表计返回A-XDR09 0C 07 E8 03 1F 07 03 00 00 00 00 78 80拆开字节值解析前缀09 0Ctag octet-stringlength 121–207 E8year 0x07E8 20243–503 1F 07month 3day of month 31day of week 7Sunday1 Monday6–903 00 00 0003:00:00.0010–1100 78deviation 0x0078 120 min → UTC21280clock status bit 7 1 →夏令时生效中于是本地时间2024-03-31 03:00:00UTC 03:00 − 120 min 01:00 UTC。再读statusattribute 3应返回unsigned 1280x80与time第 12 字节一致 —— 这就是 2.4 那条一致性约束的现场验证。同一块表在冬天的读数09 0C 07 E8 01 0F 01 0A 1E 00 00 00 3C 00→ 2024-01-15周一10:30:00deviation 0x003C 60 minCETstatus 0x00正常、非夏令时。示例 2配置欧洲中部夏令时CET → CEST规则三月最后一个周日02:00:00本地正常时间进入夏令时十月最后一个周日03:00:00本地夏令时退出增量 60 分钟。# 静态配置示例值 time_zone 60 # CET UTC1 daylight_savings_deviation 60 # 1 小时 daylight_savings_enabled TRUE clock_base 1 # internal crystal # daylight_savings_begin每年三月最后一个周日 02:00:00 # 字段 → yearFFFF month03 domFE dow07 hh:mm:ss:hh02:00:00:00 dev8000 statusFF 09 0C FF FF 03 FE 07 02 00 00 00 80 00 FF # daylight_savings_end每年十月最后一个周日 03:00:00 09 0C FF FF 0A FE 07 03 00 00 00 80 00 FF注意这里day of month 0xFElast dayday of week 0x07Sunday的组合正是蓝皮书 date 定义里给的例子“year 0xFFFF, month 0x03, dayOfMonth 0xFE, dayOfWeek 0x07: last Sunday in March in every year”。这就是一次配置、年年自动生效的实现方式—— 表里跑的是算法不是日历表。上面字节的字段顺序为year / month / day of month / day of week / hour / minute / second / hundredths / deviation / clock status。deviation与clock_status这两个字段在日期配置里没有意义按原文规则应置为 not specified0x8000/0xFF。示例 3切换那一夜到底发生了什么时序春季前推spring forward2024-03-31CET01:59:59 deviation60 status0x00 正常时间 02:00:00 → 切换点到达内部算法触发 03:00:00 deviation120 status0x80 夏令时生效bit 7 1本地时间 02:00:00 ~ 02:59:59 这一小时在这一天不存在。表内的 15 分钟曲线如果按本地时间打时间戳这一天的 02:00/02:15/02:30/02:45 四个点是空缺的不是 0是没有。秋季回拨fall back2024-10-27CEST02:59:59 deviation120 status0x80 仍是夏令时 03:00:00 → 切换点到达 02:00:00 deviation60 status0x00 回到正常时间bit 7 清零 02:59:59 deviation60 status0x00 这一小时第一次走完 03:00:00 deviation60 status0x00 本地时间第二次经过 03:00本地时间 02:00:00 ~ 02:59:59 这一小时出现了两次。曲线上会出现两组时间戳完全相同但内容不同的记录。主站的应对三条缺一不可① 存储统一转 UTC——UTC 本地时间 − deviationdeviation就写在同一个time里不需要查时区库这正是 DLMS 把 deviation 塞进 date-time 的最大好处② 曲线排序键用 UTC 而非本地时间否则秋季回拨那一小时的记录顺序会乱③ 识别短日/长日—— 春季那天 23 小时、秋季 25 小时按24 条/天断言采集完整性会误报。示例 4只改日期不动时间 ——not specified的正确用法要把日期改成 2024-03-31、但保留表内当前的时分秒SETtime时必须把时间相关字节置为 0xFFSET (class_id8, logical_name0-0:1.0.0, attribute_index2) 09 0C 07 E8 03 1F 07 FF FF FF FF 80 00 FF year mo dom dow |--时间全 not specified--| dev status如果图省事把时间字节填00表计的时分秒会被一并改成 00:00:00 —— 这种顺手清零若在夜里执行会让日冻结和曲线采集瞬间错位反过来只改时间不改日期时year 应填0xFFFF、month/dayOfMonth/dayOfWeek 填0xFF。另外deviation的 not specified 是0x8000不是 0—— 填 0 会被理解成UTC0而非不改这是最容易踩的一个坑。小步微调则走shift_timedata long±900 秒慢 7 分钟 →shift_time(420)慢 20 分钟 →超出范围只能 SETtime或用preset_adjusting_time。示例 5批量对时 ——preset_adjusting_timeadjust_to_preset_time现场有 5000 块表要求在新年零点统一校时且不能让各表因通信延迟而产生几秒的离散。两步法# 第 1 步提前几天逐表下发「目标时间 生效窗口」预置此时不改时间 ACTION preset_adjusting_time data structure { preset_time 2024-01-01 00:00:00 (dev480, statusFF), validity_interval_start 2023-12-31 23:59:50 (dev8000, statusFF), validity_interval_end 2024-01-01 00:00:10 (dev8000, statusFF) } # 第 2 步在窗口内广播触发广播 ACTION 到达时刻有离散窗口把它包住了 ACTION adjust_to_preset_time data integer (0) # 每块表收到时的行为当前时间 ∈ [start, end] ? 是 → time : preset_time否 → 忽略原文对adjust_to_preset_time的描述正是“If the meter’s time lies between validity_interval_start and validity_interval_end, then time is set to preset_time.”——窗口外的触发会被忽略这正是它存在的意义。示例 6时钟失效时曲线数据怎么办某表掉电很久后上电读回GET time → 09 0C 07 E8 06 0F 06 0A 1E 00 00 80 00 01 year2024, month6, day15, dow6 (Saturday), 10:30:00 deviation 0x8000 → not specified ! clock status 0x01 → bit 0 (invalid) 置位即表计自己声明事故后时间未能恢复且连 deviation 都给不出来。主站应当先检查 bit 0 / bit 1任一置位 →该时间戳不可信。另记住原文的互斥约束 —— bit 0 与 bit 1不应同时置位读到0x03这类组合说明表计实现有问题“For a valid status, bit 0 shall not be set if bit 1 is set”。deviation 0x8000→ 无法换算 UTC曲线入库时不能按本地时间直接存。先对时、再读曲线此时若直接读Profile generic的 buffer读回来的一整段数据都挂着错误时间戳且这段数据在表内已不可修复。对时后若校正幅度超过阈值如 ±5 分钟主站应做时间校正留痕很多表会在事件日志里记一条时钟调整事件以便事后解释数据为何位移。5. 工程上容易踩的坑把deviation当成独立 attribute 去读Clock没有deviation属性它是time12 字节date-time的第 10–11 字节long范围-720…720 分钟0x8000 not specified。要算 UTC 就必须先拿到完整的time。秋季回拨的重复小时本地时间 02:00–02:59 出现两次曲线时间戳重复。入库键必须用UTC 本地时间 − deviation或者至少用本地时间 status bit 7复合去重。春季前推的缺失小时当天只有 23 小时按每天固定 N 条校验采集完整性会误报缺失。不检查status就把时间戳写进结算系统bit 0invalid/ bit 1doubtful置位说明表计自己都不信这个时间还要注意原文的互斥约束 ——bit 0 与 bit 1 不应同时置位读到0x03说明实现有问题。拿daylight_savings_enabled判断现在是不是夏令时错。它是功能开关当前状态要看status的bit 7原文“The current status is indicated in the status attribute.”。SETtime时不该改的字段没填 not specified时间字节填00会把时分秒清零deviation填0会被当成 UTC0正确写法是0x8000。以为time_zone含夏令时它是normal time的偏移夏令时增量在daylight_savings_deviation里夏令时期间二者叠加60 60 120。以为校时方法一定存在 / 一定够用6 个方法全部是o可选且需写权限shift_time只有±900 秒跨小时校时必须走 SETtime或preset_adjusting_timeadjust_to_minute秒 ≥ 30 会进位日期23:59:31 → 次日 00:00批量整分对齐要避开日冻结窗口。6. 小结 下期预告本篇要点Clockclass_id 8, version 0是表计里唯一的时间权威9 个属性 6 个可选校时方法只有time与status是 dyn.time是12 字节date-time打包了日期、时间、**deviation第 10–11 字节-720…720 分钟**与clock status第 12 字节——deviation不是独立属性status的 8 个 bit 是时间可信度的全部依据bit 0 invalid / bit 1 doubtful二者互斥/ bit 2 different clock base / bit 3 invalid clock status / bit 7 daylight saving activetime里的 clock status 必须等于它夏令时靠daylight_savings_begin/end支持通配符三月最后一个周日式配置daylight_savings_deviation±120 分钟daylight_savings_enabled驱动切换点由表内算法逐年算出写time时只改 specified 字段不想改的必须填0xFF/0xFFFF/0x8000校时方法全是 optional小步用shift_time±900 s整分对齐用adjust_to_minute批量同步用preset_adjusting_timeadjust_to_preset_time两步法。下一篇第 9 篇Script tableclass_id 9。本篇讲清了现在几点但光知道几点还不够 —— 到了切换费率、切换季节的那个瞬间谁去执行那一系列动作Script table就是把一组SET/ACTION打包成一个可执行动作的容器Activity calendar到点了调用Script table的execute脚本里的每一条指令依次执行切费率、激活寄存器、清需量……。如果说Clock决定了何时那么Script table决定的就是做什么。我们会讲scripts数组的结构、execute方法以及脚本执行失败时的部分生效问题。参考资料DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》Clock (class_id 8, version 0) 章节以及date-time/date/clock_status通用数据类型定义4.6.1 等。文中 9 个属性的名称、static/dyn 标记、数据类型、Min/Max、Short name 偏移、6 个方法的 m/o 与data类型、以及全部英文引文均与原文一致示例中的 OBIS0-0:1.0.0、时区/夏令时配置值、报文字节与时序为帮助理解而构造实际以设备对象列表与厂商文档为准。
返回列表