欢迎光临
我们一直在努力

DLMS/COSEM 蓝皮书解读(十一):Special days table 类(class_id = 11)—— 节假日如何覆盖常规日程:费率日历的“补丁表“

DLMS/COSEM 蓝皮书解读(十一):Special days table 类(class_id = 11)—— 节假日如何覆盖常规日程:费率日历的"补丁表"

系列说明:本系列基于 DLMS UA《Blue Book(蓝皮书)第 16 版 · 第 2 部分 —— COSEM interface classes》,一个接口类一篇。第 10 篇讲了 Schedule(class_id = 10),它解决的是"脚本在什么时候、按什么周期执行"。本篇的 Special days table 解决的是紧随其后的那个洞:节假日 / 例外日如何覆盖常规日程 —— 春节、国庆这类日子不能按普通周末 / 工作日的规则走,需要一张"特殊日表"来给日历打补丁。

上篇回顾:Schedule 是一张表内的 cron 表,靠 switch_time 通配符 + exec_weekdays + begin_date/end_date 表达了"每周一到周五 22:00 切谷"这类周期性规则。但它有一个结构性盲区:它只认"星期几",不认"几月几日"。2026 年春节是 2 月 17 日,星期二 —— 按 exec_weekdays 它是一个普通工作日,可业务上它必须按假日费率走。Schedule 自己在 exec_specdays 里留了一个 bit-string 接口,但那个接口指向的规则表,正是本篇的 Special days table。

(版本说明:本卷 raw 原文中 Special days table 只有 version = 0 一个版本,故本篇不设版本对比章节。)


0. 为什么必须要有 Special days table

先算一笔账。某地峰谷电价:峰(08:00–11:00、18:00–21:00)、平、谷(22:00–次日 06:00)。表内靠 Activity calendar 的 week_profile 表达:周一至周五用 “工作日 day_profile”,周六周日用 “假日 day_profile”。

现在把 2026 年的日历摊开看:

日期实际星期业务上应该是只靠 week_profile 的结果
2026-01-01(元旦) 星期四 假日费率 ❌ 按工作日计费
2026-02-17(春节) 星期二 假日费率 ❌ 按工作日计费
2026-05-01(劳动节) 星期五 假日费率 ❌ 按工作日计费
2026-10-01(国庆) 星期四 假日费率 ❌ 按工作日计费

