You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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. 指令构造与校验码计算错误

  • 问题点:
    原代码存在语法错误(多余的&quot;)导致指令格式错误,设备无法识别;校验码仅累加i>1的字节,需确认是否符合设备协议要求,若协议要求范围不同则计算逻辑错误。
  • 修复措施:
    • 修正指令构造代码为TXBUF += String.format("%02X", CHK) + ">";;
    • 对照设备通信协议文档,确认校验码计算范围,确保与设备要求完全一致。

4. 线程安全问题

  • 问题点:
    failedItems为类成员变量,Spring单例服务下多请求并发调用时会出现修改冲突;串口操作无同步控制,并发访问会导致通信混乱。
  • 修复措施:
    • 将failedItems改为方法内局部变量,避免并发修改;
    • 对串口操作加synchronized锁,确保同一时间仅一个线程操作串口;
    • 或使用异步任务队列,将出货请求排队处理,彻底避免并发冲突。

5. 协议解析不严谨

  • 问题点:
    仅通过包含特定字符串判断响应,未严格按照设备协议解析帧格式(起始符、长度、校验码),易出现误判;未处理帧错误、校验失败等异常情况。
  • 修复措施:
    • 严格遵循设备通信协议解析响应:先校验起始符、长度、校验码,再解析内容;
    • 增加错误处理逻辑,对校验失败、帧格式错误的响应直接判定为通信失败,并记录详细日志。

内容的提问来源于stack exchange,提问作者Muhammad Faizan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:55:09