4. SECS-II数据项类型实战

SECS-II 消息体本质上是一棵由“数据项”组成的树。每个数据项都带有格式码(Format Code)和长度(Length),格式码决定它是什么类型,长度决定它后面跟多少字节。真正让工程师踩坑的,往往不是消息语义,而是这些类型在编码、解码时的字节序、符号、长度单位和对齐方式。本节把常用类型放在一起,从“是什么”讲到“怎么编、怎么解、哪里会错”。

一、格式码与长度字节:所有类型的共同外壳

每个数据项以 1 字节格式码开头,低 6 位是格式码,高 2 位表示长度字节数(1、2 或 3 字节)。例如格式码 0x01 表示 BOOLEAN,0x10 表示 A(ASCII),0x20 表示 B(Binary),0x00 表示 L(List),0x21 表示 U1,0x22 表示 U2,0x24 表示 U4,0x31 表示 I1,0x32 表示 I2,0x34 表示 I4,0x44 表示 F4,0x48 表示 F8。长度字段表示的是“数据字节数”,而不是元素个数——这是第一个高频陷阱。

例如 U4 有 3 个元素,长度应为 12,而不是 3;A 类型长度就是字符数;L 类型长度是子项个数,但每个子项自身还带格式码和长度。解码时必须先读格式码,再按高 2 位确定长度字节数,然后读取长度,最后按长度读取数据体。

SECS-II 数据项编码结构与类型对照 通用编码结构 格式码 1B 长度 1~3B 数据体(按长度) 高2位=长度字节数,低6位=格式码 L / A / B L:List,长度=子项个数 A:ASCII,长度=字符数 B:Binary,长度=字节数 BOOLEAN 格式码 0x01 每元素 1 字节 0x00=False,非0=True U1/U2/U4 无符号整数 1/2/4 字节,大端 长度=元素数×字节数 I1/I2/I4 有符号整数 补码表示 1/2/4 字节,大端 F4 / F8 浮点 F4:IEEE 754 单精度,4 字节 F8:IEEE 754 双精度,8 字节 字节序为大端,需按位重组 常见陷阱 ① 长度按字节而非元素数 ② 大端/小端混用导致数值错乱 ③ 有符号与无符号误判

二、L、A、B:最基础也最容易误用的三类

L(List)是容器,长度字段是子项个数。例如一个 L 包含 3 个子项,格式码为 0x01(高 2 位为 0,表示长度 1 字节),长度字节为 0x03。解码 L 时不能按“字节数”去读,而要循环读取子项,每个子项自己带格式码和长度。嵌套 L 时尤其要注意递归解析,否则会把子项当成数据体。

A(ASCII)长度就是字符数,通常用于设备名、配方名、状态文本。B(Binary)长度是字节数,常用于位图、校验值或原始数据。A 与 B 在字节层面都是连续字节,区别只在语义:A 应按字符集解释,B 应按原始字节处理。若把 B 当 A 解码,遇到 0x00 或非打印字符就会截断或乱码。

三、BOOLEAN:0x00 与“非零”

BOOLEAN 每个元素占 1 字节,0x00 表示 False,非 0 表示 True。标准中通常用 0x01 表示 True,但解码时应把任何非零值都视为 True,避免设备用 0xFF 表示 True 时被误判。编码时建议统一输出 0x01,保证兼容性。BOOLEAN 的长度是元素个数,例如 4 个布尔值长度为 4。

四、U1/U2/U4 与 I1/I2/I4:整数族的字节序与符号

U 系列是无符号整数,I 系列是有符号整数,均采用大端字节序。U1/I1 占 1 字节,U2/I2 占 2 字节,U4/I4 占 4 字节。长度字段等于“元素个数 × 每元素字节数”。例如 U4 的 3 个元素,长度是 12。解码时先按大端把字节拼成整数,再根据有无符号决定是否做补码转换。

// 示例:U2 大端解码(Java 伪代码)
int decodeU2(byte[] data, int offset) {
    return ((data[offset] & 0xFF) << 8)
         |  (data[offset + 1] & 0xFF);
}

// 示例:I2 有符号解码
short decodeI2(byte[] data, int offset) {
    return (short) (((data[offset] & 0xFF) << 8)
                  |  (data[offset + 1] & 0xFF));
}

常见陷阱是“把 I2 当 U2 解”。例如字节 0xFF 0xFE,按 U2 是 65534,按 I2 是 -2。设备上报温度、偏移量、差值时常用有符号类型,若按无符号解析,负值会变成巨大正数,导致 EAP 判断异常。另一个陷阱是长度计算错误:U4 的 2 个元素长度是 8,若写成 2,接收方只会读到半个元素,后续全部错位。

五、F4/F8:浮点的精度与字节重组

F4 是 IEEE 754 单精度,4 字节;F8 是双精度,8 字节,均为大端。解码时不能简单地把字节转成整数,而要按 IEEE 754 的符号位、指数位、尾数位重组。很多语言提供现成方法,例如 Java 的 Float.intBitsToFloat 配合大端整数,C# 的 BitConverter.Int32BitsToSingle 配合字节序反转。若直接按整数解释,会得到完全错误的数值。

浮点常见陷阱有三类:一是字节序未转换,小端平台直接读大端数据;二是 F4 与 F8 混用,长度和精度不匹配;三是用浮点表示精确值(如配方中的整数计数)导致舍入误差。工程上建议:能用整数表达的量优先用 U/I 类型,浮点仅用于温度、压力、流量等连续量,并在协议文档中明确精度和单位。

实战建议:在 EAP 侧为每种类型建立独立的编解码函数,并在单元测试中覆盖边界值,例如 U1 的 0 与 255、I2 的 -32768 与 32767、F4 的 0.0 与 -0.0。边界值通过后,再接入真实设备消息验证。

六、综合判断:拿到一段字节如何正确解码

假设收到一段消息体:0x21 0x02 0x00 0x0A 0x01 0x01 0x00。先读第一个字节 0x21,低 6 位 0x21 对应 U1,高 2 位为 0 表示长度 1 字节;读长度 0x02,表示 2 个 U1 元素;读数据 0x00 0x0A,得到 0 和 10。接着读 0x01,低 6 位 0x01 对应 BOOLEAN,长度 1 字节;读长度 0x01,表示 1 个布尔;读数据 0x00,得到 False。剩余 0x01 0x00 若继续解析,则又是一个 BOOLEAN,长度为 0,表示空列表。整个过程必须严格按“格式码→长度→数据体”推进,任何一步长度算错都会导致后续全部错位。

把类型、长度、字节序、符号四件事同时盯住,SECS-II 的编解码就不会再是黑盒。真正稳定的 EAP 集成,靠的不是记住格式码,而是对每个字节的去向都有明确判断。