liuwei před 2 týdny
rodič
revize
924b61878c

+ 22 - 108
src/com/mes/plc/QmPlcMonitorPanel.java

@@ -117,15 +117,12 @@ public class QmPlcMonitorPanel extends JPanel {
     public void startMonitor() {
         active = true;
         loadUiFromConfig();
-        if (s7Client == null) {
-            s7Client = new QmS7Client(config);
-        } else if (config != null && config.host != null) {
-            s7Client.updateHost(config.host);
-        }
+        s7Client = QmPlcPoller.getSharedClient();
         if (timer != null) {
             timer.cancel();
         }
         timer = new Timer("QmPlcMonitor", true);
+        // 2 秒刷新,降低与轮询抢同一连接的频率
         timer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() {
@@ -133,7 +130,7 @@ public class QmPlcMonitorPanel extends JPanel {
                     refreshOnce();
                 }
             }
-        }, 200, 1000);
+        }, 500, 2000);
     }
 
     public void stopMonitor() {
@@ -142,10 +139,7 @@ public class QmPlcMonitorPanel extends JPanel {
             timer.cancel();
             timer = null;
         }
-        if (s7Client != null) {
-            s7Client.close();
-            s7Client = null;
-        }
+        s7Client = null;
         SwingUtilities.invokeLater(() -> statusLabel.setText("已停止刷新"));
     }
 
@@ -157,101 +151,37 @@ public class QmPlcMonitorPanel extends JPanel {
             if (config == null) {
                 config = QmPlcConfig.load();
             }
-            if (s7Client == null) {
-                s7Client = new QmS7Client(config);
-            }
-
-            final Snapshot s = new Snapshot();
-            s.inflationTime = readFloat(config.inflationTimeOffset, s);
-            s.holdTime = readFloat(config.holdTimeOffset, s);
-            s.testTime = readFloat(config.testTimeOffset, s);
-            s.inflationPressure = readFloat(config.inflationPressureOffset, s);
-            s.stablePressure = readFloat(config.stablePressureOffset, s);
-            s.leak = readFloat(config.leakOffset, s);
-            s.judgeResult = readBit(config.judgeResultByte, config.judgeResultBit, s);
-            s.frameCode = readFrame(s);
-            s.complete = readBit(config.completeByte, config.completeBit, s);
-            s.allowEntry = readBit(config.allowEntryByte, config.allowEntryBit, s);
-            s.scanFeedback = readBit(config.scanFeedbackByte, config.scanFeedbackBit, s);
+            s7Client = QmPlcPoller.getSharedClient();
+            // 一次 PDU 读齐点位,且不在本页关闭共享连接
+            final QmS7Client.Db200Snapshot snap = s7Client.readDb200Snapshot();
 
-            final boolean allOk = s.errorCount == 0;
             SwingUtilities.invokeLater(() -> {
-                inflationTime.setValue(s.inflationTime);
-                holdTime.setValue(s.holdTime);
-                testTime.setValue(s.testTime);
-                inflationPressure.setValue(s.inflationPressure);
-                stablePressure.setValue(s.stablePressure);
-                leak.setValue(s.leak);
-                judgeResult.setValue(s.judgeResult);
-                frameCode.setValue(s.frameCode);
-                complete.setValue(s.complete);
-                allowEntry.setValue(s.allowEntry);
-                scanFeedback.setValue(s.scanFeedback);
-                if (allOk) {
-                    statusLabel.setForeground(new Color(0, 128, 0));
-                    statusLabel.setText("已连接 · 实时刷新中");
-                } else {
-                    statusLabel.setForeground(Color.RED);
-                    statusLabel.setText("部分读取失败: " + shorten(s.firstError));
-                }
+                inflationTime.setValue(String.format("%.3f", snap.inflationTime));
+                holdTime.setValue(String.format("%.3f", snap.holdTime));
+                testTime.setValue(String.format("%.3f", snap.testTime));
+                inflationPressure.setValue(String.format("%.3f", snap.inflationPressure));
+                stablePressure.setValue(String.format("%.3f", snap.stablePressure));
+                leak.setValue(String.format("%.3f", snap.leak));
+                judgeResult.setValue(snap.judgePass ? "true" : "false");
+                frameCode.setValue(snap.frameCode == null || snap.frameCode.isEmpty() ? "(空)" : snap.frameCode);
+                complete.setValue(snap.complete ? "true" : "false");
+                allowEntry.setValue(snap.allowEntry ? "true" : "false");
+                scanFeedback.setValue(snap.scanFeedback ? "true" : "false");
+                statusLabel.setForeground(new Color(0, 128, 0));
+                statusLabel.setText("已连接 · 实时刷新中");
             });
-
-            if (s.errorCount >= 11 && s7Client != null) {
-                s7Client.close();
-                s7Client = null;
-            }
         } catch (Exception e) {
-            if (s7Client != null) {
-                s7Client.close();
-                s7Client = null;
-            }
-            final String msg = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
+            final String msg = QmS7Client.formatConnectError(e);
             SwingUtilities.invokeLater(() -> {
                 statusLabel.setForeground(Color.RED);
                 statusLabel.setText("读取失败: " + shorten(msg));
             });
+            // 不在调试页关闭共享连接,由轮询在协议错乱时统一重连
         } finally {
             refreshing.set(false);
         }
     }
 
