简介:secs4j-master 是一套面向半导体设备自动化领域的 Java 版 SECS/GEM 协议实现库,适合从事设备通信、工厂自动化系统开发的工程师与学习者使用。它把 SECS-I、SECS-II 的物理层与应用层协议,以及 GEM 规范中的设备初始化、状态报告、命令控制、数据采集等交互流程封装为可复用的类与方法,帮助开发者免去从零编写底层通信逻辑的繁琐。资源包共 80 个文件,以 72 个 java 源码为主体,辅以 3 个 xml 配置、4 个 txt 说明和 1 个 md 文档,压缩后约 109KB,结构紧凑、便于按模块阅读。内容涵盖 SECS 连接管理、消息构建与解析、GEM 接口、事件订阅发布、数据交换模型、异常处理及单元与集成测试用例,可帮助读者快速搭建设备与主机系统之间的通信桥梁,理解协议分层与消息结构,并在此基础上完成二次开发与调试。目前已有 2497 人学习下载,适合作为入门与进阶 SECS/GEM 开发的参考工程。
1. 从 secs4j-master 看 SECS/GEM 源码到底解决什么问题
半导体设备工程师第一次接触 secs4j-master 这类源码时,最常问的不是“怎么编译”,而是“它到底替我把哪一层协议做完了”。SECS/GEM 是设备与主机之间的通信标准,SECS 负责消息编码与收发,GEM 在 SECS 之上定义状态机、事件、报警、数据采集等行为。裸写这套协议,光是把一条 S1F1 的二进制流拼对,就要处理 10 字节头、会话 ID、流功能号、W 位、系统字节和消息体长度。secs4j-master 的价值在于把这些字节级细节封装成 Java 对象和回调,让上层只关心“收到 S1F1 要回 S1F2”。
它适合三类人:做设备端 SECS 驱动的嵌入式或 Java 工程师、做 EAP 主机侧通信模块的后端开发者、以及需要把旧设备接入 MES 的集成人员。源码里通常包含消息类、会话管理、超时重发、事件注册这几块,读它的过程本身就是理解 GEM 状态机怎么落地的最短路径。
2. secs4j-master 的模块划分与 SECS 消息编码原理
2.1 从源码目录反推 SECS/GEM 的分层
拿到 secs4j-master 后,先别急着找 main 方法。常见做法是按包名判断职责:message或secs包放 SECS-II 消息体和项(Item)的建模,session包管 HSMS 连接与 T3/T5/T6/T7/T8 定时器,gem包放状态机和事件。如果源码里出现HsmsConnection、SecsMessage、GemHandler这类类名,基本可以确认分层是“传输层 HSMS → 消息层 SECS-II → 行为层 GEM”。
读源码时优先看SecsMessage的编码方法。SECS-II 消息体由 Item 递归组成,Item 头两个字节里,第一个字节的高 6 位是格式码,低 2 位是长度字节数;第二个字节是长度。格式码决定这是 List、ASCII、Binary 还是数值类型。secs4j 一般用Item抽象类加AsciiItem、U4Item、ListItem等子类实现,编码时递归写头再写值。
2.2 用一段 Java 代码复现 SECS-II 消息编码
下面这段代码模拟 secs4j 里常见的 Item 编码逻辑,不依赖具体版本,只还原原理:
// 模拟 SECS-II Item 编码:格式码 + 长度 + 值 public class SecsItemEncoder { // 格式码常量,实际源码中通常定义在 Item 基类 static final int ASCII = 0x10; // 0b010000 static final int U4 = 0x34; // 0b110100 static final int LIST = 0x00; // 0b000000 // 编码一个 ASCII Item,返回字节数组 public static byte[] encodeAscii(String value) { byte[] data = value.getBytes(); int lenBytes = data.length <= 0xFF ? 1 : 2; // 长度字节数 byte[] out = new byte[1 + lenBytes + data.length]; out[0] = (byte) ((ASCII << 2) | (lenBytes - 1)); // 格式码左移2位,低2位放长度字节数 if (lenBytes == 1) { out[1] = (byte) data.length; } else { out[1] = (byte) ((data.length >> 8) & 0xFF); out[2] = (byte) (data.length & 0xFF); } System.arraycopy(data, 0, out, 1 + lenBytes, data.length); return out; } public static void main(String[] args) { byte[] encoded = encodeAscii("HELLO"); for (byte b : encoded) { System.out.printf("%02X ", b); } // 输出:45 05 48 45 4C 4C 4F } }逻辑说明:ASCII << 2把格式码放到高 6 位,lenBytes - 1占低 2 位,这是 SECS-II 的标准头格式。参数上,长度字节数只能是 1、2、3,分别对应 1、2、3 字节长度字段,实际源码里会用getLengthByteCount()判断。跑通这段,再看 secs4j 的Item.encode()就不会被位运算绕晕。
2.3 HSMS 连接与定时器参数怎么设
HSMS 是 SECS 在 TCP 上的承载,源码里通常有HsmsConnection负责握手和收发。关键参数是 T3(回复超时,默认 45 秒)、T5(连接分离超时,默认 10 秒)、T6(控制事务超时,默认 5 秒)、T7(未选中超时,默认 10 秒)、T8(网络字符间超时,默认 5 秒)。这些值在 secs4j 里一般通过配置文件或HsmsConfig设置。
| 参数 | 含义 | 常见默认值 | 调整建议 |
|---|---|---|---|
| T3 | 等待回复超时 | 45s | 设备慢时调到 60s |
| T5 | 连接分离后重连间隔 | 10s | 内网可降到 5s |
| T6 | 控制消息超时 | 5s | 一般不动 |
| T7 | 未选中状态超时 | 10s | 主机侧可调大 |
| T8 | 字符间超时 | 5s | 网络差时调大 |
注意:T3 设太小会导致正常但稍慢的设备被误判超时,进而触发重发,主机侧看到重复消息。调参前先用抓包确认实际往返时间。
3. 用 secs4j-master 跑通一条 S1F1 的完整流程
3.1 初始化会话与注册事件回调
要让源码跑起来,第一步是建立 HSMS 连接并注册消息处理器。secs4j 常见用法是创建HsmsConnection,传入被动或主动模式、IP、端口,然后open()。被动模式监听端口等主机连,主动模式连主机。设备端通常做被动。
// 初始化 HSMS 被动连接,监听 5000 端口 HsmsConnection conn = new HsmsConnection( HsmsConnection.Mode.PASSIVE, // 设备端一般被动等待 "0.0.0.0", 5000 ); // 注册消息处理器,收到消息后回调 conn.setMessageHandler((session, msg) -> { int stream = msg.getStream(); // 流号 int function = msg.getFunction(); // 功能号 if (stream == 1 && function == 1) { // S1F1 Are You There,回 S1F2 SecsMessage reply = new SecsMessage(1, 2, true); reply.setSystemBytes(msg.getSystemBytes()); // 系统字节必须一致 session.send(reply); } }); conn.open(); // 启动连接逻辑说明:setMessageHandler是源码里典型回调入口,参数session代表当前会话,msg是解析后的SecsMessage。getSystemBytes()用于匹配请求与回复,回复必须带相同系统字节,否则主机会认为回复不属于该请求。参数上,端口要和主机配置一致,被动模式不需要填主机 IP。
3.2 解析 S1F1 并构造 S1F2 回复
S1F1 通常没有消息体,S1F2 的常见格式是L,2 [A MDLN, A SOFTREV],即设备型号和软件版本。用 secs4j 的 Item 构造:
// 构造 S1F2 消息体:L,2 包含设备型号和版本 ListItem body = new ListItem( new AsciiItem("EQP-3000"), // MDLN 设备型号 new AsciiItem("1.0.0") // SOFTREV 软件版本 ); SecsMessage s1f2 = new SecsMessage(1, 2, true); s1f2.setBody(body); s1f2.setSystemBytes(request.getSystemBytes()); session.send(s1f2);逻辑说明:ListItem对应 SECS-II 的 List 格式,构造时传入子 Item。AsciiItem编码为 ASCII 格式。参数上,MDLN 和 SOFTREV 长度一般不超过 20 和 10 字符,主机侧可能校验。如果主机没收到回复,先确认setSystemBytes是否漏掉,再看 T3 是否超时。
3.3 验证收发是否成功的三种手段
第一种是日志。secs4j 一般有Log或Logger输出十六进制报文,看到01 02 81 00 00 00 00 00这类头就说明发出去了。第二种是抓包,用 tcpdump 看 5000 端口流量,确认 TCP 层有数据。第三种是主机侧日志,看是否收到 S1F2 并解析出 MDLN。三种手段结合,能快速定位是编码错、连接断还是主机没回。
提示:如果日志里 S1F2 的 W 位是 0,主机可能不认。回复消息的 W 位通常设为 true,表示需要回复,但 S1F2 作为回复本身 W 位常为 false,具体看主机规范。
4. GEM 状态机与事件报警在源码中的落地
4.1 GEM 状态模型与源码里的状态类
GEM 在 SECS 之上定义了设备状态:通信状态(Disabled/Enabled)、控制状态(Equipment Offline/Online Local/Online Remote)。secs4j-master 的gem包里通常有GemStateMachine或类似类,用枚举表示状态,用transition()方法切换。主机发 S1F17 请求 Online,设备回 S1F18 并切到 Online Remote,这时才允许远程命令。
读源码时重点看状态切换的触发条件。比如从 Online Local 到 Online Remote 需要主机发 S1F17 且设备接受,从 Online 到 Offline 可能由本地操作或 S1F15 触发。状态不对,后续的 S2F41 远程命令会被拒绝。
4.2 事件注册与报告发送的代码路径
GEM 的事件机制是设备主动上报。源码里常见GemEvent和CollectionEvent,设备通过registerEvent注册事件 ID,触发时调triggerEvent,GEM 根据报告定义(RPTID)组装 S6F11 发给主机。
// 注册事件 CEID=1001,关联报告 RPTID=2001 gem.registerEvent(1001, 2001); // 触发事件,附带数据项 gem.triggerEvent(1001, new U4Item(12345)); // 源码内部会构造 S6F11 并发送逻辑说明:registerEvent把 CEID 和 RPTID 绑定,triggerEvent触发时查报告定义,把数据项按顺序填入 S6F11 的L,3 [U4 DATAID, U4 CEID, L,1 [L,2 [U4 RPTID, L,n [items]]]]。参数上,CEID 和 RPTID 要和主机侧配置一致,否则主机解析失败。数据项类型和顺序也必须匹配报告定义。
4.3 报警上报 S5F1 的构造与确认
报警用 S5F1 上报,格式L,3 [B ALCD, A ALID, A ALTX],ALCD 是报警码,ALID 是报警 ID,ALTX 是文本。源码里通常有Alarm类,setAlarm()触发发送。主机收到后回 S5F2 确认。
// 触发报警 ALID=9001,ALCD=1 表示报警发生 Alarm alarm = new Alarm((byte) 1, "9001", "Temperature high"); gem.sendAlarm(alarm); // 内部构造 S5F1 并发送逻辑说明:ALCD 的 bit7 表示报警发生或清除,bit6 表示是否需确认。参数上,ALID 要和主机报警表对应,ALTX 长度有限制。如果主机没回 S5F2,检查 T3 是否超时,或 ALCD 的确认位是否设对。
5. 读 secs4j-master 源码时最容易踩的四个坑
5.1 系统字节不匹配导致回复被丢弃
最常见的问题是回复消息没带请求的系统字节。SECS 用系统字节匹配请求与回复,主机收到系统字节不一致的回复会直接丢弃。源码里SecsMessage的systemBytes字段必须在回复时从请求复制。排查方法:在send前打印msg.getSystemBytes(),和请求对比。
5.2 W 位与超时重发的连锁反应
W 位表示是否需要回复。如果请求 W=1 但设备没回,主机会在 T3 后重发,设备收到重复请求。如果设备对重复请求又回一次,主机可能收到两条回复。源码里通常有事务管理,用系统字节去重。读源码时看Transaction或PendingRequest类,确认重发时是否复用系统字节。
5.3 长度字节数判断错误导致解析越界
SECS-II 长度字段可以是 1、2、3 字节,源码里如果固定按 1 字节读,遇到长消息就会解析错位。正确做法是先读头字节低 2 位,算出长度字节数,再读对应字节。排查时抓一条长消息,看解析出的 Item 长度是否和实际一致。
5.4 状态机未初始化就发消息
GEM 要求设备先进入 Online 才能发某些消息。如果源码里状态机没初始化,或状态还是 Offline,发 S6F11 可能被主机忽略。读源码时确认GemStateMachine的初始状态和切换条件,必要时在发消息前检查状态。
| 坑 | 现象 | 排查点 |
|---|---|---|
| 系统字节不匹配 | 主机丢弃回复 | 回复是否复制请求系统字节 |
| W 位重发 | 主机收到重复回复 | 事务去重逻辑 |
| 长度字节数错 | 解析越界或乱码 | 头字节低 2 位 |
| 状态未初始化 | 消息被忽略 | 状态机当前状态 |
6. 用 secs4j-master 做二次开发的两个进阶技巧
6.1 自定义 Item 类型扩展非标准格式
有些设备用非标准 Item 格式,secs4j 的Item基类可以继承扩展。做法是重写encode()和decode(),在格式码里选一个未用值,然后在解析时注册到工厂。这样既能复用现有会话管理,又能处理私有格式。扩展时注意格式码不要和标准冲突,标准格式码在 SECS-II 规范里有定义。
6.2 用拦截器统一记录收发报文
调试阶段最有用的是拦截器。secs4j 一般支持在HsmsConnection上加MessageInterceptor,在beforeSend和afterReceive里打印十六进制和解析后的结构。这样不用改业务代码就能看到完整报文流。拦截器里注意不要阻塞,打印用异步或缓冲,否则影响 T3 计时。
// 添加拦截器,打印收发报文 conn.addInterceptor(new MessageInterceptor() { @Override public void beforeSend(SecsMessage msg) { System.out.println("SEND: " + msg.toHexString()); } @Override public void afterReceive(SecsMessage msg) { System.out.println("RECV: " + msg.toHexString()); } });逻辑说明:toHexString()是源码里常见的调试方法,把消息头和体转成十六进制。参数上,拦截器要在open()前添加,否则可能漏掉握手消息。如果日志量太大,可以加过滤,只打印特定流功能号。
6.3 用单元测试验证消息编解码
改完编码逻辑后,最稳的验证是单元测试。构造已知字节数组,调decode()再encode(),对比是否一致。secs4j 源码里通常有测试用例,照着加自己的。测试覆盖边界:空 Item、最大长度、多字节长度字段。跑通测试再上设备,能省很多现场调试时间。
本文还有配套的精品资源,点击获取