| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.hwtd.mes.collect.handler; |
| | | |
| | | import com.hwtd.mes.collect.dto.SerialPortDTO; |
| | | import com.fazecast.jSerialComm.SerialPort; |
| | | import com.fazecast.jSerialComm.SerialPortDataListener; |
| | | import com.fazecast.jSerialComm.SerialPortEvent; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import javax.annotation.PreDestroy; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | |
| | | /** |
| | | * 串å£çå¬ï¼æå¨æ¨¡å¼ï¼ |
| | | * <p> |
| | | * ä¸åæ¥â项ç®å¯å¨å°±å¼å§çå¬âçæ¹å¼ä¸åï¼ç°å¨æ¹ä¸ºæå¨çå¬ï¼ |
| | | * <ul> |
| | | * <li>è°ç¨é鿥å£ï¼/lims/getFile ä¸ fileExtension=.serialPortï¼æ¶å¼å¯çå¬ï¼ä¸²å£æ²¡ææ°æ®æ¶æ¥å£è¿å空éåï¼ç±åç«¯è½®è¯¢åæ°ï¼</li> |
| | | * <li>ä¹å¯ä»¥è°ç¨ /lims/openSerialPort æå¨å¼å¯çå¬ï¼</li> |
| | | * <li>ééç»æåè°ç¨ /lims/closeSerialPort æå¨å
³éçå¬ï¼å¹çï¼æªå¼å¯æ¶è°ç¨ä¹ä¸ä¼æ¥éï¼ï¼</li> |
| | | * <li>项ç®åæ¢æ¶èªå¨å
³éçå¬ã</li> |
| | | * </ul> |
| | | * 串å£åæ°ï¼ä¸²å£åãæ³¢ç¹çãæ°æ®ä½ã忢ä½ãæ ¡éªä½ãæµæ§ã读åè¶
æ¶ãç»ææ å¿ãåè½®æ¡æ°ä¸éï¼å
¨é¨ç±æ¥å£ä¼ å
¥ï¼ |
| | | * ä¸ä¼ æ¶ä½¿ç¨ {@link SerialPortDTO} éçé»è®¤å¼ï¼åæ°åå½åçæçåæ°ä¸ä¸è´æ¶ä¼èªå¨ææ°åæ°éæ°æå¼ä¸²å£ï¼ |
| | | * åæ°ä¸è´åå¤ç¨å·²ç»æå¼ç串å£ï¼ä¸ä¼é夿å¼ã |
| | | * <p> |
| | | * å¹¶å说æï¼ |
| | | * <ul> |
| | | * <li>å¼å¯/å
³é串å£ç»ä¸ç± {@link #portLock} ä¿è¯äºæ¥ï¼éå¤è°ç¨åªä¼çæ£æå¼ä¸æ¬¡ä¸²å£ï¼ |
| | | * å¤çº¿ç¨å¹¶åè°ç¨é鿥å£ä¹ä¸ä¼é夿å¼ãé夿³¨åçå¬å¨ï¼</li> |
| | | * <li>ééå°çæ°æ®æ¾å¨åæ¥éåä¸ï¼å¤çº¿ç¨åæ¶åæ°æ¶æ¯æ¡æ°æ®åªä¼è¢«ä¸ä¸ªçº¿ç¨åèµ°ï¼ä¸ä¼éå¤ãä¸ä¼ä¸¢å¤±ï¼</li> |
| | | * <li>çå¬ç¶æä½¿ç¨ volatile æ è®°ï¼å
³é串å£ååè°çº¿ç¨ä¸ä¼åå¤çæ°æ®ã</li> |
| | | * </ul> |
| | | * æ°æ®ç¼å说æï¼ééå°çæ°æ®æéåç¼åï¼æå
¥ææ°ç䏿¡ï¼è¶
åº maxCount æ¡ï¼é»è®¤ 6 æ¡ï¼æ¶ç§»é¤ææ§ç䏿¡ï¼ |
| | | * å æ¤æ¥å£æ¯æ¬¡æå¤è¿å maxCount æ¡ææ°çæ°æ®ï¼ææ¥æ¶å
å顺åºï¼ï¼æ°æ®è¢«åèµ°åé忏
空ï¼éæ°å¼å§ç¼åã |
| | | * <p> |
| | | * 帧åå说æï¼é»è®¤æç»ææ å¿ endMark ååæ°æ®å¸§ï¼å¦æè®¾å¤æåä¸å¸§ä¸å¸¦ç»ææ å¿ï¼ä¾å¦æ¯å¸§ä»¥ * å¼å¤´è䏿¯ç»å°¾ï¼ï¼ |
| | | * 串å£ç©ºé² frameIdleMillis 毫ç§ï¼é»è®¤ 200msï¼åä¼ææ¥æ¶ç¼å²åºéå©ä¸çæ°æ®ä½ä¸ºä¸å¸§ååºï¼é¿å
æ¼ææå䏿¡ã |
| | | * |
| | | * @author data-acquisition |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | public class SerialPortListener { |
| | | |
| | | /** ä¸ç´æ¶ä¸å°ç»ææ å¿æ¶æ¥æ¶ç¼å²åºçæå¤§é¿åº¦ï¼è¶
è¿å丢å¼ï¼é²æ¢å
åæ éå¢é¿ */ |
| | | private static final int MAX_RECEIVE_BUFFER_LENGTH = 4096; |
| | | |
| | | /** |
| | | * 串å£å¼å
³éï¼å¼å¯ / å
³é串å£äºæ¥ã |
| | | * å¤çº¿ç¨å¹¶åè°ç¨é鿥壿¶ï¼åªæä¸ä¸ªçº¿ç¨è½çæ£æå¼ä¸²å£ï¼å
¶å®çº¿ç¨å¤ç¨å·²ç»æå¼ç串å£ã |
| | | */ |
| | | private final ReentrantLock portLock = new ReentrantLock(); |
| | | |
| | | /** 串å£å¯¹è±¡ï¼åªå¨ææ {@link #portLock} æ¶å建åå
³éï¼å¤é¨åªåç¶æå¤æ */ |
| | | private volatile SerialPort serialPort; |
| | | |
| | | /** æ¯å¦æ£å¨çå¬ï¼volatileï¼ä¸²å£åè°çº¿ç¨ä¸ä¸å¡çº¿ç¨ä¹é´ç«å³å¯è§ï¼ */ |
| | | private volatile boolean listening = false; |
| | | |
| | | /** å½åçæç串å£åæ°ï¼åªå¨ææ {@link #portLock} æ¶åæ´ */ |
| | | private volatile ActiveSetting activeSetting; |
| | | |
| | | /** æå䏿¬¡æ¶å°ä¸²å£æ°æ®çæ¶é´ï¼ç¨äºå¤æä¸²å£æ¯å¦å·²ç»ç©ºé²ï¼ç©ºé²è¡¥å¸§ï¼ */ |
| | | private volatile long lastDataTime = 0L; |
| | | |
| | | /** 空é²è¡¥å¸§ä»»å¡ï¼è®¾å¤æåä¸å¸§ä¸å¸¦ç»ææ å¿æ¶ï¼é 宿å©ä½æ°æ®ååºæ¥ */ |
| | | private final ScheduledExecutorService frameFlushExecutor = Executors.newSingleThreadScheduledExecutor(runnable -> { |
| | | Thread thread = new Thread(runnable, "serial-port-frame-flush"); |
| | | thread.setDaemon(true); |
| | | return thread; |
| | | }); |
| | | |
| | | /** æ¥æ¶ç¼å²åºï¼ç±ä¸²å£åè°çº¿ç¨åå
¥ï¼ç¨èªèº«ä½ä¸ºéä¿æ¤ */ |
| | | private final StringBuilder receiveBuffer = new StringBuilder(); |
| | | |
| | | /** |
| | | * ééå°çæ°æ®ï¼ä¸²å£åè°çº¿ç¨åå
¥ï¼ä¸å¡çº¿ç¨è¯»åå¹¶æ¸
空ï¼ä½¿ç¨åæ¥éåä¿è¯çº¿ç¨å®å
¨ã |
| | | * éåå¼ç¼åï¼ä»å°¾é¨æå
¥ææ°ç䏿¡ï¼è¶
åº maxCount æ¶ä»å¤´é¨ç§»é¤ææ§ç䏿¡ï¼æå¤ä¿ç maxCount æ¡æ°æ®ã |
| | | */ |
| | | private final List<String> dataList = Collections.synchronizedList(new ArrayList<>()); |
| | | |
| | | /** |
| | | * å¼å¯ä¸²å£çå¬ï¼å¹çæä½ï¼ |
| | | * <p> |
| | | * å¤çº¿ç¨åæ¶è°ç¨æ¶ï¼ç¬¬ä¸ä¸ªçº¿ç¨çæ£æå¼ä¸²å£å¹¶æ³¨åçå¬å¨ï¼å
¶å®çº¿ç¨ç´æ¥å¤ç¨ï¼ä¸ä¼é夿å¼ä¸²å£ã |
| | | * 串å£åæ°ä¸å½åçæåæ°ä¸ä¸è´æ¶ä¼éæ°æå¼ä¸²å£ï¼ä¸²å£è¢«æåºå¯¼è´å¼å¸¸æå¼æ¶ï¼ä¸æ¬¡è°ç¨ä¼èªå¨éæ°æå¼ã |
| | | * |
| | | * @param setting æ¥å£ä¼ å
¥ç串å£åæ°ï¼ä¸º null æ¶å
¨é¨ä½¿ç¨é»è®¤å¼ |
| | | * @return true=çå¬å·²å¼å¯ï¼å
嫿¬æ¬¡è°ç¨ä¹å就已å¼å¯çæ
åµï¼ï¼false=å¼å¯å¤±è´¥ï¼ä¸²å£ä¸åå¨ã被å ç¨ï¼æä¸²å£åè½æªå¯ç¨ï¼ |
| | | * @throws IllegalArgumentException 串å£åæ°éæ³ï¼ç± GlobalExceptionHandler è¿åç»åç«¯ï¼ |
| | | */ |
| | | public boolean startListening(SerialPortDTO setting) { |
| | | // åæ°æ ¡éªãè¡¥é»è®¤å¼æ¾å¨å éä¹åï¼åæ°ä¸å¯¹ç´æ¥æå¼å¸¸ï¼ä¸å ç¨ä¸²å£ |
| | | ActiveSetting requested = toActiveSetting(setting); |
| | | portLock.lock(); |
| | | try { |
| | | // å·²ç»å¨çå¬ã䏲壿£å¸¸ä¸åæ°æ²¡æååï¼ç´æ¥å¤ç¨ï¼éå¤å¼å¯çå¯ä¸å¤å®ç¹ï¼ |
| | | if (listening && isPortOpen() && requested.equals(activeSetting)) { |
| | | log.debug("ä¸²å£ {} çå¬å·²å¼å¯ä¸åæ°æªååï¼æ¬æ¬¡è°ç¨ç´æ¥å¤ç¨", requested.getSerialPortName()); |
| | | return true; |
| | | } |
| | | if (activeSetting != null && !requested.equals(activeSetting)) { |
| | | log.info("串å£åæ°åçååï¼ææ°åæ°éæ°æå¼ä¸²å£ï¼{} -> {}", activeSetting, requested); |
| | | } |
| | | // 馿¬¡å¼å¯ãåæ°ååã䏿¬¡æå¼å¤±è´¥ãæä¸²å£å¼å¸¸æå¼æ¶ï¼å
æ¸
çæ®çç¶æåéæ°æå¼ |
| | | closePortInternal(); |
| | | if (!openPort(requested)) { |
| | | return false; |
| | | } |
| | | activeSetting = requested; |
| | | log.info("ä¸²å£ {} çå¬å·²å¼å¯ï¼{}ï¼", requested.getSerialPortName(), requested.describe()); |
| | | return true; |
| | | } finally { |
| | | portLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å
³é串å£çå¬ï¼å¹çæä½ï¼ |
| | | * |
| | | * @return true=å·²å
³éï¼å
嫿¬æ¬¡è°ç¨ä¹åå°±æªå¼å¯çæ
åµï¼ï¼false=å
³éè¿ç¨ä¸åºç°å¼å¸¸ |
| | | */ |
| | | public boolean closeListening() { |
| | | portLock.lock(); |
| | | try { |
| | | boolean success = closePortInternal(); |
| | | if (success) { |
| | | log.info("ä¸²å£ {} çå¬å·²å
³é", getListenName()); |
| | | } else { |
| | | log.warn("ä¸²å£ {} çå¬å
³é失败ï¼è¯·æ£æ¥ä¸²å£ç¶æ", getListenName()); |
| | | } |
| | | return success; |
| | | } finally { |
| | | portLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åèµ°å½åééå°çæ°æ®ï¼åèµ°åæ¸
空ç¼å²åºï¼ï¼ä¾é鿥å£è°ç¨ã |
| | | * å é + å¿«ç
§ + æ¸
空æ¯ä¸ä¸ªååæä½ï¼å¤çº¿ç¨å¹¶ååæ°æ¶å䏿¡æ°æ®åªä¼è¢«ä¸ä¸ªçº¿ç¨åèµ°ã |
| | | * |
| | | * @return æ¬æ¬¡åèµ°çæ°æ®ï¼æ²¡ææ°æ®æ¶è¿å空éå |
| | | */ |
| | | public List<String> drainData() { |
| | | synchronized (dataList) { |
| | | if (dataList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | List<String> snapshot = new ArrayList<>(dataList); |
| | | dataList.clear(); |
| | | log.info("ä¸²å£ {} æ¬æ¬¡åèµ° {} æ¡æ°æ®", getListenName(), snapshot.size()); |
| | | return snapshot; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @return æ¯å¦æ£å¨çå¬ |
| | | */ |
| | | public boolean isListening() { |
| | | return listening && isPortOpen(); |
| | | } |
| | | |
| | | /** |
| | | * @return 䏲壿¯å¦å·²ç»æå¼ |
| | | */ |
| | | public boolean isPortOpen() { |
| | | SerialPort port = serialPort; |
| | | return port != null && port.isOpen(); |
| | | } |
| | | |
| | | /** |
| | | * @return å½åç¼å²åºä¸è¿æªè¢«åèµ°çæ°æ®æ¡æ° |
| | | */ |
| | | public int dataSize() { |
| | | synchronized (dataList) { |
| | | return dataList.size(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @return å½åçæç串å£åç§°ï¼è¿æ²¡å¼å¯è¿ç嬿¶è¿åé
ç½®çé»è®¤ä¸²å£åç§° |
| | | */ |
| | | public String getListenName() { |
| | | ActiveSetting current = activeSetting; |
| | | return current != null ? current.getSerialPortName() : ""; |
| | | } |
| | | |
| | | /** |
| | | * 项ç®åæ¢æ¶å
³éä¸²å£ |
| | | */ |
| | | @PreDestroy |
| | | public void destroy() { |
| | | log.info("项ç®åæ¢ï¼å
³éä¸²å£ {} çå¬", getListenName()); |
| | | closeListening(); |
| | | frameFlushExecutor.shutdownNow(); |
| | | } |
| | | |
| | | /** |
| | | * ææ¥å£ä¼ å
¥ç串å£åæ°è¡¥é½é»è®¤å¼å¹¶æ ¡éªï¼è½¬æ¢æå½åä¼è¯ä½¿ç¨çåæ°å¿«ç
§ |
| | | * |
| | | * @param setting æ¥å£ä¼ å
¥çåæ°ï¼ä¸º null æ¶å
¨é¨ä½¿ç¨é»è®¤å¼ |
| | | * @return å½ä¸ååç串å£åæ° |
| | | * @throws IllegalArgumentException åæ°éæ³ |
| | | */ |
| | | private ActiveSetting toActiveSetting(SerialPortDTO setting) { |
| | | SerialPortDTO source = setting == null ? new SerialPortDTO() : setting; |
| | | String portName = isBlank(source.getSerialPortName()) ? "" : source.getSerialPortName().trim(); |
| | | if (isBlank(portName)) { |
| | | throw new IllegalArgumentException("串å£åç§°ä¸è½ä¸ºç©ºï¼è¯·éè¿ listenName åæ°ä¼ å
¥"); |
| | | } |
| | | int baudRate = intOrDefault(source.getBaudRate(), SerialPortDTO.DEFAULT_BAUD_RATE); |
| | | if (baudRate <= 0) { |
| | | throw new IllegalArgumentException("串å£åæ° baudRate éæ³ï¼" + baudRate + "ï¼å¿
é¡»å¤§äº 0"); |
| | | } |
| | | int dataBits = checkRange("dataBits", intOrDefault(source.getDataBits(), SerialPortDTO.DEFAULT_DATA_BITS), 5, 8); |
| | | int stopBits = checkRange("stopBits", intOrDefault(source.getStopBits(), SerialPortDTO.DEFAULT_STOP_BITS), 1, 3); |
| | | int readTimeout = intOrDefault(source.getReadTimeout(), SerialPortDTO.DEFAULT_READ_TIMEOUT); |
| | | if (readTimeout < 0) { |
| | | throw new IllegalArgumentException("串å£åæ° readTimeout éæ³ï¼" + readTimeout + "ï¼ä¸è½å°äº 0"); |
| | | } |
| | | int maxCount = intOrDefault(source.getMaxCount(), SerialPortDTO.DEFAULT_MAX_COUNT); |
| | | if (maxCount < 1) { |
| | | throw new IllegalArgumentException("串å£åæ° maxCount éæ³ï¼" + maxCount + "ï¼ä¸è½å°äº 1"); |
| | | } |
| | | int frameIdleMillis = intOrDefault(source.getFrameIdleMillis(), SerialPortDTO.DEFAULT_FRAME_IDLE_MILLIS); |
| | | if (frameIdleMillis < 0) { |
| | | throw new IllegalArgumentException("串å£åæ° frameIdleMillis éæ³ï¼" + frameIdleMillis + "ï¼ä¸è½å°äº 0"); |
| | | } |
| | | String endMark = isBlank(source.getEndMark()) ? SerialPortDTO.DEFAULT_END_MARK : source.getEndMark(); |
| | | String parity = isBlank(source.getParity()) ? SerialPortDTO.DEFAULT_PARITY : source.getParity(); |
| | | String flowControl = isBlank(source.getFlowControl()) ? SerialPortDTO.DEFAULT_FLOW_CONTROL : source.getFlowControl(); |
| | | return new ActiveSetting(portName, baudRate, dataBits, stopBits, |
| | | parseParity(parity), parseFlowControl(flowControl), readTimeout, endMark, maxCount, frameIdleMillis); |
| | | } |
| | | |
| | | /** |
| | | * æå¼ä¸²å£å¹¶æ³¨åæ°æ®çå¬å¨ï¼è°ç¨æ¹å¿
é¡»ææ {@link #portLock}ï¼ |
| | | * |
| | | * @return æ¯å¦æå¼æå |
| | | */ |
| | | private boolean openPort(ActiveSetting setting) { |
| | | SerialPort port; |
| | | try { |
| | | port = SerialPort.getCommPort(setting.getSerialPortName()); |
| | | port.setBaudRate(setting.getBaudRate()); // æ³¢ç¹ç |
| | | port.setNumDataBits(setting.getDataBits()); // æ°æ®ä½ |
| | | port.setNumStopBits(setting.getStopBits()); // åæ¢ä½ |
| | | port.setParity(setting.getParity()); // æ ¡éªä½ |
| | | port.setFlowControl(setting.getFlowControl()); // æµæ§ |
| | | port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, setting.getReadTimeout(), 0); // 读åè¶
æ¶ |
| | | |
| | | if (!port.openPort()) { |
| | | log.error("ä¸²å£ {} æå¼å¤±è´¥ï¼è¯·æ£æ¥ä¸²å£æ¯å¦åå¨ãæ¯å¦è¢«å
¶ä»ç¨åºå ç¨", setting.getSerialPortName()); |
| | | return false; |
| | | } |
| | | } catch (Throwable t) { |
| | | // 串å£åç§°éæ³ãåæ°ä¸è¢«é©±å¨æ¯æã驱å¨å¼å¸¸çæ
åµç»ä¸è¿å失败ï¼é¿å
æå¼å¸¸æç»è°ç¨æ¹ï¼HTTP 线ç¨ï¼ |
| | | log.error("ä¸²å£ {} æå¼å¼å¸¸ï¼{}", setting.getSerialPortName(), t.getMessage(), t); |
| | | return false; |
| | | } |
| | | serialPort = port; |
| | | // æ°ä¸è½®ééå¼å§ï¼æ¸
æä¸ä¸è½®æªæ¥æ¶å®æ´çåå
åæ²¡è¢«åèµ°çæ°æ®ï¼é¿å
ä¸ä¸è½®çæ°æ®æ··å
¥æ¬è½®ç»æ |
| | | synchronized (receiveBuffer) { |
| | | receiveBuffer.setLength(0); |
| | | } |
| | | discardCollectedData(); |
| | | // å
置为çå¬ä¸å注åçå¬å¨ï¼é¿å
注å宿åç¬¬ä¸æ¹æ°æ®å ç¶ææªå°±ç»ªè¢«ä¸¢å¼ |
| | | listening = true; |
| | | try { |
| | | port.addDataListener(buildDataListener(port, setting)); |
| | | } catch (Exception e) { |
| | | log.error("ä¸²å£ {} æ³¨åæ°æ®çå¬å¨å¤±è´¥", setting.getSerialPortName(), e); |
| | | // 注å失败æ¶åæ»ï¼é¿å
串å£è¢«å ç¨å´è¯»ä¸å°æ°æ® |
| | | closePortInternal(); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * å
³é串å£å¹¶éæ¾èµæºï¼è°ç¨æ¹å¿
é¡»ææ {@link #portLock}ï¼æ¬æ¹æ³ä¸ä¼æåºå¼å¸¸ï¼ |
| | | * |
| | | * @return æ¯å¦æ£å¸¸å
³é |
| | | */ |
| | | private boolean closePortInternal() { |
| | | SerialPort port = serialPort; |
| | | serialPort = null; |
| | | // å
置为æªçå¬ï¼ä¸²å£åè°çº¿ç¨ä¸åå¤çåç»æ°æ® |
| | | listening = false; |
| | | boolean success = true; |
| | | if (port != null) { |
| | | try { |
| | | port.removeDataListener(); // ç§»é¤çå¬ï¼é¿å
å
³éè¿ç¨ä¸åè°è¿å¨è§¦å |
| | | if (port.isOpen() && !port.closePort()) { |
| | | log.warn("ä¸²å£ {} å
³éè¿å失败ï¼è¯·ç¡®è®¤ä¸²å£æ¯å¦å·²è¢«æåº", getListenName()); |
| | | success = false; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("å
³éä¸²å£ {} åºç°å¼å¸¸", getListenName(), e); |
| | | success = false; |
| | | } |
| | | } |
| | | // 䏢弿ªæ¥æ¶å®æ´çåå
æ°æ®ï¼é¿å
䏿¬¡å¼å¯æ¶æ¼åºéè¯¯æ°æ® |
| | | synchronized (receiveBuffer) { |
| | | receiveBuffer.setLength(0); |
| | | } |
| | | return success; |
| | | } |
| | | |
| | | /** |
| | | * 丢å¼ä¸ä¸è½®æ²¡æåèµ°çæ°æ®ï¼è°ç¨æ¹å¿
é¡»ææ {@link #portLock}ï¼ |
| | | */ |
| | | private void discardCollectedData() { |
| | | List<String> abandoned = drainData(); |
| | | if (!abandoned.isEmpty()) { |
| | | log.warn("ä¸²å£ {} å¼å¯æ°ä¸è½®çå¬ï¼ä¸¢å¼ä¸ä¸è½®æªè¢«åèµ°ç {} æ¡æ°æ®ï¼{}", getListenName(), abandoned.size(), abandoned); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå»ºä¸²å£æ°æ®çå¬å¨ï¼æ°æ®å°è¾¾æ¶å¨ jSerialComm ççå¬çº¿ç¨ä¸åè° |
| | | * <p> |
| | | * æ¬è½®ä½¿ç¨çåæ°ï¼ç»ææ å¿ãæ¡æ°ä¸éï¼ç´æ¥åºåå¨çå¬å¨éï¼é¿å
è§£ææ¶è¯»å°ä¸ä¸è½®çåæ°ã |
| | | */ |
| | | private SerialPortDataListener buildDataListener(final SerialPort port, final ActiveSetting setting) { |
| | | return 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; |
| | | } |
| | | // å
³é串å£åå¯è½è¿æä¸æ¬¡åè°å¨è·¯ä¸ï¼è¿éåå
åºå¤æä¸æ¬¡ |
| | | if (!listening || !setting.equals(activeSetting)) { |
| | | return; |
| | | } |
| | | try { |
| | | int available = port.bytesAvailable(); |
| | | if (available <= 0) { |
| | | return; |
| | | } |
| | | // 读åç¼å²åºä¸çææåè |
| | | byte[] buffer = new byte[available]; |
| | | int readBytes = port.readBytes(buffer, buffer.length); |
| | | if (readBytes > 0) { |
| | | // ææ¬æ¬¡æ¶å°çé¶æ£æ°æ®è½¬æå符串ï¼è¿½å å°ç¼å²åº |
| | | parseAndCollect(setting, new String(buffer, 0, readBytes, StandardCharsets.UTF_8)); |
| | | } |
| | | } catch (Exception e) { |
| | | // 忬¡è¯»åå¼å¸¸ä¸å½±ååç»çå¬ |
| | | log.error("ä¸²å£ {} è¯»åæ°æ®å¼å¸¸ï¼{}", setting.getSerialPortName(), e.getMessage(), e); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * è§£ææ¥æ¶å°çæ°æ®ï¼æç»ææ å¿ååï¼è§£æææ°å¼åæ¾å
¥ééç»æ |
| | | * |
| | | * @param setting æ¬è½®éé使ç¨ç串å£åæ° |
| | | * @param received æ¬æ¬¡ä»ä¸²å£è¯»å°çå符串 |
| | | */ |
| | | private void parseAndCollect(ActiveSetting setting, String received) { |
| | | List<String> completeValues = new ArrayList<>(); |
| | | synchronized (receiveBuffer) { |
| | | // è®°å½æ¶å°æ°æ®çæ¶é´ï¼ç©ºé²è¡¥å¸§é å®å¤æä¸²å£æ¯å¦å·²ç»æ²¡ææ°æ°æ® |
| | | lastDataTime = System.currentTimeMillis(); |
| | | receiveBuffer.append(received); |
| | | // æ£æ¥ç¼å²åºæ¯å¦å
å«âç»ææ å¿âï¼å¾ªç¯å¤çææå®æ´æ°æ® |
| | | int endIndex = receiveBuffer.indexOf(setting.getEndMark()); |
| | | while (endIndex != -1) { |
| | | // æåâå¼å¤´å°ç»ææ å¿âç宿´æ°æ® |
| | | String completeData = normalizeFrame(receiveBuffer.substring(0, endIndex)); |
| | | // ç§»é¤ç¼å²åºä¸å·²å¤ççé¨åï¼ä¿çå©ä½æªå®æçå
å®¹ï¼ |
| | | receiveBuffer.delete(0, endIndex + setting.getEndMark().length()); |
| | | if (!completeData.isEmpty()) { |
| | | try { |
| | | completeValues.add(completeData); |
| | | } catch (NumberFormatException e) { |
| | | // å¹²æ°æ°æ®åªå¿½ç¥å½åè¿ä¸æ¡ï¼ä¸å½±ååç»æ°æ®è§£æ |
| | | log.warn("ä¸²å£ {} æ¶å°æ æ³è§£æçæ°æ®ï¼å·²å¿½ç¥ï¼[{}]", setting.getSerialPortName(), completeData); |
| | | } |
| | | } |
| | | endIndex = receiveBuffer.indexOf(setting.getEndMark()); |
| | | } |
| | | // ä¸ç´æ²¡æç»ææ å¿æ¶ç¼å²åºä¼æç»å¢é¿ï¼è¶
è¿ä¸éç´æ¥ä¸¢å¼ï¼é²æ¢å
åæ³æ¼ |
| | | if (receiveBuffer.length() > MAX_RECEIVE_BUFFER_LENGTH) { |
| | | log.warn("ä¸²å£ {} æ¥æ¶ç¼å²åºè¶
è¿ {} 个åç¬¦ä»æªæ¶å°ç»ææ å¿ï¼å·²ä¸¢å¼ç¼åæ°æ®", |
| | | setting.getSerialPortName(), MAX_RECEIVE_BUFFER_LENGTH); |
| | | receiveBuffer.setLength(0); |
| | | } |
| | | } |
| | | if (completeValues.isEmpty()) { |
| | | return; |
| | | } |
| | | enqueue(setting, completeValues); |
| | | } |
| | | |
| | | /** |
| | | * ææ°æ®æ¾å
¥ç¼åéåï¼ä»å°¾é¨æå
¥ææ°ç䏿¡ï¼è¶
åº maxCount æ¶ä»å¤´é¨ç§»é¤ææ§ç䏿¡ |
| | | */ |
| | | private void enqueue(ActiveSetting setting, List<String> values) { |
| | | synchronized (dataList) { |
| | | for (String value : values) { |
| | | dataList.add(value); |
| | | while (dataList.size() > setting.getMaxCount()) { |
| | | String removed = dataList.remove(0); |
| | | log.debug("ä¸²å£ {} ç¼å已满 {} æ¡ï¼ç§»é¤ææ§ç䏿¡æ°æ® {}", |
| | | setting.getSerialPortName(), setting.getMaxCount(), removed); |
| | | } |
| | | log.info("ä¸²å£ {} æ¥æ¶å°æ°æ®-->{}", setting.getSerialPortName(), value); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯å¨ç©ºé²è¡¥å¸§ä»»å¡ï¼è®¾å¤æåä¸å¸§ä¸å¸¦ç»ææ å¿æ¶ï¼é 宿å©ä½æ°æ®ååºæ¥ |
| | | */ |
| | | @PostConstruct |
| | | void startFrameFlushTask() { |
| | | frameFlushExecutor.scheduleWithFixedDelay(this::flushIdleFrame, 100, 100, TimeUnit.MILLISECONDS); |
| | | } |
| | | |
| | | /** |
| | | * 空é²è¡¥å¸§ï¼ä¸²å£è¶
è¿ frameIdleMillis æ²¡ææ°æ°æ®æ¶ï¼ææ¥æ¶ç¼å²åºéå©ä¸çæ°æ®å½ä½ä¸å¸§ååºæ¥ã |
| | | * <p> |
| | | * å
¸ååºæ¯ï¼è®¾å¤æ¯å¸§ä»¥æä¸ªå符âå¼å¤´âè䏿¯âç»å°¾âï¼å¦ *0 1 0ï¼ï¼ |
| | | * è¿æ¶æåä¸å¸§ä¸ä¼æåç»æ°æ®æ¥è§¦åååï¼åªé ç»ææ å¿æ°¸è¿åä¸åºæ¥ã |
| | | * å¦æè®¾å¤æ¯å¸§é½å¸¦ç»ææ å¿ï¼ç¼å²åºå¨æ¯æ¬¡ååå齿¯ç©ºçï¼è¿éä¸ä¼åä»»ä½äºã |
| | | */ |
| | | private void flushIdleFrame() { |
| | | try { |
| | | ActiveSetting setting = activeSetting; |
| | | if (!listening || setting == null || setting.getFrameIdleMillis() <= 0) { |
| | | return; |
| | | } |
| | | String pending; |
| | | synchronized (receiveBuffer) { |
| | | if (receiveBuffer.length() == 0) { |
| | | return; |
| | | } |
| | | long idle = System.currentTimeMillis() - lastDataTime; |
| | | if (idle < setting.getFrameIdleMillis()) { |
| | | return; |
| | | } |
| | | pending = normalizeFrame(receiveBuffer.toString()); |
| | | receiveBuffer.setLength(0); |
| | | log.info("ä¸²å£ {} å·²ç©ºé² {}ms æ²¡ææ¶å°æ°çç»ææ å¿ï¼æç¼å²åºå©ä½æ°æ® [{}] ä½ä¸ºä¸å¸§ååº", |
| | | setting.getSerialPortName(), idle, pending); |
| | | } |
| | | if (!pending.isEmpty()) { |
| | | enqueue(setting, Collections.singletonList(pending)); |
| | | } |
| | | } catch (Exception e) { |
| | | // 补帧å¼å¸¸ä¸è½å½±ååç»è¡¥å¸§ |
| | | log.error("串å£ç©ºé²è¡¥å¸§å¼å¸¸ï¼{}", e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å»ææ°æ®éçæ¢è¡åé¦å°¾ç©ºç½ |
| | | */ |
| | | private static String normalizeFrame(String value) { |
| | | return value.replace("\n", "").replace("\r", "").trim(); |
| | | } |
| | | |
| | | /** |
| | | * è§£ææ ¡éªä½åæ°ï¼æ¯æ NONE/ODD/EVEN/MARK/SPACEï¼ä¸åºå大å°åï¼ï¼ä¹å
¼å®¹ 0~4 çæ°å |
| | | */ |
| | | private static int parseParity(String parity) { |
| | | String value = parity.trim().toUpperCase(Locale.ROOT); |
| | | switch (value) { |
| | | case "NONE": |
| | | case "N": |
| | | case "NO": |
| | | return SerialPort.NO_PARITY; |
| | | case "ODD": |
| | | case "O": |
| | | return SerialPort.ODD_PARITY; |
| | | case "EVEN": |
| | | case "E": |
| | | return SerialPort.EVEN_PARITY; |
| | | case "MARK": |
| | | case "M": |
| | | return SerialPort.MARK_PARITY; |
| | | case "SPACE": |
| | | case "S": |
| | | return SerialPort.SPACE_PARITY; |
| | | default: |
| | | int number = parseInt(value, -1); |
| | | if (number >= SerialPort.NO_PARITY && number <= SerialPort.SPACE_PARITY) { |
| | | return number; |
| | | } |
| | | throw new IllegalArgumentException("串å£åæ° parity éæ³ï¼" + parity + "ï¼å¯é NONE/ODD/EVEN/MARK/SPACEï¼æ 0~4 çæ°å"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£ææµæ§åæ°ï¼æ¯æ NONE/RTS_CTS/XON_XOFFï¼ä¸åºå大å°åï¼ï¼ä¹å
¼å®¹ jSerialComm çæ°å常é |
| | | */ |
| | | private static int parseFlowControl(String flowControl) { |
| | | String value = flowControl.trim().toUpperCase(Locale.ROOT).replace('-', '_'); |
| | | switch (value) { |
| | | case "NONE": |
| | | case "OFF": |
| | | case "DISABLED": |
| | | return SerialPort.FLOW_CONTROL_DISABLED; |
| | | case "RTS_CTS": |
| | | case "RTSCTS": |
| | | case "HARDWARE": |
| | | return SerialPort.FLOW_CONTROL_RTS_ENABLED | SerialPort.FLOW_CONTROL_CTS_ENABLED; |
| | | case "XON_XOFF": |
| | | case "XONXOFF": |
| | | case "SOFTWARE": |
| | | return SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED | SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED; |
| | | default: |
| | | int number = parseInt(value, -1); |
| | | if (number >= 0) { |
| | | return number; |
| | | } |
| | | throw new IllegalArgumentException("串å£åæ° flowControl éæ³ï¼" + flowControl + "ï¼å¯é NONE/RTS_CTS/XON_XOFFï¼ææ°å常é"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªåå¼èå´ |
| | | */ |
| | | private static int checkRange(String name, int value, int min, int max) { |
| | | if (value < min || value > max) { |
| | | throw new IllegalArgumentException("串å£åæ° " + name + " éæ³ï¼" + value + "ï¼åå¼èå´ " + min + "~" + max); |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * æ¥å£æ²¡ä¼ ï¼nullï¼æ¶ä½¿ç¨é»è®¤å¼ |
| | | */ |
| | | private static int intOrDefault(Integer value, int defaultValue) { |
| | | return value == null ? defaultValue : value; |
| | | } |
| | | |
| | | /** |
| | | * è§£ææ°åï¼è§£æä¸äºè¿åé»è®¤å¼ |
| | | */ |
| | | private static int parseInt(String value, int defaultValue) { |
| | | try { |
| | | return Integer.parseInt(value); |
| | | } catch (NumberFormatException e) { |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | private static boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | |
| | | /** |
| | | * å½åçæç串å£åæ°ï¼æ¥å£åæ°è¡¥é»è®¤å¼ãæ ¡éªãå½ä¸åä¹åçå¿«ç
§ï¼ç¨äºå¤æåæ°æ¯å¦åçååï¼ |
| | | */ |
| | | @Data |
| | | private static final class ActiveSetting { |
| | | |
| | | /** 串å£åç§° */ |
| | | private final String serialPortName; |
| | | /** æ³¢ç¹ç */ |
| | | private final int baudRate; |
| | | /** æ°æ®ä½ */ |
| | | private final int dataBits; |
| | | /** åæ¢ä½ */ |
| | | private final int stopBits; |
| | | /** æ ¡éªä½ï¼jSerialComm 常éï¼ */ |
| | | private final int parity; |
| | | /** æµæ§ï¼jSerialComm 常éï¼ */ |
| | | private final int flowControl; |
| | | /** 读åè¶
æ¶ï¼æ¯«ç§ï¼ */ |
| | | private final int readTimeout; |
| | | /** ä¸å¸§æ°æ®çç»ææ å¿ */ |
| | | private final String endMark; |
| | | /** åè½®ééæå¤ä¿ççæ°æ®æ¡æ° */ |
| | | private final int maxCount; |
| | | /** 帧空é²è¡¥å¸§æ¶é´ï¼æ¯«ç§ï¼ï¼0 表示å
³é */ |
| | | private final int frameIdleMillis; |
| | | |
| | | /** |
| | | * æ¥å¿ç¨çåæ°æè¿° |
| | | */ |
| | | String describe() { |
| | | return "æ³¢ç¹ç " + baudRate |
| | | + "ï¼æ°æ®ä½ " + dataBits |
| | | + "ï¼åæ¢ä½ " + stopBits |
| | | + "ï¼æ ¡éªä½ " + parityName() |
| | | + "ï¼æµæ§ " + flowControlName() |
| | | + "ï¼è¯»åè¶
æ¶ " + readTimeout + "ms" |
| | | + "ï¼ç»ææ å¿ [" + endMark + "]" |
| | | + "ï¼åè½®æå¤ " + maxCount + " æ¡" |
| | | + "ï¼ç©ºé²è¡¥å¸§ " + (frameIdleMillis > 0 ? frameIdleMillis + "ms" : "å
³é"); |
| | | } |
| | | |
| | | private String parityName() { |
| | | switch (parity) { |
| | | case SerialPort.ODD_PARITY: |
| | | return "奿 ¡éª"; |
| | | case SerialPort.EVEN_PARITY: |
| | | return "å¶æ ¡éª"; |
| | | case SerialPort.MARK_PARITY: |
| | | return "MARK"; |
| | | case SerialPort.SPACE_PARITY: |
| | | return "SPACE"; |
| | | default: |
| | | return "æ æ ¡éª"; |
| | | } |
| | | } |
| | | |
| | | private String flowControlName() { |
| | | if (flowControl == SerialPort.FLOW_CONTROL_DISABLED) { |
| | | return "æ "; |
| | | } |
| | | if (flowControl == (SerialPort.FLOW_CONTROL_RTS_ENABLED | SerialPort.FLOW_CONTROL_CTS_ENABLED)) { |
| | | return "RTS/CTS"; |
| | | } |
| | | if (flowControl == (SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED | SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED)) { |
| | | return "XON/XOFF"; |
| | | } |
| | | return String.valueOf(flowControl); |
| | | } |
| | | } |
| | | } |