-    private String readFloat(int offset, Snapshot s) {
-        try {
-            return String.format("%.3f", s7Client.readFloat(offset));
-        } catch (Exception e) {
-            s.errorCount++;
-            if (s.firstError == null) {
-                s.firstError = e.getMessage();
-            }
-            return "ERR";
-        }
-    }
-
-    private String readBit(int byteOffset, int bit, Snapshot s) {
-        try {
-            return s7Client.readBit(byteOffset, bit) ? "true" : "false";
-        } catch (Exception e) {
-            s.errorCount++;
-            if (s.firstError == null) {
-                s.firstError = e.getMessage();
-            }
-            return "ERR";
-        }
-    }
-
-    private String readFrame(Snapshot s) {
-        try {
-            String v = s7Client.readFrameCode();
-            return (v == null || v.isEmpty()) ? "(空)" : v;
-        } catch (Exception e) {
-            s.errorCount++;
-            if (s.firstError == null) {
-                s.firstError = e.getMessage();
-            }
-            return "ERR";
-        }
-    }
-
     private static String shorten(String msg) {
         if (msg == null) {
             return "";
@@ -263,22 +193,6 @@ public class QmPlcMonitorPanel extends JPanel {
         return m;
     }
 
-    private static class Snapshot {
-        String inflationTime;
-        String holdTime;
-        String testTime;
-        String inflationPressure;
-        String stablePressure;
-        String leak;
-        String judgeResult;
-        String frameCode;
-        String complete;
-        String allowEntry;
-        String scanFeedback;
-        int errorCount;
-        String firstError;
-    }
-
     private static class PointRow extends JPanel {
         private final JLabel addrLabel = new JLabel("-");
         private final JLabel valueLabel = new JLabel("-");

+ 37 - 9
src/com/mes/plc/QmPlcPoller.java

@@ -40,6 +40,24 @@ public class QmPlcPoller {
     private static boolean lastComplete;
     private static boolean busy;
 
+    /** 调试页与轮询共用同一条 S7 连接,避免 PLC 连接数占满报 constructor */
+    public static synchronized QmS7Client getSharedClient() {
+        if (config == null) {
+            config = QmPlcConfig.load();
+        }
+        if (s7Client == null) {
+            s7Client = new QmS7Client(config);
+        }
+        return s7Client;
+    }
+
+    public static synchronized QmPlcConfig getConfig() {
+        if (config == null) {
+            config = QmPlcConfig.load();
+        }
+        return config;
+    }
+
     public static void start() {
         config = QmPlcConfig.load();
         if (!config.enabled) {
@@ -47,7 +65,7 @@ public class QmPlcPoller {
             MesClient.qmPlcEnabled = false;
             return;
         }
-        s7Client = new QmS7Client(config);
+        getSharedClient();
         MesClient.qmPlcEnabled = true;
         SwingUtilities.invokeLater(MesClient::applyPlcWorkUi);
 
@@ -66,8 +84,10 @@ public class QmPlcPoller {
     }
 
     public static void reloadHost(String host) {
-        if (config != null && s7Client != null) {
-            s7Client.updateHost(host);
+        QmS7Client client = getSharedClient();
+        client.updateHost(host);
+        if (config != null) {
+            config.host = host;
         }
     }
 
@@ -76,8 +96,10 @@ public class QmPlcPoller {
             timer.cancel();
             timer = null;
         }
-        if (s7Client != null) {
-            s7Client.close();
+        synchronized (QmPlcPoller.class) {
+            if (s7Client != null) {
+                s7Client.close();
+            }
         }
         MesClient.qmPlcEnabled = false;
     }
@@ -94,13 +116,19 @@ public class QmPlcPoller {
             return;
         }
         try {
+            s7Client = getSharedClient();
             handleScanFeedback();
             handleCompleteRisingEdge();
         } catch (Exception e) {
-            log.error("框架气密PLC轮询异常: {}", e.getMessage());
-            updateStatus("PLC异常: " + e.getMessage(), false);
-            if (s7Client != null) {
-                s7Client.close();
+            log.error("框架气密PLC轮询异常", e);
+            updateStatus("PLC异常: " + QmS7Client.formatConnectError(e), false);
+            // 协议错乱/建连失败才关连接重连;避免业务异常误关导致调试页一起抖
+            if (QmS7Client.isProtocolGlitch(e)) {
+                synchronized (QmPlcPoller.class) {
+                    if (s7Client != null) {
+                        s7Client.close();
+                    }
+                }
             }
         } finally {
             running.set(false);

+ 130 - 35
src/com/mes/plc/QmS7Client.java

@@ -10,6 +10,7 @@ import java.nio.charset.StandardCharsets;
 
 /**
  * 框架气密工位 S7 读写封装(DB200)。
+ * 所有公开方法 synchronized,供轮询与调试页共用同一连接。
  */
 public class QmS7Client {
 
@@ -20,7 +21,7 @@ public class QmS7Client {
         this.config = config;
     }
 
-    public void updateHost(String host) {
+    public synchronized void updateHost(String host) {
         if (host != null && !host.equals(config.host)) {
             config.host = host.trim();
             close();
@@ -34,13 +35,18 @@ public class QmS7Client {
         if (config.host == null || config.host.isEmpty() || config.host.contains("xxx")) {
             throw new IllegalStateException("未配置有效的 mes.qm.plc.host");
         }
-        connector = S7ConnectorFactory
-                .buildTCPConnector()
-                .withHost(config.host)
-                .withRack(config.rack)
-                .withSlot(config.slot)
-                .withTimeout(3000)
-                .build();
+        try {
+            connector = S7ConnectorFactory
+                    .buildTCPConnector()
+                    .withHost(config.host)
+                    .withRack(config.rack)
+                    .withSlot(config.slot)
+                    .withTimeout(3000)
+                    .build();
+        } catch (Exception e) {
+            connector = null;
+            throw new IllegalStateException(formatConnectError(e), e);
+        }
         System.out.println("框架气密PLC已连接: " + config.host + " DB" + config.db);
     }
 
@@ -54,13 +60,13 @@ public class QmS7Client {
         }
     }
 
-    public boolean readBit(int byteOffset, int bit) throws Exception {
+    public synchronized boolean readBit(int byteOffset, int bit) throws Exception {
         ensureConnected();
         byte[] data = connector.read(DaveArea.DB, config.db, 1, byteOffset);
         return getBit(data[0], bit);
     }
 
-    public void writeBit(int byteOffset, int bit, boolean value) throws Exception {
+    public synchronized void writeBit(int byteOffset, int bit, boolean value) throws Exception {
         ensureConnected();
         byte[] data = connector.read(DaveArea.DB, config.db, 1, byteOffset);
         data[0] = setBit(data[0], bit, value);
@@ -70,7 +76,7 @@ public class QmS7Client {
     /**
      * 校验通过:写允许进站=true,同时清扫码反馈=false(同字节一次写入)。
      */
-    public void allowEntryAndClearScanFeedback() throws Exception {
+    public synchronized void allowEntryAndClearScanFeedback() throws Exception {
         ensureConnected();
         if (config.allowEntryByte == config.scanFeedbackByte) {
             byte[] data = connector.read(DaveArea.DB, config.db, 1, config.allowEntryByte);
@@ -83,53 +89,128 @@ public class QmS7Client {
         }
     }
 
-    public void clearScanFeedback() throws Exception {
+    public synchronized void clearScanFeedback() throws Exception {
         writeBit(config.scanFeedbackByte, config.scanFeedbackBit, false);
     }
 
     /** 上传成功后复位允许进站=0(不碰检测完成/扫码反馈) */
-    public void clearAllowEntry() throws Exception {
+    public synchronized void clearAllowEntry() throws Exception {
         writeBit(config.allowEntryByte, config.allowEntryBit, false);
     }
 
     /** @deprecated 检测完成由 PLC 厂家清,MES 不应调用 */
     @Deprecated
-    public void clearComplete() throws Exception {
+    public synchronized void clearComplete() throws Exception {
         throw new UnsupportedOperationException("检测完成由 PLC 厂家清,MES 不写");
     }
 
-    public String readFrameCode() throws Exception {
+    public synchronized String readFrameCode() throws Exception {
         ensureConnected();
         int readLen = config.frameCodeLen + 2;
         byte[] data = connector.read(DaveArea.DB, config.db, readLen, config.frameCodeOffset);
+        return parseSiemensString(data, config.frameCodeLen);
+    }
+
+    public synchronized float readFloat(int offset) throws Exception {
+        ensureConnected();
+        byte[] data = connector.read(DaveArea.DB, config.db, 4, offset);
+        return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getFloat();
+    }
+
+    /**
+     * 调试页一次读齐 DB200 点位,缩短占锁时间、减少与轮询交错。
+     * 覆盖偏移 0~59(含 Real、判定、框架码、58.x 三位)。
+     */
+    public synchronized Db200Snapshot readDb200Snapshot() throws Exception {
+        ensureConnected();
+        byte[] raw = connector.read(DaveArea.DB, config.db, 60, 0);
+        Db200Snapshot s = new Db200Snapshot();
+        s.inflationTime = getFloat(raw, 0);
+        s.holdTime = getFloat(raw, 4);
+        s.testTime = getFloat(raw, 8);
+        s.inflationPressure = getFloat(raw, 12);
+        s.stablePressure = getFloat(raw, 16);
+        s.leak = getFloat(raw, 20);
+        s.judgePass = getBit(raw[24], 0);
+        int strLen = Math.min(config.frameCodeLen + 2, raw.length - 26);
+        byte[] strBytes = new byte[strLen];
+        System.arraycopy(raw, 26, strBytes, 0, strLen);
+        s.frameCode = parseSiemensString(strBytes, config.frameCodeLen);
+        s.complete = getBit(raw[58], config.completeBit);
+        s.allowEntry = getBit(raw[58], config.allowEntryBit);
+        s.scanFeedback = getBit(raw[58], config.scanFeedbackBit);
+        return s;
+    }
+
+    public static String formatConnectError(Throwable e) {
+        Throwable t = e;
+        while (t != null) {
+            String msg = t.getMessage();
+            if (msg != null) {
+                String m = msg.trim();
+                if (m.isEmpty() || "constructor".equalsIgnoreCase(m)) {
+                    return "PLC连接失败(连接数可能已满,请关闭西门子工具等多余连接)";
+                }
+                if (m.toLowerCase().contains("constructor")) {
+                    return "PLC连接失败(连接数可能已满): " + m;
+                }
+                if (m.toLowerCase().contains("unexpected function code")) {
+                    return "PLC通讯错乱(请勿同时多工具读写,已尝试重连): " + m;
+                }
+            }
+            t = t.getCause();
+        }
+        return e == null ? "PLC未知错误" : String.valueOf(e.getMessage());
+    }
+
+    public static boolean isProtocolGlitch(Throwable e) {
+        Throwable t = e;
+        while (t != null) {
+            String msg = t.getMessage();
+            if (msg != null) {
+                String lower = msg.toLowerCase();
+                if (lower.contains("unexpected function code") || "constructor".equalsIgnoreCase(msg.trim())) {
+                    return true;
+                }
+            }
+            t = t.getCause();
+        }
+        return false;
+    }
+
+    private static float getFloat(byte[] raw, int offset) {
+        return ByteBuffer.wrap(raw, offset, 4).order(ByteOrder.BIG_ENDIAN).getFloat();
+    }
+
+    private static String parseSiemensString(byte[] data, int maxLen) {
+        if (data == null || data.length < 2) {
+            return "";
+        }
         int curLen = data[1] & 0xFF;
         if (curLen < 0) {
             curLen = 0;
         }
-        if (curLen > config.frameCodeLen) {
-            curLen = config.frameCodeLen;
+        if (curLen > maxLen) {
+            curLen = maxLen;
+        }
+        if (curLen > data.length - 2) {
+            curLen = data.length - 2;
         }
         String sn = new String(data, 2, curLen, StandardCharsets.US_ASCII).trim();
-        if (sn.isEmpty()) {
-            StringBuilder sb = new StringBuilder();
-            for (int i = 2; i < data.length; i++) {
-                char c = (char) (data[i] & 0xFF);
-                if (c == 0) {
-                    break;
-                }
-                if (c >= 32 && c < 127) {
-                    sb.append(c);
-                }
+        if (!sn.isEmpty()) {
+            return sn;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 2; i < data.length; i++) {
+            char c = (char) (data[i] & 0xFF);
+            if (c == 0) {
+                break;
+            }
+            if (c >= 32 && c < 127) {
+                sb.append(c);
             }
-            sn = sb.toString().trim();
         }
-        return sn;
-    }
-
-    public float readFloat(int offset) throws Exception {
-        ensureConnected();
-        byte[] data = connector.read(DaveArea.DB, config.db, 4, offset);
-        return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getFloat();
+        return sb.toString().trim();
     }
 
     public static boolean getBit(byte b, int n) {
@@ -142,4 +223,18 @@ public class QmS7Client {
         }
         return (byte) (b & ~(1 << n));
     }
+
+    public static class Db200Snapshot {
+        public float inflationTime;
+        public float holdTime;
+        public float testTime;
+        public float inflationPressure;
+        public float stablePressure;
+        public float leak;
+        public boolean judgePass;
+        public String frameCode;
+        public boolean complete;
+        public boolean allowEntry;
+        public boolean scanFeedback;
+    }
 }