四个法定节假日,没有一个落在周末。 这不是巧合 —— 全世界的法定假日绝大多数是"某月某日",而不是"某星期几",两套体系天生对不上。少了 Special days table,会出现三件很难收场的事:

  • 电费算错且不可逆 —— 节假日那几天的峰段电量被记进峰寄存器,账单已出,事后补差价是商务纠纷不是技术修补;
  • 只能靠主站实时下发救火 —— 元旦 00:00 主站给全城表计各发一条 SET 切假日费率。可表计离线、通道拥塞、下发到一半断线……漏掉的表就按工作日费率跑一整天(第 9 篇讲 Script table 时已经论证过"主站实时下发"这条路为什么走不通);
  • 改一次就要动固件 —— 如果把节假日硬编码进固件,每年的放假安排都不一样(2026 年春节 2/17,2027 年春节 2/6),等于每年重新送检一次。
  • Special days table 的思路与前两篇一脉相承:把"例外"也外置成数据。一张 entries 表放在 attribute 里,表内固件只有一个通用引擎(今天是不是特殊日?是 → 取那个 day_id → 用它覆盖常规 day profile),改节假日 = 一次 SET / 一次 insert,不动固件,也不依赖主站在线。

    蓝皮书原文(Special days table, Overview):
    “This IC allows defining special dates. On such dates, a special switching behaviour overrides the normal one. The IC works in conjunction with the class ‘Schedule’ or ‘Activity calendar’. The linking data item is day_id.”

    三句话,把这个类的全部定位说完了:① 它定义特殊日期;② 特殊日上,“特殊切换行为覆盖(overrides)常规行为”;③ 它与 Schedule 或 Activity calendar 协同工作,链接两者的那个数据项叫 day_id。

    一句话定位:

    Special days table = 日历的补丁表。它自己不执行任何动作,只回答一个问题:“今天是不是特殊日?如果是,用几号 day profile / 置位哪个 specday bit?”

    三个"日历类"怎么分工

    类class_id回答什么关键属性
    Schedule 10 什么时刻、星期几、哪段日期区间,去执行哪个脚本 entries(switch_time / exec_weekdays / exec_specdays / begin_date…)
    Special days table 11 哪一天是例外?例外日用哪个 day_id? entries(index / specialday_date / day_id)
    Activity calendar 20 季节 → 周 → 日,层层嵌套的完整费率日历 season_profile / week_profile_table / day_profile_table

    注意 Special days table 与 Activity calendar 是两条不同的覆盖链路(见示例 3 与示例 4),很多"配了特殊日却不生效"的工单,根源就是没分清自己在配哪一条。


    1. 类蓝图

    Special days table 0…n class_id = 11, version = 0

    基数 0…n:一台设备可以有多个 Special days table 实例(例如"法定节假日一张"“检修日一张”),也可以一个都没有 —— 没有实例时,日历就完全按常规 week_profile / exec_weekdays 走,这本身是合法配置。

    属性静态/动态数据类型MinMaxDefShort name
    logical_name (static) octet-string x
    entries (static) array x + 0x08
    方法必选/可选(m/o)Short name
    insert (data) o x + 0x10
    delete (data) o x + 0x18

    注 1:原文未给 Min / Max / Def 三列任何取值,故留空。
    注 2:两个属性全是 (static) —— static 表示"值不会由设备自发改变",不等于不可写;entries 恰恰是主站改写最频繁的属性之一(每年都要更新)。
    注 3:两个方法都是 o(optional)。表计可以完全不实现任何方法,只把 entries 当成一个可整块 SET 的配置数组。调用 insert / delete 前必须先确认设备是否支持。
    注 4:本类的方法 Short name 从 x + 0x10 起步,紧贴 entries 的 x + 0x08;对比第 10 篇的 Schedule(属性到 x + 0x08,方法却从 x + 0x20 起步,中间 0x10/0x18 是空的)—— Short name 偏移不能跨类按固定步长推算,必须查表。

    只有 2 个属性、2 个方法,是全集里最"轻"的类之一。全部复杂度都藏在 entries 这一个 array 里。


    2. 属性逐条解读

    2.1 logical_name(static, octet-string)

    “Identifies the ‘Special days table’ object instance.”

    6 字节 OBIS 编码。工程上常见取值形如 0-0:11.0.0.255(示例值,非蓝皮书原文,实际以设备对象列表为准):A 组 = 0 属于"抽象 / 公用"对象,与相邻几个日历类连号排布(Script table 0-0:10.0.0.255、Schedule 0-0:12.0.0.255、Special days table 0-0:11.0.0.255、Activity calendar 0-0:13.0.0.255)。

    关键提醒:本对象的 logical_name 不会被任何类的属性引用。第 10 篇的 Schedule 是通过 exec_specdays 的 bit 位间接关联到 day_id 的,Activity calendar 也是通过 day_id 关联 —— 两边都只认 day_id,不认这张表是谁。这是典型的"软引用",改错 day_id 不会触发任何一致性检查(详见第 5 节坑 3)。

    2.2 entries(static, array)—— 本篇的全部内容

    蓝皮书原文(Special days table, entries):
    “Specifies a special day identifier for a given date. The date may have wildcards for repeating special days like Christmas.”

    “array spec_day_entry”

    两句话两层意思:entries 的每个条目 = “给某个日期指定一个特殊日标识”;日期字段允许通配符,用来表达"每年圣诞"这类重复节日 —— 这一句直接决定了本类的使用边界(见示例 2)。

    原文给出的 structure 定义,三个字段,一个都不少:

    spec_day_entry ::= structure
    {
    index: long-unsigned,
    specialday_date: octet-string,
    day_id: unsigned
    }

    注:部分资料把这个字段写作 special_day_date;本卷 raw 原文的拼写是 specialday_date(无第二个下划线),本文统一按原文。

    字段类型干什么用
    index long-unsigned 条目主键(不是数组下标!),delete 与"覆盖判定"都按它
    specialday_date octet-string 特殊日的日期,5 字节,按 4.6.1 的 date 规则,允许通配符
    day_id unsigned 链接数据项:对应 Activity calendar 的 day_profile,或 Schedule 的 exec_specdays bit 位
    specialday_date:5 字节 date 与通配符

    “specialday_date formatting follows the rules set in 4.6.1 for date;”

    按第 8 篇讲过的 date 编码(5 字节 octet-string):year_hi | year_lo | month | day_of_month | day_of_week。年份写 0xFFFF = not specified = 每年。

    specialday_date(5 字节)含义生效次数
    FF FF 01 01 FF 每年 1 月 1 日(元旦) 每年,永久
    FF FF 0A 01 FF 每年 10 月 1 日(国庆) 每年,永久
    FF FF 0C 19 FF 每年 12 月 25 日(圣诞) 每年,永久
    07 EA 02 11 02 2026-02-17(周二,春节) 仅 2026 年一次
    07 EA 0A 01 04 2026-10-01(周四) 仅 2026 年一次

    年份 0x07EA = 2026;月 0x02 = 2 月;日 0x11 = 17 日;星期 0x02 = 周二(4.6.1 规定 day_of_week 为 1…7,1 = Monday,0xFF = not specified)。

    "每年重复"与"一次性"的分水岭就是年份那两个字节 —— 这一点与第 10 篇 Schedule 的 begin_date / end_date 完全同源:写 FF FF 是周期性规则,写具体年份是"到点就过期"。而 Special days table 的残酷之处在于:农历节日(春节、中秋、清明)每年日期都不同,通配符表达不了,只能逐年下发具体日期(详见示例 2 与坑 1)。

    day_id:整个类的"链接数据项"

    蓝皮书原文(Special days table, entries, Where):
    “the range of the day_id shall match the length of the bit-string exec_specdays in the related object of IC ‘Schedule’.”

    这是本类唯一一条硬性约束,也是最常见的故障点:day_id 的取值范围必须与相关 Schedule 对象里 exec_specdays 这个 bit-string 的长度一致。

    工程上怎么落地(示例,非原文):若 Schedule 的 exec_specdays 是 9 bit(对应 S1…S9),那么 day_id 只能取 1…9 —— 你写一个 day_id = 12,那个 bit 根本不存在,特殊日静默失效,没有任何报错。

    原文未明确规定的两点(不要当成规格):① day_id 的下界(是否从 1 开始、0 是否保留);② 第 n 个 bit 与 day_id = n 的位序映射(S1 占字节最高位还是最低位)。本系列第 10 篇 exec_weekdays 采用"第 1 个 bit = 最高位"(Mo = 0x40),exec_specdays 按同例类推 —— 实际请以设备对象列表与厂商文档为准,并在实验表上验证。

    day_id 的另一半语义在 Activity calendar 那边(见示例 3),本卷 Special days table 原文只给了 day_id: unsigned 和上面那条约束,没有展开它在 Activity calendar 侧的用法。


    3. 方法详解(两个,全是 o)

    3.1 insert (data)

    “Inserts a new entry in the table.”

    “entry ::= spec_day_entry”

    “If a special day with the same index or with the same date as an already defined day is inserted, the old entry will be overwritten.”

    data 就是一个完整的 spec_day_entry。重点在第三句:覆盖条件是"二选一" —— index 相同或**specialday_date 相同**,旧条目都会被覆盖。**

    这与第 10 篇 Schedule.insert 的语义不一样:Schedule 只说 “If the index of the entry exists already, the existing entry is overwritten” —— 只按 index 判定。而本类多了一个"日期相同也算撞车"的条件,后果是:

    已存在:{ index = 5, specialday_date = 2026-10-01, day_id = 9 } # 国庆 → 假日
    新下发:{ index = 6, specialday_date = 2026-10-01, day_id = 1 } # 手滑写错 day_id
    结果 :index = 5 的条目被静默覆盖(删除),表里只剩 index = 6 这一条

    也就是说 insert 实际上是 upsert,而且 upsert 的键有两个。批量下发节假日表时,"同一天被写了两次"不会报错,只会留下最后一条(见示例 6)。

    3.2 delete (data)

    “Deletes an entry in the table.”

    “index Index of the entry that shall be deleted.”

    “data ::= long-unsigned”

    三点:

    • data 是单个 long-unsigned(一个 index 值),不是区间。对比 Schedule.delete(data ::= structure { firstIndex, lastIndex },按区间删)—— 这两个同名方法参数完全不同,别把 Schedule 的写法套过来。
    • 按 index 匹配,不是数组下标。删掉中间一条后其余条目的 index 不变。
    • 原文没有给出"删除不存在的 index 时是否报错"的规定 —— 原文未提供该项。

    3.3 没有 enable/disable:与相邻类的能力对比

    类方法备注
    Schedule(10) enable/disable(按区间关 A 开 B)、insert、delete(按区间) 专为季节切换设计
    Special days table(11) insert(单条,双条件覆盖)、delete(单个 index) 无 enable/disable
    Activity calendar(20) activate_passive_calendar(passive → active 一次性整体切换) 靠"双份日历 + 定时激活"实现无缝换季

    工程含义很直接:想临时停用一个特殊日,只能 delete 掉它,或者把整张 entries 读回来改完再整块 SET 回去(后者在两个方法都不支持时是唯一退路)。另外,原文未说明 insert / delete 之后是否立即生效、是否需要重启 —— 原文未提供该项,实践中建议 insert 后立刻 GET entries 读回校验。


    4. 【实战举例】

    以下示例中的 OBIS、配置取值与报文字节均为帮助理解而构造,非蓝皮书原文,实际以设备对象列表与厂商文档为准。编码按 4.6.1 的 date 规则与 A-XDR 规则(structure = 0x02、array = 0x01、octet-string = 0x09、long-unsigned = 0x12、unsigned = 0x11、bit-string = 0x04)构造。

    示例 1:一条 entry 的完整构造与 A-XDR —— 2026 年春节

    需求:2026-02-17(周二,春节)不走工作日费率,改用 day_id = 9 的假日 day profile。

    Special days table logical_name = 0-0:11.0.0.255 (示例)
    entries[0] = {
    index = 1, # 主键(不是数组下标)
    specialday_date = 07 EA 02 11 02, # 2026-02-17,周二
    day_id = 9 # → 假日 day_profile / S9
    }

    SET 该 entries(单条)的 A-XDR(示例):

    01 01 array(1) entries
    02 03 structure(3) spec_day_entry
    12 00 01 long-unsigned 1 index
    09 05 07 EA 02 11 02 octet-string(5) 2026-02-17 specialday_date
    11 09 unsigned 9 day_id

    等价的增量写法(若设备实现了 insert):

    ACTION (class_id = 11, logical_name = 0-0:11.0.0.255, method_index = 1) # insert
    data ::= spec_day_entry = { index = 1, specialday_date = 07 EA 02 11 02, day_id = 9 }

    对比第 10 篇 Schedule 的 10 字段 entry(20 多个字节),本类一个条目只有 12 个字节 —— 一张 30 天的节假日表也就 360 字节左右,完全可以整块 SET,这是它设计成这么轻的原因。

    示例 2:国庆能用通配符,春节不能 —— 农历节日的硬约束

    同一个"法定节假日"需求,两种日期写法,语义天差地别(示例):

    # A. 公历固定日 → 通配符,一次下发永久有效
    { index = 10, specialday_date = FF FF 0A 01 FF, day_id = 9 } # 每年 10-01 国庆
    { index = 11, specialday_date = FF FF 01 01 FF, day_id = 9 } # 每年 01-01 元旦
    { index = 12, specialday_date = FF FF 05 01 FF, day_id = 9 } # 每年 05-01 劳动节

    # B. 农历节日 → 每年日期不同,只能逐年写具体日期
    { index = 20, specialday_date = 07 EA 02 11 02, day_id = 9 } # 2026-02-17 春节(周二)
    # 2027 年春节是 2 月 6 日(周六),必须再下发一条:
    { index = 21, specialday_date = 07 EB 02 06 06, day_id = 9 } # 2027-02-06(0x07EB = 2027)

    day_of_month 通配符(0xFD = 倒数第二天、0xFE = 最后一天)能表达"每月最后一天",但表达不了"农历正月初一" —— 4.6.1 的 date 是公历结构,没有农历字段。

    公历节日(元旦 / 劳动节 / 国庆 / 圣诞)→ 通配符,一次配置多年;农历与浮动节日(春节 / 中秋 / 清明 / 复活节 / 感恩节)→ 必须逐年下发具体日期。 这条结论直接决定了跨年维护的工作量(见示例 6)。

    示例 3:day_id → Activity calendar 的 day_profile(覆盖链路之一)

    这是 Special days table 最正统的用法。原文对优先级的描述写在 Activity calendar 那一章:

    蓝皮书原文(Activity calendar, Overview):
    “If an instance of the IC ‘Special days table’ is available, relevant entries there take precedence over the ‘Activity calendar’ object driven selection of a day profile. The day profile referenced in the ‘Special days table’ activates the day_schedule of the day_profile_table in the ‘Activity calendar’ object by referencing through the day_id.”

    翻成工程语言:如果设备里有 Special days table 实例,且今天命中了其中一条,那么它的选择优先于 Activity calendar 自己按 week_profile 选出来的 day profile;而它引用的那个 day profile,正是通过 day_id 在 Activity calendar 的 day_profile_table 里查到的。

    一组完整配置(示例):

    Activity calendar (0-0:13.0.0.255) (示例 OBIS)
    day_profile_table_active:
    day_id = 8 → day_schedule: # 工作日
    06:00 → Script(0-0:10.0.100.255, #1) # 切峰
    22:00 → Script(0-0:10.0.100.255, #2) # 切谷
    day_id = 9 → day_schedule: # 假日(全天谷)
    00:00 → Script(0-0:10.0.100.255, #2) # 切谷
    week_profile_table_active:
    "WORK-WEEK" → { mon=8, tue=8, wed=8, thu=8, fri=8, sat=9, sun=9 }

    Special days table (0-0:11.0.0.255)
    { index = 1, specialday_date = 07 EA 02 11 02, day_id = 9 } # 2026-02-17 春节 → 假日
    { index = 10, specialday_date = FF FF 0A 01 FF, day_id = 9 } # 每年国庆 → 假日

    2026-02-17 那天:week_profile 说"周二 → day_id = 8",但 Special days table 命中并覆盖为 day_id = 9 → 走假日 day_schedule → 全天谷费率。注意这里 Schedule 的 exec_specdays 完全不参与 —— 这是 Activity calendar 侧的覆盖。

    示例 4:day_id ↔ Schedule.exec_specdays(覆盖链路之二)

    另一条链路是给 Schedule 条目用的。第 10 篇留的接口在这里接上:

    Special days table (0-0:11.0.0.255)
    { index = 1, specialday_date = 07 EA 02 11 02, day_id = 9 } # 春节 → S9
    { index = 2, specialday_date = FF FF 0C 19 FF, day_id = 1 } # 每年圣诞 → S1
    … day_id 取值 1…9,共 9 个

    Schedule (0-0:12.0.0.255)
    entry { index = 300,
    switch_time = 12:00,
    exec_weekdays = 04 07 01, # 7 bit,仅周日(0x01 = Su)
    exec_specdays = 04 09 00 80 } # 9 bit,S9 置位(第 9 位)

    exec_specdays 的 bit 数必须等于 day_id 的取值范围长度:表里有 S1…S9 → 9 bit → 2 字节(04 09 xx xx);若表里只有 S1…S4 → 4 bit → 1 字节(04 04 x0)。长度写错(写成 7 bit)时,S8 / S9 永远命中不了,现象是"特殊日不生效"且极难排查。

    另一条来自 Activity calendar 原文的排序规则值得记住:

    “If actions in a ‘Schedule’ object are scheduled for the same activation time as in an ‘Activity calendar’ object, the actions triggered by the ‘Schedule’ object are executed first.”

    即同一时刻 Schedule 的动作先于 Activity calendar 的动作执行。所以如果"特殊日切换"和"常规整点切换"撞在同一分钟,顺序是固定的,别指望靠配两次来互相纠正。

    示例 5:完整协作时序 —— 从 Clock 到费率寄存器

    把第 6 篇(费率切换链路)、第 9 篇(Script table)、第 10 篇(Schedule)、本篇串起来,2026-02-17 春节这一天表内发生了什么(示例):

    [0] 表内时钟 (Clock, 8):2026-02-16 23:59:59 → 跨日
    [1] Activity calendar (20):按 season_profile → week_profile 取 "周二 → day_id = 8"
    [2] Special days table (11):查 specialday_date == 2026-02-17 → 命中 index=1 → day_id = 9
    └─ 覆盖(override)上一步的 day_id = 8
    [3] 选中 day_profile 9 的 day_schedule(假日:00:00 切谷)
    [4] 到达激活时刻 → Script table (9).execute(#2)
    └─ 脚本内容:SET Register activation (6).active_mask = "holiday"
    [5] Register activation (6).active_mask = "holiday" → 谷寄存器 1-0:1.8.3.255 开始累加
    → 结果:2026-02-17 全天电量计入谷费率寄存器,而不是峰/平寄存器

    同一天里,Schedule 若也有一条 exec_specdays 命中 S9 的条目(示例 4 的 index = 300),它会在 12:00 再触发一次自己的脚本 —— 两条链路各走各的,day_id 是它们共同的锚点。工程上务必只选一条链路配置同一个动作,否则就是"费率被切两次"。

    示例 6:跨年下发 —— 时序、条数与那个"同 date 覆盖"陷阱

    真实运维节奏(示例):每年 12 月中旬,主站要把次年的特殊日表下发到全网表计。

    T0 2026-12-10 主站生成 2027 年表:12 条(元旦/春节/清明/劳动/端午/中秋/国庆…)
    T1 2026-12-15 批量 ACTION insert ×12(或整块 SET entries)
    ⚠ 每条下发后 GET entries 读回校验条数与 day_id
    T2 2026-12-31 抽查:随机抽 1% 表计,确认 entries 里 2027 年条目齐全
    T3 2027-01-01 表计自动切换(无需主站在线)

    三个必须记住的点:

  • 必须提前下发。如果等到 2027-01-05 才下发,1/1–1/4 已经按工作日费率计费,这四天的电量已经记进峰/平寄存器,不可逆。这是节假日配置里唯一无法事后补救的错误。
  • 旧条目不会自动清理。specialday_date 写了具体年份的一次性条目,过期后仍然占着 entries 的位置(原文未提供自动过期机制)。三年不清理,表里就堆了三十多条废条目,既占空间又容易被 insert 的"同 index 覆盖"误伤 —— 建议每年下发前先 delete 掉上一年的一次性条目。
  • insert 的"同 date 覆盖"。下发 2027 年春节时若先写了一条 index = 21, date = 2027-02-06, day_id = 1(day_id 写错),随后又补发 index = 22, date = 2027-02-06, day_id = 9,按原文 “with the same date … the old entry will be overwritten”,结果是 index = 21 被删掉、只留 index = 22 —— 看起来"补发成功了",实际上前一条已经静默消失。批量下发时务必保证同一日期只出现一次,并在下发后读回核对 index 集合。

  • 5. 工程上容易踩的坑

  • 通配符的两个方向都容易写错:① 想用通配符表达农历 / 浮动节日 —— specialday_date 是公历 date 结构,FF FF 只能通配年份,通配不了"农历正月初一",春节 / 中秋 / 清明 / 复活节这类浮动节日必须逐年下发具体日期,且年底就要把次年的表配好(见示例 6);② 反向错误 —— 元旦写成 07 EA 01 01 04 指望它年年生效(到 2027 年静默失效),春节写成 FF FF 02 11 FF 则会每年 2 月 17 日都当春节(2027 年那天是周三,根本不是节日)。两类错误都不会有任何告警。
  • day_id 与 exec_specdays 的 bit 长度不匹配:原文唯一硬约束(“the range of the day_id shall match the length of the bit-string exec_specdays”)。表里有 S1…S9 而 exec_specdays 只写 7 bit,S8 / S9 永远命中不了 —— 表现为"特殊日不生效",且没有任何报错。
  • day_id 与 Activity calendar 的 day_profile_table 对不上:day_id 是软引用,Activity calendar 里根本不存在 day_id = 9 的 day profile 时,配置照样能写进去,只是到了那天什么都不发生。下发前先 GET 一次 day_profile_table_active 核对 day_id 全集。
  • insert 是双条件 upsert:index 相同或specialday_date 相同都会覆盖旧条目(与 Schedule 只按 index 不同)。批量下发时"同一天写了两次"不会报错,只会留下最后一条 —— 下发后必须读回核对 index 集合。
  • delete 的 data 是单个 long-unsigned:不是 Schedule 那种 {firstIndex, lastIndex} 区间结构。把 Schedule 的参数格式套过来会得到参数类型错误。
  • 两个方法都是 o(可选):不要假设 insert / delete 存在。不支持时退化为"整块 SET entries",但必须先 GET 读回原值再改,否则会把现场正在用的条目整表覆盖掉。
  • 搞混两条覆盖链路:Activity calendar 侧是"特殊日覆盖 day profile 的选择"(示例 3),Schedule 侧是"exec_specdays 位命中触发条目"(示例 4)。同一个切换动作在两条链路上各配一次 → 费率被切两次。另外同一时刻 Schedule 的动作先于 Activity calendar 执行(“the actions triggered by the ‘Schedule’ object are executed first”),别指望靠配两次互相纠正。
  • day_of_week 字节随手填:4.6.1 规定 day_of_week 为 1…7(1 = Monday)、0xFF = not specified;原文未给出该字节与实际星期不符时的比对规则。保守做法是统一填 0xFF,或按真实星期填写后在实验表上验证,不要凭猜测。

  • 6. 小结 & 下期预告

    本篇要点:

  • Special days table(class_id = 11, version = 0)是日历的补丁表:它不执行任何动作,只回答"今天是不是特殊日?是的话用哪个 day_id";
  • 只有 2 个属性 —— logical_name(static, octet-string)、entries(static, array spec_day_entry),entry 三个字段:index(long-unsigned,业务主键)/ specialday_date(octet-string,5 字节 date,支持通配符)/ day_id(unsigned,链接数据项);
  • day_id 是唯一硬约束的承载者:“the range of the day_id shall match the length of the bit-string exec_specdays”,同时它也是 Activity calendar 里 day_profile_table 的查找键;
  • 通配符只能表达公历固定日(元旦 / 国庆 / 圣诞 → FF FF 年份),农历与浮动节日必须逐年下发具体日期 —— 这是跨年维护工作量的根源;
  • 两个方法 insert(双条件 upsert:index 相同或 date 相同都覆盖)与 delete(单个 long-unsigned,不是区间)都是 o;本类没有 enable/disable;
  • 特殊日对常规日程是**覆盖(override)**关系:“a special switching behaviour overrides the normal one”,但它与 Schedule 的 exec_specdays 是两条独立链路,同一个动作不要配两处。
  • 下一篇(第 12 篇):Association SN(class_id = 12)—— 前 11 篇讲的都是"数据对象",从下一篇起换一个维度:在 SN(short name)寻址方式下,"会话"本身如何建模。一台设备可以为它能支持的每一个 AA(application association)各建一个 Association SN 实例,object_list / access_rights_list / security_setup_reference / user_list / current_user 等属性定义了"这个客户端能看到什么、能做什么"。这个类有 4 个 version(0 / 1 / 2 / 4),我们会按版本分节,逐版说明新增了哪些属性与方法(read_by_logicalname、change_secret、reply_to_HLS_authentication、add_user、remove_user 等),并解释为什么中间跳过了 version = 3。


    参考资料:DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》,Special days table (class_id = 11, version = 0) 章节(含 Overview、Attribute description 与 spec_day_entry 定义、Method description),以及 Activity calendar (class_id = 20, version = 0) 章节 Overview 中关于 Special days table 优先级与 day_id 引用关系的描述、Schedule (class_id = 10) 章节中 exec_specdays 的相关描述。文中 2 个属性的名称、static 标记、数据类型、Short name 偏移、2 个方法的名称与 m/o、Short name 偏移、以及全部英文引文均与原文一致;原文未提供的项(Min/Max/Def 取值、insert/delete 后的生效时机、删除不存在 index 的行为、day_id 下界与 bit 位序映射、条目自动过期机制)已如实标注。示例中的 OBIS(0-0:12.0.0.255 / 0-0:11.0.0.255 / 0-0:10.0.0.255 / 0-0:13.0.0.255)、day_id 取值、日期字节与报文字节为帮助理解而构造(实际以设备对象列表与厂商文档为准)。

    赞(0)
    未经允许不得转载:171主机测评 » DLMS/COSEM 蓝皮书解读(十一):Special days table 类(class_id = 11)—— 节假日如何覆盖常规日程:费率日历的“补丁表“
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址