ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DLMS/COSEM 蓝皮书解读(九):Script table 类(class_id = 9)—— “到点了做什么”:把一串 SET / ACTION 打包成可执行脚本

DLMS/COSEM 蓝皮书解读(九):Script table 类(class_id = 9)—— “到点了做什么”:把一串 SET / ACTION 打包成可执行脚本 DLMS/COSEM 蓝皮书解读九Script table 类class_id 9—— “到点了做什么”把一串 SET / ACTION 打包成可执行脚本系列说明本系列基于 DLMS UA《Blue Book蓝皮书第 16 版 · 第 2 部分 —— COSEM interface classes》一个接口类一篇。第 8 篇讲了Clockclass_id 8它解决的是全表的时间基准从哪来。本篇的Script table解决的是紧接着的下一个问题有了时间基准之后到点了具体要执行哪些动作由谁来描述、由谁来执行。它是Activity calendar20、Schedule10乃至Register monitor21共同的执行器。上篇回顾ClockOBIS 典型为0-0:1.0.0把现在几点做成了一个可配置、可信、可校时的对象于是Activity calendar能判断当前处于哪个费率时段、Profile generic能给曲线打上时间戳。但Clock只回答何时它不回答到 22:00 该做些什么。如果到点做什么只能靠主站在那一刻远程下发一串SET/ACTION那么通信中断、下发顺序错乱、成千上万块表无法在同一秒内全部下发都会让表内状态停在半途 —— 数据本身没错动作却漏了。版本说明Script table在蓝皮书里只有version 0一个版本raw 原文未给出其他 version故本篇不设版本对比章节。0. 为什么必须要有 Script table把表内自动化这件事拆开看一共只有三个要素什么时候when、做什么what、谁去执行who。COSEM 的分工异常清楚要素由谁负责类现在几点Clockclass_id 8什么时候执行Activity calendar/Schedule/Single action schedule20 / 10 / 22做什么Script tableclass_id 9被操作的对象Register activation、Register、Demand register、Profile generic…6 / 3 / 5 / 7先看几个真实的到点动作业务场景到点要做的一串动作22:00 切谷费率SETRegister activation的active_mask “valley”每月 1 日 00:00 结算ACTIONProfile generic的capture抓一次冻结负荷超阈值SET告警标志 触发一次上报不用Script table的话只有三条路每条都带硬伤① 主站到点下发—— 依赖在线率一个含 5 个SET的脚本若在第 3 个断线表内状态就停在半途5000 块表逐个下发还会有几十秒时间离散② 表内固件写死—— 改一次时段就要升级固件在计量行业意味着重新送检③ 厂商私有对象—— 各家各一套主站换一家表就得重写互操作性归零。COSEM 的答案是把做什么外置成数据动作清单写在一个attributescripts里由通用执行器execute方法执行。改动作 改数据一次SET不需要动固件也不会因为外部通信而中断。蓝皮书原文Script table, Overview“This IC allows modelling the triggering of a series of actions by executing scripts using the execute (data) method.”“Script table objects contain a table of script entries. Each entry consists of a script identifier and a series of action specifications. An action specification activates a method or modifies an attribute of a COSEM object within the logical device.”两句话拆出三个要点① 一个脚本script entry 脚本标识符 一串动作规格action specification② 单条动作只有两种可能 —— 激活一个 method或修改一个 attribute③ 被操作的对象必须在本 logical device 内脚本不能跨设备操作别的表。一句话定位Script table表内可配置的宏macro。它自己不计时、不判断条件只负责被叫到时把这串动作按顺序做完。1. 类蓝图Script table 0...n class_id 9, version 0基数是0...n—— 一台设备里可以有多个Script table实例按用途分组费率切换一张、日冻结一张、告警一张也可以一个都没有有些简表型表计没有任何时间表驱动的行为。属性静态/动态数据类型MinMaxDefShort namelogical_name(static)octet-stringxscripts(static)arrayx 0x08方法必选/可选(m/o)Short nameexecute (data)mx 0x20注 1原文未给 Min / Max / Def 三列任何取值故上表留空。另外两个属性全是 static—— 在 COSEM 里 static 表示值不会由设备自发改变不等于不可写scripts恰恰是主站最常改写的属性改脚本 一次SET。注 2关于executed执行结果记录——本卷 raw 原文未提供该项。Script tableversion 0只有上表 2 个属性没有记录哪个脚本在何时被执行、结果如何的属性。如果你的设备对象列表里出现了该类的 attribute 3请先核对这块表所依据的蓝皮书版本与厂商文档不要按 version 0 的模型去解析。三个必须记住的结构性事实execute是mmandatory强制实现—— 对比Clock的 6 个校时方法全是o本类存在的全部意义就是这个方法没有它这个类毫无价值。属性区与方法区的 Short name 不连续属性结束在x 0x08方法却在x 0x20。SNshort name寻址时不能按 0x08 步长顺推。只有 2 个属性但scripts是三层嵌套结构array → structure → array → structure实际复杂度全藏在这一个属性里。2. 属性逐条解读2.1logical_namestatic, octet-string“Identifies the ‘Script table’ object instance.”6 字节OBIS。Script table常见的 OBIS 取值形如0-0:10.0.0示例值非蓝皮书原文实际以设备对象列表为准。注意 A 组 0属于抽象/公用对象。这个 OBIS 会被别的对象引用见示例 4Activity calendar的script_logical_name、Schedule的script_logical_name、Single action schedule的executed_script填的都是它。因此改一个Script table的 OBIS 会影响所有引用方。2.2scriptsstatic, array—— 本篇的全部内容“Specifies the different scripts, i.e. the lists of actions.”原文给出的类型定义是两层scripts :: array script script :: structure { script_identifier: long-unsigned, actions: array action_specification }即scripts是脚本的数组每个脚本是一个2 元素 structure—— 一个long-unsigned的编号script_identifier 一个动作数组actions。关于编号有一条硬规则“The script_identifier 0 is reserved. If specified with an execute method, it results in a null script (no actions to perform).”script_identifier 0是保留值拿它去execute得到的是空脚本null script—— 什么都不做而且不报错见示例 3。action_specification一条动作怎么写action_specification :: structure { service_id: enum, class_id: long-unsigned, logical_name: octet-string, index: integer, parameter: service specific }五个字段的分工用一句话概括“对哪个对象的什么东西做什么带什么参数”。字段类型含义service_idenum做什么写属性write attribute/ 执行方法execute specific methodclass_idlong-unsigned目标对象的类6 Register activation、5 Demand register、7 Profile generic…logical_nameoctet-string目标对象的 OBIS不是Script table自己的indexinteger属性索引 / 方法索引parameterservice specific写属性时 要写入的值执行方法时 该方法的data原文对service_id与index的说明是“the service_id element defines which action to be applied to the referenced object: write attribute, execute specific method”“the index element defines (with service_id 1) which attribute of the selected object is affected; or (with service_id 2) which specific method is to be executed. The first attribute (logical_name) has index 1, the first specific method has index 1 as well.”推导出的枚举取值与两条编号规则service_id含义index的含义1write attributeSETattribute 索引从 1 开始logical_name是 12execute specific methodACTIONmethod 索引也从 1 开始第一个 specific method 是 1说明raw 原文此处只写了 “write attribute / execute specific method” 两个枚举名数值 1 / 2 是从上面那句with service_id 1/with service_id 2推定出来的原文此处枚举数值在提取时丢失。工程上的对应关系即 1 SET、2 ACTION。index 从 1 开始、attribute 与 method 各有一套编号是最容易写错的地方想写Register activation的active_mask第 4 个属性index就是4想执行Profile generic的第 2 个方法captureindex是2—— 两者互不相干不要混着数。两条 NOTE 同样关键“NOTE 1 — The action_specification is limited to activate methods that do not produce any response (from the server to the client).”脚本只能调用无响应数据的方法。也就是说Profile generic的capturemethod 2可以get_buffer_by_range有返回数据就不行 —— 脚本执行发生在表内没有客户端在等这个返回值。“NOTE 2 — A ‘dummy’ action specification with all elements 0 means that the action is not configured.”所有元素全 0 的 action_specification 该动作未配置不是执行一次空操作。这是标准的占位写法见示例 3。3. 方法execute (data)execute (data) data :: long-unsigned“Executes the script specified in parameter data.”“If data matches one of the script_identifiers in the script table, then the corresponding action_specification is executed.”三条工程要点data是script_identifier不是数组下标。它按编号去scripts里匹配匹配上才执行。写成下标极可能命中另一个脚本编号恰好等于下标时看起来是对的改了配置才发现错位匹配不上则什么都不执行。匹配不上时原文没有规定任何报错语义—— 既没有说返回错误也没有说忽略。工程上必须自己确认主站触发后应读回受影响对象的值做校验不要假设没报错就执行了。谁可以调用它原文明确“A certain script may be activated by other COSEM objects within the same logical device or from the outside.”即表内对象Activity calendar/Schedule/Single action schedule/Register monitor或外部客户端主站发一条ACTION都可以触发。这一句也是安全设计的依据execute是一条远程执行通道必须放在高安全等级后面。同一时刻撞车的处理规则“If two scripts have to be executed at the same time instance, then the one with the smaller index is executed first.”索引较小者先执行。注意这里说的是脚本在scripts数组中的索引Schedule里对 entry 也有同样的低 index 优先规则。跨对象两张Script table之间、Script table与Single action schedule之间的顺序原文未作规定不要依赖。4. 【实战举例】以下示例中的 OBIS、配置取值与报文字节均为帮助理解而构造非蓝皮书原文实际以设备对象列表与厂商文档为准。示例 1一条最经典的脚本 —— 22:00 切谷费率需求每天 22:00把Register activationclass_id 6的active_mask改成 “valley”。Script table logical_name 0-0:10.0.0 示例 scripts[0] { script_identifier 0x0001, actions [ { service_id 1, # write attribute (SET) class_id 6, # Register activation logical_name 0-0:10.0.2, # 目标对象示例 index 4, # attribute 4 active_mask parameter valley } # octet-string ] }SET 该scripts属性的 A-XDR 编码示例01 01 array(1) scripts 02 02 structure(2) script 12 00 01 long-unsigned 0x0001 script_identifier 01 01 array(1) actions 02 05 structure(5) action_specification 16 01 enum 1 service_id SET 12 00 06 long-unsigned 6 class_id 09 06 00 00 0A 00 02 FF octet-string(6) 0-0:10.0.2 logical_name 0F 04 integer 4 index 4 09 06 76 61 6C 6C 65 79 octet-string(6) valley parameter连成一串01 01 02 02 12 00 01 01 01 02 05 16 01 12 00 06 09 06 00 00 0A 00 02 FF 0F 04 09 06 76 61 6C 6C 65 79几个编码要点array的 tag 是0x01、structure是0x02、enum是0x16、integer有符号是0x0F、octet-string是0x09、long-unsigned是0x12。parameter的类型是service specific—— 这里因为目标是active_maskoctet-string所以参数也必须编码成octet-string用visible-stringtag0x0A写进去很多表会直接拒绝。示例 2一个脚本做三件事顺序执行每月结算脚本示例script_identifier 0x0002actions [ ① { service_id 1, class_id 6, logical_name 0-0:10.0.2, index 4, parameter peak } # SET Register activation.active_mask peak → 切回峰费率 ② { service_id 2, class_id 7, logical_name 1-0:99.1.0, index 2, parameter integer 0 } # ACTION Profile generic.capture (method 2)data 0 → 抓一条冻结记录 ③ { service_id 1, class_id 6, logical_name 0-0:10.0.3, index 4, parameter peak_demand } # SET 第二组寄存器激活掩码 → 需量组也跟着切 ]执行时序表内execute(0x0002)→ ① 切回峰费率 → ②capture抓冻结 → ③ 需量组跟着切全程无返回值、无日志。关键推论原文没有规定脚本的事务语义没有全部成功或全部回滚的说法。工程上必须假定可能部分生效—— 若 ① 成功而 ② 失败目标对象不存在、buffer 满表内就停在费率已切、冻结没抓的状态且没有任何属性记录这个失败。所以脚本动作必须按幂等设计见示例 6。示例 3script_identifier 0null script与全 0 的 dummy action两种空的写法含义完全不同# A. 空脚本编号 0 是保留值execute(0) null script什么都不做 scripts[0] { script_identifier 0x0000, actions array(0) } 编码: 01 01 02 02 12 00 00 01 00 ↑ script_identifier 0 ↑ array(0)空的 actions # B. 未配置的动作NOTE 2 的 dummy五个元素全 0 scripts[1] { script_identifier 0x0001, actions [ { service_id 0, class_id 0, logical_name 00 00 00 00 00 00, index 0, parameter 空 } ] }区别A适合这个时段暂时不做事Activity calendar/Schedule里把script_selector填0配置保留但动作空转B适合脚本里某一步留作将来扩展—— 占位但不执行。注意原文只说 “all elements 0”未规定parameter的零值该编码成什么null-data / 空octet-string/ 目标类型的零值实际以厂商实现为准。别把 B 误当成执行一次无害的写 0 操作—— 它的语义是这一步没配。示例 4谁在触发它 —— 四个调用方Script table从不自己醒来它总是被别人调用。蓝皮书里至少四处引用它接口形式完全一致OBIS 编号的软引用触发方class_id怎么引用脚本Activity calendar20day_profile_action { start_time, script_logical_name, script_selector }Schedule10schedule_table_entry { …, script_logical_name, script_selector, … }Single action schedule22executed_script { script_logical_name, script_selector }Register monitor21阈值越限时执行脚本原文“a set of scripts … that are executed when the value monitored crosses a threshold”外部客户端—主站直接ACTION execute(data)Activity calendar的 Overview 一句话把关系说死了蓝皮书原文Activity calendar, Overview“The ‘Activity calendar’ object defines the activation of certain scripts, which can perform different activities inside the logical device. The interface to the IC ‘Script table’ is the same as for the IC ‘Schedule’.”蓝皮书原文Activity calendar, day_profile_action“script_logical_name: defines the logical_name of the ‘Script table’ object; script_selector: defines the script_identifier of the script to be executed.”于是完整的费率切换链条是结合第 6 篇Clock (8) ── 提供现在几点 ↓ Activity calendar (20) ── day_profile 里start_time 22:00script_selector 1 ↓ 匹配 script_identifier Script table (9) ── execute(1) → 逐条执行 actions ↓ Register activation (6) ── active_mask 被 SET 成 valley → 费率寄存器组切换职责分离得非常干净Register activation只管当前选谁Activity calendar只管什么时候切Script table只管怎么切。三者可独立配置这也是 COSEM 对象模型的精髓。示例 5主站从外部触发ACTION execute现场调试或补执行时主站可以直接调executeACTION (class_id 9, logical_name 0-0:10.0.0, method_index 1) data :: long-unsigned A-XDRdata 部分12 00 02 # script_identifier 2示例 2 的结算脚本 # SNshort name寻址时该方法短名为 x 0x20参数同样是 long-unsigned外层 ActionRequest APDU 的封装属 xDLMS 部分此处只展开方法参数。触发后的验证动作version 0 没有executed可查只能间接验GET (9, 0-0:10.0.0, 2)读回scripts确认script_identifier存在GET (6, 0-0:10.0.2, 4)读active_mask确认已改成 “peak”对 ACTION 型动作则读目标对象的状态属性如Profile generic的 entries_in_use / 末条时间戳印证。示例 6改脚本引发的静默失效某现场把费率脚本从script_identifier 1改成2改完发现 22:00 不再切换但没有任何告警改之前Activity calendar.day_profile_action{start_time22:00, script_selector 1} Script table.scripts [ {id 1, actions [SET active_mask valley]} ] ✔ 命中 改之后Script table.scripts [ {id 2, actions […]} ] ✘ 1 匹配不上 Activity calendar 仍然持有 script_selector 1 → 22:00 到了execute(1)无匹配 → 什么都不执行不报错根因调用方存的是script_logical_namescript_selector两个软引用不是指针删改脚本不会产生任何一致性检查。工程对策三条改脚本时只覆盖actions不要动script_identifier—— 编号一旦投入使用就当主键用。确需增删脚本时同步检查所有引用方Activity calendar的 day_profile、Schedule的 entries、Single action schedule、Register monitor。脚本动作写成幂等的用SET绝对值“active_mask valley”不要用切换/取反这类依赖当前状态的动作 —— 因为重复执行掉电恢复、时间回拨在Schedule里是被允许的正常行为见下期。5. 工程上容易踩的坑把execute的data当数组下标它是long-unsigned的script_identifier靠匹配定位脚本。匹配不上原文未规定报错行为 —— 表现为到点了什么都没发生且无从查证。script_identifier 0是保留值填 0 得到 null script“no actions to perform”静默空转。未配置要用 0而不是留空数组或填 9999。index的两套编号混着数attribute 与 method 各自从1开始logical_name是 attribute 1、第一个 specific method 是 method 1。写SET时把业务属性序号当index用会写到完全错误的属性上而SET通常不报错。想在脚本里读数据service_id只有 write attribute 与 execute specific method 两种没有 read且 NOTE 1 限定只能调用不产生响应的方法capture可以get_buffer_by_range不行。dummy全 0当成无害空操作NOTE 2 明确它的语义是该动作未配置。反过来未使用的动作位应留成全 0别填class_id 1/index 2之类的占位值—— 那会真的去写某个对象。软引用脱节Activity calendar/Schedule/Single action schedule里存的是 OBIS script_selector改脚本不会触发任何一致性校验失效是静默的。假设脚本是原子的、且能查执行结果原文未规定事务/回滚语义Script tableversion 0也没有记录执行结果的属性本卷 raw 未提供executed。必须按可能部分生效 无据可查来设计动作写成幂等的。parameter类型与权限parameter是 service specific必须与目标 attribute / method 的data类型严格一致active_mask是octet-stringreset的data是integer另外scripts可写 execute可外部调用“or from the outside”等于给客户端一条远程执行通道必须置于最高安全等级之下。6. 小结 下期预告本篇要点Script tableclass_id 9, version 0是表内可配置的动作清单宏Clock决定何时它决定做什么只有 2 个属性 ——logical_namestatic、scriptsstaticscripts是三层结构array script→script{script_identifier: long-unsigned, actions: array action_specification}一条action_specification五要素service_id1 write attribute / 2 execute specific method、class_id、logical_name目标对象的 OBIS、indexattribute / method 各从 1 起、parameterservice specificscript_identifier 0保留null script全 0 的 action_specification 未配置NOTE 2只能调用无响应的方法NOTE 1executem唯一方法的data是script_identifier靠匹配执行可由表内对象或外部客户端触发同一时刻撞车时较小 index 者优先原文未提供executed属性、也未规定事务语义—— 按可能部分生效、无执行记录、需要幂等来做工程假设。下一篇第 10 篇Scheduleclass_id 10—— 本篇的搭档。Script table只回答做什么在什么时候、按什么周期执行由Schedule回答它的entries数组里每个schedule_table_entry都有index、enable、script_logical_name、script_selector、switch_time、validity_window、exec_weekdaysbit-string周一到周日、exec_specdays关联Special days table的 day_id、begin_date/end_date还有enable/disable、insert、delete三个方法。我们还会讲它最硬核的两块工程逻辑掉电恢复后如何补执行丢失的条目以及时间被向前/向后设置、时间同步、夏令时切换这四种时间变更分别该怎么处理。第 6 篇讲费率切换时提到的Activity calendar触发Script table在下下篇会完整串起来。参考资料DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》Script table (class_id 9, version 0) 章节以及 Activity calendar (class_id 20) 章节中day_profile_action/ Overview 关于脚本引用的描述。文中 2 个属性的名称、static 标记、数据类型、Short name 偏移、execute方法的 m/o 与data类型、以及全部英文引文均与原文一致示例中的 OBIS0-0:10.0.0/0-0:10.0.2/1-0:99.1.0、配置取值与报文字节为帮助理解而构造实际以设备对象列表与厂商文档为准。
返回列表