欢迎光临
我们一直在努力

DLMS/COSEM 蓝皮书解读(二):Data 类(class_id = 1)—— 对象模型的“最小完备样本“

DLMS/COSEM 蓝皮书解读(二):Data 类(class_id = 1)—— 对象模型的"最小完备样本"

系列说明:本系列基于 DLMS UA《Blue Book(蓝皮书)第 16 版 · 第 2 部分》,一个接口类一篇。第 1 篇建立了协议、文档、模型三张地图。本篇进入第一个具体的类。

上篇回顾:第 1 篇讲过 —— 计量设备 = 一堆 COSEM 对象的集合,每个对象是某个接口类的实例,靠 class_id + logical_name + 属性索引定位。本篇就用最简单的类,把这个抽象说法落成具体的样子。


0. 为什么从 Data 类讲起

Data 是蓝皮书里 class_id 最小(= 1)、结构最简单 的接口类:两个属性、零个方法,原文定义不到一页。

但千万别因为它简单就跳过 —— 它是理解整个对象模型的"最小完备样本":把 Data 吃透,你就掌握了四件后面反复用到的事:

  • 一个 COSEM 对象最少由哪些部分组成(为什么 logical_name 一定排第一);
  • logical_name 背后那套 OBIS 编码(后面 96 个类全在用);
  • value 的 CHOICE 数据类型母表(Register、Profile generic 等都在复用它的子集);
  • 如何划清 Data(纯数据) 与 Register(带量纲的数值) 的边界 —— 这是新手最容易用错的地方。
  • 一句话定位:

    Data = 一个带标准名字的通用数据槽。它不关心数据代表什么物理量、也没有单位 —— 它只负责"用 OBIS 名字标定身份 + 把任意类型的数据装进去"。


    1. 它解决什么问题:表计里"不该有单位"的那些东西

    先看一台真实电表里的两类数据:

    数据例子需要单位吗
    正向有功总电能 1234,5 kWh ✅ 必须(否则主站不知道这是度还是瓦时)
    设备序列号 "ABC20230001" ❌ 不需要(它就是个字符串)
    心跳周期配置 300(秒) ❌ 不需要(配置项,主站知道含义)
    电池欠压标志 TRUE ❌ 不需要(是/否而已)
    固件版本 "FW-v2.3.1" ❌ 不需要

    后四类东西,强行给它们配单位不仅多余,还会造成误解("300 秒"的 scaler_unit 该怎么填?)。Data 就是为这类"没有物理量纲"的数据准备的通用容器。

    蓝皮书原文(Data, Overview):
    “This IC allows modelling various data, such as configuration data and parameters. The data are identified by the attribute logical_name.”

    基数(Cardinality):0…n —— 一个逻辑设备里可以有 0 个或多个 Data 对象,数量不限(有些类是强制必现的,Data 不是)。


    2. 类蓝图

    Data 0…n class_id = 1, version = 0

    属性静态/动态数据类型MinMaxDefShort name
    logical_name (static) octet-string x
    value (原文未标 static,即动态) CHOICE x + 0x08
    方法
    Specific methods 无(该类没有任何方法)

    三点必须记住:

  • logical_name 永远是第 1 个属性(attribute_index = 1),这对所有接口类都成立;
  • value 的 Short name 偏移是 x + 0x08(SN 寻址时用);
  • 方法列表为空 —— Data 没有任何方法。对比 Register(class_id = 3)有一个可选的 reset 方法。

  • 3. 属性一:logical_name(身份证 / OBIS 编码)

    • 类型:octet-string,定长 6 字节
    • 性质:(static) —— 设备自己不会改它
    • 取值:必须符合 OBIS 编码规则

    3.1 OBIS 到底怎么读

    OBIS 文本形式写作 A-B:C.D.E*F,六段(A/B/C/D/E/F)各占 1 字节,正好编进 6 字节的 logical_name:

    段名称含义常见取值
    A 介质(Medium) 计量的是什么能量 0 = 抽象;1 = 电;6 = 热;7 = 气;8 = 水
    B 通道(Channel) 第几个测量通道 0 = 无通道(整体)
    C 数据类别 测量量 / 数据项类型 1 = 有功电能;15 = 有功功率;96 = 厂商/设备标识类;0 = 抽象对象
    D 类型 / 处理方法 累计?瞬时?最大值? 8 = 累计值;7 = 瞬时值;2 = 版本类
    E 费率(Tariff) 属于哪个费率 0 = 总;1/2/3 = 费率 1/2/3
    F 计费周期 属于哪个结算周期 0 = 当前;255(0xFF)= 不区分

    3.2 【举例】把 1-0:1.8.0 逐字节拆开

    文本:1-0:1.8.0
    │ │ │ │ │
    │ │ │ │ └─ E = 0 → 总(不分费率)
    │ │ │ └─── D = 8 → 累计值
    │ │ └───── C = 1 → 有功电能
    │ └─────── B = 0 → 无通道(整体)
    └───────── A = 1 → 电

    语义:正向有功总电能(+A,累计,不分费率)

    6 字节编码(十六进制):01 00 01 08 00 00
    │ │ │ │ │ └─ F = 0x00
    │ │ │ │ └──── E = 0x00
    │ │ │ └─────── D = 0x08
    │ │ └────────── C = 0x01
    │ └───────────── B = 0x00
    └──────────────── A = 0x01

    再拆一个 Data 类常用的(设备标识):

    文本:0-0:96.1.0
    A=0 → 抽象对象(非某种能源)
    C=96 → 厂商/设备标识类
    D=1 → 设备标识(序列号一类)
    E=0 → 总

    语义:设备序列号(Device ID / 制造序列号)

    6 字节编码:00 00 60 01 00 00
    注意 C = 96 → 十六进制 0x60

    常见的设备标识类 OBIS:0-0:96.1.0、0-b:96.1.0*255 等(具体以设备的对象列表为准)。0x60 = 96。

    3.3 为什么 logical_name 必须排第一

    因为它是对象的主键。蓝皮书原文(Clause 4):

    “The first attribute of any object is the logical_name.”

    任何对象的引用,至少要带 class_id + logical_name。主站拿到一个对象的 logical_name,就能知道"这是什么";再配合 class_id,就知道"它有哪些属性可以读"。


    4. 属性二:value(万能数据槽,CHOICE)

    “Contains the data. The data type depends on the instantiation defined by the logical_name and possibly from the manufacturer. It has to be chosen so, that together with the logical_name, an unambiguous interpretation is possible. Any simple and complex data types listed in … can be used, unless the choice is restricted.”

    三层含义:

  • 装什么类型由 logical_name(这个对象是什么)+ 厂商选择决定;
  • 选择原则是 —— 类型 + logical_name 组合起来必须能唯一解释这段数据(不能产生歧义);
  • Data 允许使用简单类型 AND 复杂类型(除非条款另有限制)—— 这一点很关键,见 4.2。
  • 4.1 简单数据类型(含 A-XDR tag)

    数据类型Tag说明典型用途
    null-data [0] 空值 / 占位 值不可用
    boolean [3] TRUE / FALSE 标志位(Data 专属优势)
    bit-string [4] 位串 状态位集合
    double-long [5] 32 位有符号
    double-long-unsigned [6] 32 位无符号 大累计量
    octet-string [9] 字节串 二进制、签名、MAC 地址
    visible-string [10] ASCII 可见字符串 序列号、固件版本
    utf8-string [12] UTF-8 字符串 含中文/多字节文本
    bcd [13] BCD 编码数 老式表计数值
    integer [15] 8 位有符号
    long [16] 16 位有符号
    unsigned [17] 8 位无符号 小配置项
    long-unsigned [18] 16 位无符号 常用配置值
    long64 [20] 64 位有符号
    long64-unsigned [21] 64 位无符号
    enum [22] 枚举 状态、模式
    float32 [23] 32 位浮点
    float64 [24] 64 位浮点
    date-time [25] 日期时间 时间戳(Data 专属优势)
    date [26] 日期
    time [27] 时间
    delta-integer … delta-double-long-unsigned [28]–[33] 差分编码系列 压缩存储

    4.2 复杂数据类型 ⭐(Data 相对 Register 的关键优势)

    数据类型Tag说明
    array [1] 数组(同类型元素序列)
    structure [2] 结构(字段可不同类型、可嵌套)
    compact-array [19] 紧凑数组(类型一致时省 tag)

    这意味着:一个 Data 对象可以把一整条"配置结构"整体存进来。而 Register 的 value CHOICE 里没有这些复杂类型(见第 5 节对比)。

    4.3 与 Register 的类型范围对比(重要)

    把 Data(本篇)与 Register(第 3 篇)的 value CHOICE 放在一起看:

    类型Data (1)Register (3)
    null-data / bit-string / 整数族 / 浮点族 / enum ✅ ✅
    octet-string / visible-string / utf8-string ✅ ✅
    boolean ✅ ❌
    bcd ✅ ❌
    date-time / date / time ✅ ❌
    delta-* 差分系列 [28]–[33] ✅ ❌
    array / structure / compact-array ✅ ❌
    scaler_unit(量纲) ❌ ✅

    结论:

    • Data 的 value 是母表(简单 + 复杂全支持);
    • Register 的 value 只取了其中数值型的简单类型,但多了一个 scaler_unit。

    简单记:要装结构/文本/标志 → Data;要装有单位的数 → Register。


    5. 没有方法:Data 与 Register 的分界线

    Data 类没有任何 Specific methods。对比 Register 有一个可选方法 reset(data)(把 value 复位到实例特定的默认值)。

    语义边界:

    维度Data(class_id = 1)Register(class_id = 3)
    用途 任意数据(配置、参数、字符串、标志、结构…) 带量纲的过程值 / 状态值
    属性数 2(logical_name + value) 3(多一个 scaler_unit)
    是否有单位/倍率 ❌ ✅ scaler_unit = { scaler, unit }
    方法 无 可选 reset
    value 类型范围 简单 + 复杂全支持 仅数值型简单类型
    典型场景 序列号、固件版本、配置项、布尔标志 电能、功率、电压、电流

    蓝皮书给的"退化"口子(第 3 篇会详细展开):如果硬要用 Register 当 Data(scaler_unit 不用,或 scaler = 0 + unit = 255),那么 Register 的 value 可以使用 Data 类允许的全部数据类型。
    反过来说:Data 是更底层的通用容器,Register 是在它之上叠加量纲的特化。


    6. 【实战举例】

    例 1:设备制造序列号

    logical_name = 0-0:96.1.0 (示例 OBIS,设备标识类)
    value 类型 = visible-string [10]
    value 取值 = "ABC20230001"

    主站读取:GET ( 1, 0-0:96.1.0, 2 )
    响应编码(A-XDR):0A 0B 41 42 43 32 30 32 33 30 30 30 31
    │ │ └────── "ABC20230001" 的 ASCII
    │ └───────── 长度 11
    └──────────── tag = 0x0A (visible-string)

    这是最简单的 Data 用法:一个字符串,没有单位,谁读都是这个字符串。

    例 2:一个通信配置参数(心跳周期)

    logical_name = 某配置项 OBIS
    value 类型 = long-unsigned [18]
    value 取值 = 300 (心跳周期,单位秒)

    主站写入:SET ( 1, <OBIS>, 2, 300 )
    响应编码:12 01 2C (0x012C = 300)

    注意:"秒"这个概念没有存在对象里,是主站和运维约定好的。这正是 Data 的取舍 —— 换来简单,代价是单位靠约定。

    例 3:布尔状态标志(电池欠压)

    logical_name = 某状态类 OBIS
    value 类型 = boolean [3]
    value 取值 = TRUE (电池欠压)

    响应编码:03 01

    boolean 在 Register 里不可用 —— 所以"是/否"类标志只能(也最好)用 Data。

    例 4:固件标识

    logical_name = 固件版本类 OBIS(常见如 1-0:0.2.0,以设备为准)
    value 类型 = visible-string [10]
    value 取值 = "FW-METER-A-v2.3.1"

    与之配套的"固件数字签名"通常也是 Data 思路的延伸 —— 把二进制签名塞进 octet-string 类型的 value。

    例 5 ⭐:用 structure 一次装下一组配置(Register 做不到)

    这是 Data 最能体现"通用"的场景 —— 把一整条配置结构放进一个 value:

    logical_name = 某通信配置 OBIS
    value 类型 = structure [2]
    value 取值 = {
    heartbeat_interval : long-unsigned = 300, # [18]
    tls_enabled : boolean = TRUE, # [3]
    server_address : visible-string = "meter.example.com" # [10]
    }

    报文里的样子(A-XDR):

    02 ← structure 标签
    03 ← 3 个元素
    12 01 2C ← 元素1: long-unsigned 300
    03 01 ← 元素2: boolean TRUE
    0A 11 6D 65 74 65 72 … ← 元素3: visible-string "meter.example.com"(长度 0x11 = 17)

    主站一次 GET 就拿回整组配置,不需要发三条请求。这就是为什么"复杂类型"的支持如此重要 —— 而 Register 的 value 装不了 structure,只能拆成三个独立对象。

    例 6:SN(短名)寻址下怎么读

    某 Data 对象的基名 x(示例 0x0100)
    logical_name → x = 0x0100
    value → x + 0x08 = 0x0108

    主站:GET ( short_name = 0x0108 )

    对比 LN 寻址要带 6 字节 OBIS + class_id,SN 只发一个 13 位整数 —— 报文更短,适合资源受限设备(如无线水表)。


    7. 工程上容易踩的坑

  • 拿 Data 存有单位的量:能存,但主站读到的只是裸数字,单位必须靠文档约定。换表/换厂商就可能出错 —— 有单位就用 Register。
  • value 类型必须与 logical_name 语义自洽:原文要求"类型 + logical_name 能唯一解释"。比如一个叫"序列号"的对象装 long-unsigned,就违反了这个原则。
  • Data 没有方法,所以不能 reset:想改值只能 SET(且需要相应访问权限);想复位只能靠厂商自定义手段或换用 Register。
  • 别在 Register 里用 boolean / date-time / structure:Register 的 value CHOICE 不含这些 —— 建模前先查类的 CHOICE 表。
  • 复杂类型嵌套要双方对齐:structure 里再套 array 是可以的,但解析端必须知道确切结构(顺序、类型),否则解错。
  • 某些标识类 Data 对象是强制存在的:如设备序列号、固件标识等(用于设备识别与合规),实现时不能省。
  • logical_name 是 static:不会自己变;如果读到变化,通常是设备被重新配置/更换了。

  • 8. 小结 & 下期预告

    本篇要点:

  • Data(class_id = 1, version = 0)只有 logical_name + value 两个属性、零个方法,基数 0…n,是对象模型的"最小完备样本";
  • logical_name 是 6 字节 OBIS 编码(A-B:C.D.E*F,A=介质 / C=数据类别 / D=类型 / E=费率 …),永远排第一,是对象主键;
  • value 是 CHOICE 母表:支持全部简单类型 + array / structure / compact-array 复杂类型,可一次装下整组配置;
  • 与 Register 的分界:Data 无 scaler_unit(无单位)但类型范围更广;Register 多量纲但 value 仅限数值型简单类型;
  • Data 的典型场景:序列号、固件版本、配置参数、布尔标志、复杂配置结构。
  • 下一篇(第 3 篇):Register 类(class_id = 3) —— 在 Data 的基础上加上第三个属性 scaler_unit。你会看到 scaler / unit 如何把裸数字 12345 变成"1 234,5 Wh",为什么蓝皮书说 Register 对象"知道自己所装值的性质",以及"硬编码 scaler 会让你差 10 倍"这个经典事故是怎么发生的。我们还会把物理单位枚举表(unit = 30 是 Wh、27 是 W、255 是 count)一次讲清。


    参考资料:DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》,Data (class_id = 1, version = 0) 章节(含 Overview、类蓝图、value 的 CHOICE 类型表)与 Clause 4。文中属性、数据类型与 tag、Short name 偏移、OBIS 结构说明均与该章节原文一致;示例中的 OBIS 取值与配置为帮助理解而构造(实际以设备对象列表为准)。

    赞(0)
    未经允许不得转载:171主机测评 » DLMS/COSEM 蓝皮书解读(二):Data 类(class_id = 1)—— 对象模型的“最小完备样本“
    分享到: 更多 (0)

    评论 抢沙发

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