我正在编写一个 java spring boot 服务,该服务通过 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 对象列表。
该服务循环遍历 VendRequestItemModel 对象列表,对于每个项目,它打开串行端口,构造一个命令来销售产品,将命令发送到硬件设备,读取响应,然后关闭串行端口。
如果响应表明产品已成功售出,则服务将移至列表中的下一项。如果响应表明该槽已锁定,则服务会将该项目添加到失败项目列表中,并等待 5 秒,然后再移至下一个项目。如果响应表明售货失败,服务会将该商品添加到失败商品列表中,并等待 5 秒,然后再移至下一个商品。如果从硬件设备发送或接收数据时出现异常,服务会捕获异常并返回失败项目的列表。
重要的一点是,当此代码在任何阶段失败时,机器都不会发送任何响应字节,然后需要重新启动服务器和自动售货机才能使代码再次工作,但在重新启动后,此代码再次执行过程中随机点失败。
虽然存在多种可能性,一直到电气问题,但此类错误的常见编程原因与对串行端口数据读取工作方式的错误假设有关,尤其是在不使用任何流量控制时。
以这一行为例:
serialPort.readBytes(ANS_alpha, ANS_alpha.length);
如果使用阻塞读取,那么如果发生超时,这可能会在完整的“消息”之前返回。即发件人比平常慢一点。如果使用非阻塞,有时可能会在完整的“消息”之前返回。您当前的逻辑会在尝试再次读取之前刷新接收缓冲区。发送方在尝试发送剩余字节时可能会挂起。 如果您怀疑这可能是问题所在,那么您可能需要更改逻辑以继续调用 readBytes 直到您知道您有完整的消息。另一个快速检查是对任何错误使用 bytesAvailable/readBytes 以确保在尝试再次读取之前清除发送者。类似地,您可以在连接时尝试 bytesAvailable/readBytes,然后再尝试发送任何内容。
请务必查看
readBytes的文档并检查 com 端口超时设置。看起来您没有更改默认值,每个操作系统的默认值可能有所不同。
我还没有研究所有问题,有些事情可以监控,比如缓冲区大小、设置为阻塞等。