diff --git a/wms-webapi/pom.xml b/wms-webapi/pom.xml index 68ce21f..bac2344 100644 --- a/wms-webapi/pom.xml +++ b/wms-webapi/pom.xml @@ -67,6 +67,13 @@ aws-java-sdk-s3 1.12.780 + + + com.fazecast + jSerialComm + 2.9.2 + + diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/weighManage/ah/AHDZCConnect.java b/wms-webapi/src/main/java/top/wms/admin/controller/weighManage/ah/AHDZCConnect.java new file mode 100644 index 0000000..01856dc --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/weighManage/ah/AHDZCConnect.java @@ -0,0 +1,307 @@ +package top.wms.admin.controller.weighManage.ah; + +import com.fazecast.jSerialComm.SerialPort; +import com.fazecast.jSerialComm.SerialPortDataListener; +import com.fazecast.jSerialComm.SerialPortEvent; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.nio.charset.Charset; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 安衡电子秤连接类 + */ +@Component +@Slf4j +public class AHDZCConnect { + + private static final String PORT_NAME = "COM5"; + private static final int BAUD_RATE = 9600; + private static final int DATA_BITS = 8; + private static final int STOP_BITS = 1; + private static final int TIMEOUT = 2000; + private static final Charset SCALE_CHARSET = Charset.forName("GBK"); + + private static final long HEALTH_CHECK_INTERVAL = 5000; + private static final long DATA_TIMEOUT = 10000; + private static final int MAX_RECONNECT_DELAY = 30000; + private static final int INITIAL_RECONNECT_DELAY = 1000; + private static final int BUFFER_SIZE = 256; + + private volatile SerialPort serialPort; + private final AtomicBoolean isConnecting = new AtomicBoolean(false); + private final AtomicBoolean isRunning = new AtomicBoolean(true); + private final AtomicLong lastDataTime = new AtomicLong(System.currentTimeMillis()); + private final AtomicInteger reconnectAttempts = new AtomicInteger(0); + private final AtomicInteger currentReconnectDelay = new AtomicInteger(INITIAL_RECONNECT_DELAY); + + private final byte[] readBuffer = new byte[BUFFER_SIZE]; + + private ScheduledExecutorService executorService; + + + + @PostConstruct + public void init() { + // 项目启动时初始化并启动服务 + ScaleService(); + start(); + } + + @PreDestroy + public void destroy() { + // 项目关闭时停止服务 + stop(); + } + + + public void ScaleService() { + executorService = Executors.newScheduledThreadPool(2, r -> { + Thread t = new Thread(r, "ScaleService-Worker"); + t.setDaemon(true); + return t; + }); + + Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "Shutdown-Hook")); + } + + public void start() { + log.info("========================================"); + log.info("启动电子秤连接线程,串口: " + PORT_NAME + " | 波特率: " + BAUD_RATE); + log.info("========================================"); + + startHealthCheck(); + startConnectionMonitor(); + if (connect()) { + log.info("电子秤连接成功!"); + } else { + log.info("电子秤连接失败,将自动尝试重连..."); + } + } + + private boolean connect() { + if (!isRunning.get()) { + return false; + } + + if (!isConnecting.compareAndSet(false, true)) { + log.error("[连接] 已有连接任务正在执行,跳过"); + return false; + } + + try { + closeSerialPort(); + + SerialPort newPort = SerialPort.getCommPort(PORT_NAME); + newPort.setBaudRate(BAUD_RATE); + newPort.setNumDataBits(DATA_BITS); + newPort.setNumStopBits(STOP_BITS); + newPort.setParity(SerialPort.NO_PARITY); + newPort.setComPortTimeouts(TIMEOUT, TIMEOUT, TIMEOUT); + + if (!newPort.openPort()) { + int attempts = reconnectAttempts.incrementAndGet(); + int delay = calculateReconnectDelay(); + log.info("[连接失败] 无法打开串口 {} (第{}次尝试,下次重试间隔: {}ms)", PORT_NAME, attempts, delay); + return false; + } + + serialPort = newPort; + reconnectAttempts.set(0); + currentReconnectDelay.set(INITIAL_RECONNECT_DELAY); + lastDataTime.set(System.currentTimeMillis()); + + log.info("[连接成功] 串口 {} 已打开", PORT_NAME); + + startDataListener(); + + return true; + } catch (Exception e) { + log.error("[连接异常] {}", e.getMessage()); + return false; + } finally { + isConnecting.set(false); + } + } + + private int calculateReconnectDelay() { + int delay = currentReconnectDelay.get(); + int newDelay = Math.min(delay * 2, MAX_RECONNECT_DELAY); + currentReconnectDelay.set(newDelay); + return newDelay; + } + + private void closeSerialPort() { + SerialPort oldPort = serialPort; + serialPort = null; + + if (oldPort != null) { + try { + if (oldPort.isOpen()) { + oldPort.closePort(); + } + } catch (Exception e) { + log.error("[关闭串口] 异常: {}", e.getMessage()); + } + } + } + + private void startDataListener() { + SerialPort port = serialPort; + if (port == null || !port.isOpen()) { + return; + } + + port.addDataListener(new SerialPortDataListener() { + @Override + public int getListeningEvents() { + return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; + } + + @Override + public void serialEvent(SerialPortEvent event) { + if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { + return; + } + + try { + SerialPort currentPort = serialPort; + if (currentPort == null) { + return; + } + + int bytesAvailable = currentPort.bytesAvailable(); + if (bytesAvailable <= 0) { + return; + } + + int bytesToRead = Math.min(bytesAvailable, BUFFER_SIZE); + int numRead = currentPort.readBytes(readBuffer, bytesToRead); + + if (numRead > 0) { + lastDataTime.set(System.currentTimeMillis()); + String data = new String(readBuffer, 0, numRead, SCALE_CHARSET); + log.info("[数据] {}", data.trim()); + } + } catch (Exception e) { + log.error("[读取异常] {}", e.getMessage()); + triggerReconnect(); + } + } + }); + } + + private void startHealthCheck() { + log.info("[健康检查] 启动,间隔 {} 秒", HEALTH_CHECK_INTERVAL / 1000); + + executorService.scheduleAtFixedRate(() -> { + if (!isRunning.get()) { + return; + } + + try { + SerialPort port = serialPort; + + if (port == null) { + log.error("[健康检查] 串口对象为空"); + if (reconnectAttempts.get() < 10) { + connect(); + } + return; + } + + boolean isOpen = port.isOpen(); + log.error("[健康检查] 串口状态: {}", isOpen ? "已打开" : "已关闭"); + + if (!isOpen) { + log.info("[健康检查] 检测到串口关闭,尝试重连..."); + connect(); + } + } catch (Exception e) { + log.error("[健康检查异常] {}", e.getMessage()); + } + }, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL, TimeUnit.MILLISECONDS); + } + + private void startConnectionMonitor() { + log.info("[连接监控] 启动,间隔 3 秒"); + + executorService.scheduleAtFixedRate(() -> { + if (!isRunning.get()) { + return; + } + + try { + long currentTime = System.currentTimeMillis(); + long timeSinceLastData = currentTime - lastDataTime.get(); + + if (timeSinceLastData > DATA_TIMEOUT) { + SerialPort port = serialPort; + + if (port != null && port.isOpen()) { + try { + int bytesAvailable = port.bytesAvailable(); + + if (bytesAvailable < 0) { + log.info("[连接监控] 检测到连接已断开,尝试重连..."); + triggerReconnect(); + } else { + log.error("[连接监控] 超时无数据,但连接正常,等待数据..."); + } + } catch (Exception e) { + log.error("[连接监控] 检测到连接异常: {}", e.getMessage()); + triggerReconnect(); + } + } + } + } catch (Exception e) { + log.error("[连接监控异常] {}", e.getMessage()); + } + }, 3000, 3000, TimeUnit.MILLISECONDS); + } + + private void triggerReconnect() { + if (isConnecting.compareAndSet(false, true)) { + isConnecting.set(false); + connect(); + } + } + + public void stop() { + if (!isRunning.compareAndSet(true, false)) { + return; + } + + log.info("========================================"); + log.info("停止电子秤线程..."); + log.info("========================================"); + + try { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + if (!executorService.awaitTermination(3, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } + } catch (InterruptedException e) { + if (executorService != null) { + executorService.shutdownNow(); + } + Thread.currentThread().interrupt(); + } + + closeSerialPort(); + + log.info("线程已完全停止"); + } + + +}