4、SECS消息解析:Message结构、Stream与Function、Item数据类型、编码与解码

各位工程师,今天我们来聊聊SECS消息解析。说实话,这是整个设备数据采集中最核心的一环。你想想看,设备吐出来的数据就是一堆二进制流,你得知道它说了什么,对吧?

我在刚入行那会儿,第一次看到SECS报文,满屏的十六进制,头都大了。后来跟着老工程师一点点啃规范,才明白这东西其实有章可循。今天我就把这块掰开了讲给你听。

4.1 Message结构:报文长什么样?

一条SECS消息,说白了就是一个数据包。它由两部分组成:消息头消息体

消息头固定10个字节,里面包含了设备ID、控制码、Stream号、Function号、等待位这些关键信息。消息体则是真正的数据内容,由Item嵌套组成。

我习惯把消息头比作快递单,消息体比作包裹里的东西。快递单告诉你从哪来、到哪去、什么类型,包裹里才是你真正要的东西。

消息头结构(10字节)

  • 字节0-1:设备ID(Device ID)
  • 字节2:控制码(Control Code)
  • 字节3:Stream号
  • 字节4:Function号
  • 字节5:等待位(W-Bit)
  • 字节6-9:系统字节(System Bytes)

举个例子,一条S1F1消息,Stream=1,Function=1,这是最基础的通信握手消息。设备收到S1F1,必须回复S1F2。嗯,这里要注意,如果设备不回,说明通信链路有问题。

4.2 Stream与Function:消息的身份证

Stream和Function,合起来叫SF码。每个SF码代表一种特定的消息类型。Stream是类别,Function是具体操作。

我个人习惯把Stream比作书的章节,Function比作具体段落。比如:

  • S1F1:通信请求(Are you there?)
  • S1F2:通信响应(I'm here)
  • S6F11:事件报告发送(设备上报数据)
  • S6F12:事件报告确认(主机确认收到)

我在项目中遇到过最坑的事,就是有人把S6F11和S6F12搞反了。设备发了报告,主机却回了S6F11,结果设备一直等确认,生产线直接卡死。所以,SF码一定要记清楚。

小技巧:Stream号奇数表示主机发起的消息,偶数表示设备发起的消息。Function号奇数表示请求,偶数表示响应。比如S1F1是主机请求,S1F2是设备响应。

4.3 Item数据类型:数据的基本单元

消息体里的数据,是由Item组成的。每个Item就是一个数据单元,包含类型、长度和值三部分。

SECS定义了多种数据类型,我列个表给你看:

类型码 名称 说明 字节数
0x00 List 列表,可包含多个子Item 不定
0x10 Binary 二进制数据 1字节
0x20 Boolean 布尔值(0/1) 1字节
0x30 ASCII 字符串 1字符
0x40 I8 8位有符号整数 1字节
0x50 I1 1位有符号整数 1字节
0x60 I2 2字节有符号整数 2字节
0x70 I4 4字节有符号整数 4字节
0x80 F8 8字节浮点数 8字节
0x90 F4 4字节浮点数 4字节
0xA0 U8 8位无符号整数 1字节
0xB0 U1 1位无符号整数 1字节
0xC0 U2 2字节无符号整数 2字节
0xD0 U4 4字节无符号整数 4字节

你可能会问,为什么有这么多整数类型?说白了,就是为了节省带宽。设备上报温度,用U1就够了,没必要用U4。我曾经见过有人不管什么数据都用U4,结果报文体积大了好几倍,网络拥堵得一塌糊涂。

4.4 编码与解码:二进制到数据的转换

编码就是把数据变成二进制流,解码就是反过来。SECS的编码规则其实很简单,就是类型码+长度+数据。

举个例子,编码一个ASCII字符串"ABC":

  • 类型码:0x30(ASCII)
  • 长度:0x03(3个字符)
  • 数据:0x41 0x42 0x43(A、B、C的ASCII码)

所以最终二进制流就是:30 03 41 42 43

再复杂一点,编码一个List,里面包含一个整数和一个字符串:

List (0x00 0x02)  // 列表,包含2个元素
  ├── I4 (0x70 0x04 0x00 0x00 0x00 0x64)  // 整数100
  └── ASCII (0x30 0x03 0x41 0x42 0x43)    // 字符串"ABC"

解码就是反过来,先读类型码,再读长度,最后读数据。如果类型码是List,就递归解析子Item。

注意:长度字段的编码方式有两种:短格式(1字节)和长格式(3字节)。当长度小于256时用短格式,否则用长格式。我曾经踩过这个坑,解析时没判断长度格式,结果数据全乱了。

4.5 实战:手写一个简单的解码器

光说不练假把式。我写个简单的Python解码函数,你感受一下:

def decode_secs_item(data, offset):
    """解码一个SECS Item"""
    # 读取类型码
    type_byte = data[offset]
    offset += 1
    
    # 判断长度格式
    if data[offset] & 0x80:  # 长格式
        length = ((data[offset] & 0x7F) << 16) | (data[offset+1] << 8) | data[offset+2]
        offset += 3
    else:  # 短格式
        length = data[offset]
        offset += 1
    
    # 根据类型码解析数据
    if type_byte == 0x00:  # List
        items = []
        for _ in range(length):
            item, offset = decode_secs_item(data, offset)
            items.append(item)
        return items, offset
    elif type_byte == 0x30:  # ASCII
        return data[offset:offset+length].decode('ascii'), offset + length
    elif type_byte == 0x70:  # I4
        value = int.from_bytes(data[offset:offset+4], 'big', signed=True)
        return value, offset + 4
    # ... 其他类型类似

这段代码虽然简单,但核心逻辑都在了。实际项目中,你还要处理各种边界情况,比如数据截断、类型不匹配等。

4.6 知识体系总览

说了这么多,我画张图帮你理清思路:

SECS消息解析知识体系 SECS消息 消息头(10字节) 消息体(Item嵌套) 设备ID + 控制码 Stream/Function W-Bit + 系统字节 Item:类型码 + 长度 + 数据 List(嵌套容器) 整数/浮点数/布尔 ASCII/Binary 编码:数据 → 二进制流 | 解码:二进制流 → 数据

这张图把SECS消息解析的脉络都串起来了。从消息头到消息体,从Item类型到编解码,每一步都有章可循。

我的建议:刚开始接触SECS解析时,别急着写代码。先拿一个真实的报文,用手工方式逐字节解析一遍。我当年就是这么干的,虽然慢,但理解特别深。等你把几个典型报文都手解过一遍,写代码就是水到渠成的事。

好了,SECS消息解析这块就讲到这里。记住,核心就是三件事:看懂消息头、理解Item嵌套、掌握编解码规则。把这三点吃透了,设备数据采集的大门就算真正打开了。

专注资料整理