基于jSerialComm的Java串口通信偶发故障排查求助
问题
基于Spring Boot开发的售货机出货服务,采用fazecast/jSerialComm库实现串口通信。服务有时能正常完成全部商品出货,但会随机在出货流程任意阶段失败;故障发生时设备无响应,需重启服务与售货机才能恢复,但重启后仍会随机出现故障。
服务代码
package com.intelparcel.java.be.services; import com.fazecast.jSerialComm.SerialPort; import com.intelparcel.java.be.models.vend.FailedItemsModel; import com.intelparcel.java.be.models.vend.VendRequestItemModel; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @Service public class VendService { private List<FailedItemsModel> failedItems = new ArrayList(); public List<FailedItemsModel> vendProducts(List<VendRequestItemModel> data) { failedItems.clear(); SerialPort serialPort = SerialPort.getCommPort("/dev/ttyUSB0"); serialPort.setComPortParameters(9600, 8, 1, SerialPort.NO_PARITY); for (VendRequestItemModel item : data) { for (int quantity = 0; quantity < item.getQuantity(); quantity++) { openPort(serialPort); String TXBUF = "01C1" + Integer.toHexString(item.getSlot_number()); System.out.println(TXBUF); byte CHK = 0; String[] HexValues = new String[TXBUF.length() / 2]; byte[] ByteValues = new byte[TXBUF.length() / 2]; for (int i = 0; i < (TXBUF.length() / 2); i++) { HexValues[i] = TXBUF.substring((2 * i), (2 * i) + 2); ByteValues[i] = (byte) Integer.parseInt(HexValues[i], 16); if (i > 1) { CHK += ByteValues[i]; } } CHK = (byte) (0 - CHK); TXBUF += String.format("%02X", CHK) + ">"; TXBUF = "<" + TXBUF; serialPort.writeBytes(TXBUF.getBytes(), TXBUF.length()); System.out.println("Command Sent = " + TXBUF); //---sending command ends here----- try { Thread.sleep(3000); byte[] ANS_alpha = new byte[1024]; serialPort.readBytes(ANS_alpha, ANS_alpha.length); logBytes(ANS_alpha); System.out.println("Initial Response: " + new String(ANS_alpha, StandardCharsets.UTF_8)); if (new String(ANS_alpha, StandardCharsets.UTF_8).toLowerCase().contains("01c100ff".toLowerCase())) { flushAndClosePort(serialPort); System.out.println("Slot is locked"); addFailedItems(item.getSlot_number(), -1, "Slot stuck"); Thread.sleep(5000); continue; } if (ANS_alpha[0] != 6) { serialPort.flushIOBuffers(); flushAndClosePort(serialPort); System.out.println("Did not receive 6...continuing forward..."); addFailedItems(item.getSlot_number(), -2, "Vending failed, please retry"); Thread.sleep(5000); continue; } int count = 0; while (count <= 10) { System.out.println(); System.out.println(); System.out.println(); byte[] ANS = new byte[1024]; serialPort.readBytes(ANS, ANS.length); String decodedResponse = new String(ANS, StandardCharsets.UTF_8); System.out.println("RESPONSE String: " + decodedResponse); logBytes(ANS); if (decodedResponse.toLowerCase().contains("01c101fe".toLowerCase()) || decodedResponse.toLowerCase().contains("01c100ff".toLowerCase())) { if (new String(ANS, StandardCharsets.UTF_8).toLowerCase().contains("01c100ff".toLowerCase())) { System.out.println("Slot locked after vend"); } break; } Thread.sleep(1000); count++; } flushAndClosePort(serialPort); Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); return failedItems; } } } flushAndClosePort(serialPort); return failedItems; } private void flushAndClosePort(SerialPort serialPort) { serialPort.flushIOBuffers(); serialPort.closePort(); } private void addFailedItems(int slotNumber, int errorCode, String message) { failedItems.add(new FailedItemsModel(slotNumber, errorCode, message)); } private void openPort(SerialPort serialPort) { if (serialPort.isOpen()) { flushAndClosePort(serialPort); } serialPort.flushIOBuffers(); serialPort.openPort(); } private void logBytes(byte[] bytesRead) { System.out.println("RAW_BYTES_RECEIVED: "); for (byte i : bytesRead) { System.out.print(i); } System.out.println(); System.out.println(); System.out.println(); } }
代码逻辑说明
代码使用fazecast/jSerialComm的SerialPort库实现与硬件设备的串口通信,核心方法vendProducts接收VendRequestItemModel列表作为输入,返回FailedItemsModel列表。服务循环处理每个出货请求,针对每个商品的每一次出货,执行打开串口→构造出货指令→发送指令→读取响应→关闭串口的流程;根据响应结果判断出货成功、货道锁定或出货失败,并记录失败项;若通信过程中出现异常,立即返回已记录的失败项。
故障排查与解决办法
1. 串口资源频繁开关与泄漏
- 问题点:
每次出货都重复打开/关闭串口,极易引发串口资源竞争、未完全释放的问题,导致设备无响应;异常分支中仅返回失败项,未确保串口被强制关闭,造成资源泄漏。 - 修复措施:
- 改为全局单例串口实例,服务启动时初始化打开,出货流程复用,销毁时统一关闭;
- 所有代码分支(正常/异常)都通过
finally块强制执行串口关闭逻辑,避免资源泄漏; - 移除每次出货的
openPort调用,避免频繁开关串口。
2. 串口读取逻辑存在阻塞与脏数据干扰
- 问题点:
readBytes会阻塞直到读取到指定长度字节,若设备未返回足够数据会一直卡住;直接将1024字节数组转字符串,脏数据会干扰响应判断;固定Thread.sleep等待响应,无法适配设备实际响应速度,易出现漏读或超时。 - 修复措施:
- 先通过
serialPort.bytesAvailable()判断可用字节数,再读取对应长度数据,避免无意义阻塞; - 仅处理实际读取到的字节部分,示例:
int bytesRead = serialPort.readBytes(ANS_alpha, ANS_alpha.length); String response = new String(ANS_alpha, 0, bytesRead, StandardCharsets.UTF_8); - 替换固定休眠为超时等待机制,循环检查可用字节数,超过指定时间(如5秒)则判定超时。
- 先通过
3. 指令构造与校验码计算错误
- 问题点:
原代码存在语法错误(多余的")导致指令格式错误,设备无法识别;校验码仅累加i>1的字节,需确认是否符合设备协议要求,若协议要求范围不同则计算逻辑错误。 - 修复措施:
- 修正指令构造代码为
TXBUF += String.format("%02X", CHK) + ">";; - 对照设备通信协议文档,确认校验码计算范围,确保与设备要求完全一致。
- 修正指令构造代码为
4. 线程安全问题
- 问题点:
failedItems为类成员变量,Spring单例服务下多请求并发调用时会出现修改冲突;串口操作无同步控制,并发访问会导致通信混乱。 - 修复措施:
- 将
failedItems改为方法内局部变量,避免并发修改; - 对串口操作加
synchronized锁,确保同一时间仅一个线程操作串口; - 或使用异步任务队列,将出货请求排队处理,彻底避免并发冲突。
- 将
5. 协议解析不严谨
- 问题点:
仅通过包含特定字符串判断响应,未严格按照设备协议解析帧格式(起始符、长度、校验码),易出现误判;未处理帧错误、校验失败等异常情况。 - 修复措施:
- 严格遵循设备通信协议解析响应:先校验起始符、长度、校验码,再解析内容;
- 增加错误处理逻辑,对校验失败、帧格式错误的响应直接判定为通信失败,并记录详细日志。
内容的提问来源于stack exchange,提问作者Muhammad Faizan
相关产品推荐
相关产品推荐

