4、GEM消息类型:Stream消息、Function消息、Block消息

GEM协议里的消息,说白了就是设备(比如机器人、泵、阀门)和主机之间沟通的“语言”。我刚开始接触的时候,觉得消息类型这个概念很抽象。后来在项目里调了几个月,才真正摸清楚它们的脾气。

GEM标准定义了三种核心消息类型:Stream消息、Function消息和Block消息。它们不是平级关系,而是层层嵌套的。你想想看,就像寄快递:Stream是快递公司,Function是包裹里的物品类别,Block就是物品本身的数据。

一句话总结:Stream + Function + Block 构成了GEM通信的最小完整单元。

4.1 消息结构总览

先看一张整体结构图,后面我们再逐个拆解。

GEM消息三层结构 Stream 消息(流) 消息大类:通信控制、设备状态、数据收集、报警管理、远程控制等 范围:0 ~ 255(0 为测试流,1 ~ 255 为正式流) Function 消息(功能) 具体操作:请求、响应、事件通知、数据上报 范围:0 ~ 255(奇数为请求,偶数为响应) Block 消息(数据块) 实际数据载荷:ASCII 或二进制格式

4.2 Stream消息——消息的大类

Stream是消息的第一层分类。它定义了这条消息属于哪个功能域。GEM标准里,Stream 0 是测试流,Stream 1 到 Stream 255 是正式流。不过实际项目中,常用的也就十几个。

我记得第一次看SECS-II协议文档时,被Stream列表吓了一跳。后来发现,大部分项目只需要用到Stream 1、2、5、6、7这几个。其他的,说实话,很多设备厂商自己都没实现。

Stream编号 功能域 典型用途
S0 测试/诊断 通信测试、回环测试
S1 设备状态 设备ID查询、状态模型、在线/离线
S2 设备控制与诊断 参数设置、诊断请求、时间设置
S5 报警管理 报警上报、报警确认、报警列表
S6 数据收集 事件上报、追溯数据、SPC数据
S7 配方管理 配方下载、配方上传、配方验证
S10 终端服务 终端显示消息、操作员交互
我的习惯:开发新设备时,先确认客户需要哪些Stream。不要一股脑全实现,那是给自己挖坑。我见过一个项目,工程师把S0到S255全写了,结果测试时发现一半都没用过,还引入了不少bug。

4.3 Function消息——消息的具体操作

确定了Stream之后,Function告诉对方你要做什么。每个Function都有一个编号,而且有个规律:奇数Function是请求,偶数Function是响应。

举个例子,S1F13是“请求设备ID”,S1F14就是“返回设备ID”。你发S1F13过去,对方回S1F14给你。这个配对关系,是GEM通信的基础。

为什么会这样设计?说白了,就是为了让通信双方能明确知道:我发的请求有没有被处理,处理结果是什么。如果对方回了一个错误的Function编号,那基本可以断定通信出问题了。

我曾经踩过的坑:有一次开发,我忘了Function编号的奇偶规则,把请求和响应写反了。结果设备一直收不到正确响应,排查了整整一天。后来我养成了习惯:写代码前先在文档里核对Function编号。

常见的Function编号示例:

  • S1F13 / S1F14:设备ID查询
  • S1F17 / S1F18:在线/离线请求
  • S2F41 / S2F42:远程命令发送
  • S5F1 / S5F2:报警上报
  • S6F11 / S6F12:事件数据上报

4.4 Block消息——真正的数据

Block是消息的最内层,也是实际数据的载体。GEM支持两种数据格式:ASCII和二进制。

ASCII格式可读性好,调试方便。二进制格式效率高,适合传输大量数据。我个人习惯:开发阶段用ASCII,方便抓包看数据;正式上线前再切到二进制,提升性能。

Block消息的结构如下:

Block = 长度(2字节) + 数据(N字节) + 校验(1字节)

长度:整个Block的字节数(包含长度和校验字段)
数据:实际的消息内容
校验:异或校验,用于检测传输错误
关键点:Block的校验是可选实现的。很多设备为了性能,直接跳过了校验。但如果你做的是安全关键设备,我建议还是加上。

4.5 三种消息的协作流程

现在我们把三者串起来,看一个完整的通信流程。

消息协作流程示例:查询设备ID 主机 (Host) 设备 (Equipment) 1. 构造消息 Stream: S1 Function: F13 (请求) Block: 空数据 发送 S1F13 2. 解析并响应 Stream: S1 Function: F14 (响应) Block: "EQ-001" 返回 S1F14 3. 收到设备ID 设备ID = "EQ-001"

这个流程看起来简单,但实际开发中,每一步都有坑。比如:

  • 主机发了S1F13,设备没响应怎么办?——需要设置超时重传机制。
  • 设备返回的Block数据格式不对怎么办?——需要做数据校验和格式检查。
  • 网络断开后重连,消息序号怎么处理?——需要维护消息计数器。
我的建议:开发初期,先用一个简单的Stream/Function组合(比如S1F13/S1F14)打通整个通信链路。链路通了,再逐步添加其他消息。别一上来就想把所有消息都实现,那样出了问题都不知道是哪里卡住的。

4.6 消息类型对比总结

特性 Stream消息 Function消息 Block消息
层级 第一层(外层) 第二层(中间层) 第三层(内层)
作用 定义消息类别 定义具体操作 承载实际数据
取值范围 0 ~ 255 0 ~ 255 无固定范围
奇偶规则 无 奇数请求,偶数响应 无
数据格式 无 无 ASCII 或 二进制
调试难度 低 中 高(需解析数据)

嗯,到这里,三种消息类型的基本概念应该清楚了。记住这个三层结构,后面讲消息编码和解析时,你会回来感谢我的。