wangxichen 14 시간 전
부모
커밋
0e0d61f795

+ 39 - 31
src/com/mes/component/StationPanel.java

@@ -1,6 +1,7 @@
 package com.mes.component;
 
 import javax.swing.*;
+import javax.swing.border.LineBorder;
 import java.awt.*;
 
 /**
@@ -11,7 +12,7 @@ import java.awt.*;
 public class StationPanel extends JPanel {
 
     // 状态文字色,沿用原顶栏绿/红
-    public static final Color STATE_WAIT = new Color(0xBD, 0xBD, 0xBD);
+    public static final Color STATE_WAIT = Color.GREEN; // 待机(等待扫码)正常状态
     public static final Color STATE_BUSY = new Color(0xFF, 0xD5, 0x4F);
     public static final Color STATE_OK = Color.GREEN;
     public static final Color STATE_NG = Color.RED;
@@ -36,8 +37,8 @@ public class StationPanel extends JPanel {
     private final JLabel[] signalLabels = new JLabel[SIG_NAMES.length];
     private final JTextField codeField;
     private final JLabel stateLabel;
-    private final JLabel baseLabel;
-    private final JLabel leakLabel;
+    private final JTextField baseField;
+    private final JTextField leakField;
     private final JLabel resultLabel;
     private final JLabel retryLabel;
 
@@ -68,42 +69,49 @@ public class StationPanel extends JPanel {
         codeField.setEditable(false);
         codeField.setFont(new Font("微软雅黑", Font.PLAIN, 28));
         codeField.setBounds(20, 106, 420, 46);
-        codeField.setText("--");
         add(codeField);
 
         JLabel baseTitle = new JLabel("本底");
         baseTitle.setFont(new Font("微软雅黑", Font.PLAIN, 20));
         baseTitle.setForeground(new Color(0x61, 0x61, 0x61));
-        baseTitle.setBounds(30, 158, 90, 30);
+        baseTitle.setBounds(30, 158, 90, 32);
         add(baseTitle);
 
-        baseLabel = new JLabel("--");
-        baseLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
-        baseLabel.setBounds(120, 158, 310, 30);
-        add(baseLabel);
+        baseField = new JTextField();
+        baseField.setEditable(false);
+        baseField.setHorizontalAlignment(SwingConstants.CENTER);
+        baseField.setFont(new Font("微软雅黑", Font.BOLD, 24));
+        baseField.setBounds(120, 158, 160, 32);
+        add(baseField);
 
         JLabel leakTitle = new JLabel("漏率");
         leakTitle.setFont(new Font("微软雅黑", Font.PLAIN, 20));
         leakTitle.setForeground(new Color(0x61, 0x61, 0x61));
-        leakTitle.setBounds(30, 192, 90, 30);
+        leakTitle.setBounds(30, 194, 90, 32);
         add(leakTitle);
 
-        leakLabel = new JLabel("--");
-        leakLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
-        leakLabel.setBounds(120, 192, 310, 30);
-        add(leakLabel);
+        leakField = new JTextField();
+        leakField.setEditable(false);
+        leakField.setHorizontalAlignment(SwingConstants.CENTER);
+        leakField.setFont(new Font("微软雅黑", Font.BOLD, 24));
+        leakField.setBounds(120, 194, 160, 32);
+        add(leakField);
 
-        resultLabel = new JLabel("--", SwingConstants.CENTER);
+        // 检测结果显示在工位右侧,带边框;未出结果时灰显 OK
+        resultLabel = new JLabel("OK", SwingConstants.CENTER);
         resultLabel.setFont(new Font("微软雅黑", Font.BOLD, 30));
-        resultLabel.setForeground(STATE_WAIT);
+        resultLabel.setForeground(new Color(0xBD, 0xBD, 0xBD));
         resultLabel.setIconTextGap(12);
-        resultLabel.setBounds(120, 230, 220, 70);
+        resultLabel.setOpaque(true);
+        resultLabel.setBackground(Color.WHITE);
+        resultLabel.setBorder(new LineBorder(new Color(0xA0, 0xA0, 0xA0)));
+        resultLabel.setBounds(290, 156, 150, 70);
         add(resultLabel);
 
-        retryLabel = new JLabel("该工件为返修件,请仔细检查", SwingConstants.CENTER);
-        retryLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
+        retryLabel = new JLabel("该工件为返修件", SwingConstants.CENTER);
+        retryLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
         retryLabel.setForeground(Color.RED);
-        retryLabel.setBounds(20, 300, 420, 28);
+        retryLabel.setBounds(20, 228, 250, 26);
         retryLabel.setVisible(false);
         add(retryLabel);
 
@@ -115,7 +123,7 @@ public class StationPanel extends JPanel {
             lamp.setIcon(DOT_GREY);
             lamp.setFont(new Font("微软雅黑", Font.PLAIN, 16));
             lamp.setForeground(new Color(0x42, 0x42, 0x42));
-            lamp.setBounds(20 + col * 210, 336 + row * 26, 205, 26);
+            lamp.setBounds(20 + col * 210, 258 + row * 26, 205, 26);
             add(lamp);
             signalLabels[i] = lamp;
         }
@@ -138,7 +146,7 @@ public class StationPanel extends JPanel {
 
     public void setCode(String code) {
         SwingUtilities.invokeLater(() ->
-                codeField.setText(code == null || code.isEmpty() ? "--" : code));
+                codeField.setText(code == null ? "" : code));
     }
 
     /** color 用 STATE_xxx 常量,同一色调的文字显示在黑底上 */
@@ -151,8 +159,8 @@ public class StationPanel extends JPanel {
 
     public void setData(String base, String leak) {
         SwingUtilities.invokeLater(() -> {
-            baseLabel.setText(base == null || base.isEmpty() ? "--" : base);
-            leakLabel.setText(leak == null || leak.isEmpty() ? "--" : leak);
+            baseField.setText(base == null ? "" : base);
+            leakField.setText(leak == null ? "" : leak);
         });
     }
 
@@ -169,8 +177,8 @@ public class StationPanel extends JPanel {
                 resultLabel.setForeground(STATE_NG);
             } else {
                 resultLabel.setIcon(null);
-                resultLabel.setText("--");
-                resultLabel.setForeground(STATE_WAIT);
+                resultLabel.setText("OK");
+                resultLabel.setForeground(new Color(0xBD, 0xBD, 0xBD));
             }
         });
     }
@@ -182,14 +190,14 @@ public class StationPanel extends JPanel {
     /** 复位到等待扫码 */
     public void reset() {
         SwingUtilities.invokeLater(() -> {
-            codeField.setText("--");
-            baseLabel.setText("--");
-            leakLabel.setText("--");
+            codeField.setText("");
+            baseField.setText("");
+            leakField.setText("");
             stateLabel.setText("等待扫码");
             stateLabel.setForeground(STATE_WAIT);
             resultLabel.setIcon(null);
-            resultLabel.setText("--");
-            resultLabel.setForeground(STATE_WAIT);
+            resultLabel.setText("OK");
+            resultLabel.setForeground(new Color(0xBD, 0xBD, 0xBD));
             retryLabel.setVisible(false);
             for (JLabel lamp : signalLabels) {
                 lamp.setIcon(DOT_GREY);

+ 135 - 0
src/com/mes/plc/McPlcClient.java

@@ -0,0 +1,135 @@
+package com.mes.plc;
+
+import com.github.xingshuangs.iot.protocol.melsec.model.McDeviceAddress;
+import com.github.xingshuangs.iot.protocol.melsec.model.McDeviceContent;
+import com.github.xingshuangs.iot.protocol.melsec.service.McNetwork;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+
+/**
+ * 三菱 MC 3E 协议封装:读写位软元件、读 D 区字符串
+ * 地址写法:M205、D5000
+ */
+public class McPlcClient {
+
+    private static final Logger log = LoggerFactory.getLogger(McPlcClient.class);
+
+    private final String ip;
+    private final int port;
+    private McNetwork mc;
+    private volatile boolean connected = false;
+
+    public McPlcClient(String ip, int port) {
+        this.ip = ip;
+        this.port = port;
+    }
+
+    public synchronized void connect() {
+        if (connected) {
+            return;
+        }
+        try {
+            mc = new McNetwork(ip, port);
+            mc.setPersistence(true);
+            mc.setEnableReconnect(false);
+            mc.setConnectTimeout(3000);
+            mc.setReceiveTimeout(3000);
+            mc.connect();
+            connected = true;
+            log.info("PLC 连接成功 " + ip + ":" + port);
+        } catch (Exception e) {
+            closeQuietly();
+            connected = false;
+            mc = null;
+            log.info("PLC 连接失败 " + ip + ":" + port + " " + e.getMessage());
+        }
+    }
+
+    public synchronized void disconnect() {
+        closeQuietly();
+        connected = false;
+        mc = null;
+    }
+
+    public boolean isConnected() {
+        return connected;
+    }
+
+    /** 读单个位,失败返回 false 并标记断开 */
+    public synchronized boolean readBit(String address) {
+        try {
+            checkMc();
+            McDeviceContent content = mc.readDeviceBatchInBit(McDeviceAddress.createBy(address, 1));
+            return mc.getBooleansBy(content.getData()).get(0);
+        } catch (Exception e) {
+            onError("读 " + address, e);
+            return false;
+        }
+    }
+
+    /** 写单个位 */
+    public synchronized boolean writeBit(String address, boolean value) {
+        try {
+            checkMc();
+            McDeviceAddress addr = McDeviceAddress.createBy(address, 1);
+            byte[] data = mc.getBytesBy(Collections.singletonList(value));
+            mc.writeDeviceBatchInBit(new McDeviceContent(addr.getDeviceCode(), addr.getHeadDeviceNumber(), 1, data));
+            return true;
+        } catch (Exception e) {
+            onError("写 " + address, e);
+            return false;
+        }
+    }
+
+    /**
+     * 读 D 区字符串(ASCII/GBK),charCount 为字符数
+     * 三菱 D 区每个字存 2 个字符且字节序相反,这里做高低字节交换
+     */
+    public synchronized String readString(String address, int charCount) {
+        try {
+            checkMc();
+            int words = (charCount + 1) / 2;
+            McDeviceContent content = mc.readDeviceBatchInWord(McDeviceAddress.createBy(address, words));
+            byte[] raw = content.getData();
+            byte[] swapped = new byte[raw.length];
+            for (int i = 0; i + 1 < raw.length; i += 2) {
+                swapped[i] = raw[i + 1];
+                swapped[i + 1] = raw[i];
+            }
+            String s = new String(swapped, "GBK");
+            int end = s.indexOf('\0');
+            if (end >= 0) {
+                s = s.substring(0, end);
+            }
+            return s.trim();
+        } catch (Exception e) {
+            onError("读 " + address, e);
+            return "";
+        }
+    }
+
+    private void checkMc() {
+        if (!connected || mc == null) {
+            throw new IllegalStateException("PLC 未连接");
+        }
+    }
+
+    private void onError(String action, Exception e) {
+        log.info("PLC " + action + " 失败: " + e.getMessage());
+        // 通信异常后关闭旧连接,由外层重连
+        closeQuietly();
+        connected = false;
+        mc = null;
+    }
+
+    private void closeQuietly() {
+        try {
+            if (mc != null) {
+                mc.close();
+            }
+        } catch (Exception ignored) {
+        }
+    }
+}

+ 2 - 0
src/com/mes/ui/LoginFarme.java

@@ -181,6 +181,7 @@ public class LoginFarme extends JFrame {
                     MesClient.user_menu.setText(user_id);
                     MesClient.welcomeWin.setVisible(false);
                     MesClient.mesClientFrame.setVisible(true);
+                    MesClient.addLog("界面已加载,等待 PLC 连接");
 
                     // 已弃用WebView(工作记录),改为使用WorkRecordPanel
                     // if(MesClient.jfxPanel == null){
@@ -198,6 +199,7 @@ public class LoginFarme extends JFrame {
                     }
 
                     MesClient.initWarehouseData();
+                    MesClient.startPlcTimer();
                 }
 
             }

+ 124 - 18
src/com/mes/ui/MesClient.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSONObject;
 import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.component.StationPanel;
+import com.mes.plc.McPlcClient;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.JdbcUtils;
@@ -39,13 +40,19 @@ public class MesClient extends JFrame {
     public static int mes_heart_icon_cycle = 1;
     public static String mes_line_sn = ""; // 产线编号
 
+    // PLC(三菱 MC 3E)
+    public static String plc_ip = "";
+    public static int plc_port = 3000;
+    public static String plc_a_code_addr = "D5000";
+    public static String plc_b_code_addr = "D5000";
+    public static int plc_code_len = 20;
+
     //session
     public static String sessionid = "";
 
     public static JPanel contentPane;
     public static MesClient mesClientFrame;
     public static JTabbedPane tabbedPane;
-    public static JScrollPane indexScrollPaneA;
     // public static JScrollPane searchScrollPane; // 已弃用,改为使用 WorkRecordPanel
     public static JScrollPane searchScrollPaneDj;
 
@@ -120,7 +127,12 @@ public class MesClient extends JFrame {
         mes_server_ip = pro.getProperty("mes.server_ip");
         mes_line_sn = pro.getProperty("mes.line_sn");
         mes_gw_des = OprnoUtil.getGwDes(mes_line_sn,mes_gw);
-        log.info(mes_gw + ";" + mes_gw_des + ";" + mes_server_ip + ";");
+        plc_ip = pro.getProperty("plc.ip", "").trim();
+        plc_port = Integer.parseInt(pro.getProperty("plc.port", "3000").trim());
+        plc_a_code_addr = pro.getProperty("plc.a.code_addr", "D5000").trim();
+        plc_b_code_addr = pro.getProperty("plc.b.code_addr", "D5000").trim();
+        plc_code_len = Integer.parseInt(pro.getProperty("plc.code_len", "20").trim());
+        log.info(mes_gw + ";" + mes_gw_des + ";" + mes_server_ip + ";" + plc_ip + ":" + plc_port);
     }
 
     //启动心跳包程序
@@ -273,11 +285,6 @@ public class MesClient extends JFrame {
                 }
             }
 
-            @Override
-            public void windowOpened(WindowEvent e) {
-                // 窗口显示后再写日志,避免与首次布局争抢锁
-                addLog("界面已加载,等待 PLC 连接");
-            }
         });
 
         JMenuBar menuBar = new JMenuBar();
@@ -403,6 +410,11 @@ public class MesClient extends JFrame {
         plc_menu.setForeground(Color.RED);
         plc_menu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
         plc_menu.setBackground(Color.BLACK);
+        plc_menu.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                showPlcSignals();
+            }
+        });
         toolBar.add(plc_menu);
 
         tabbedPane = new JTabbedPane(JTabbedPane.TOP);
@@ -412,37 +424,53 @@ public class MesClient extends JFrame {
 
         //首页
         JPanel indexPanelA = new JPanel();
-        indexScrollPaneA = new JScrollPane(indexPanelA);
-        indexScrollPaneA.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
-        indexScrollPaneA.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
         indexPanelA.setLayout(null);
+        indexPanelA.setBackground(Color.WHITE);
 
         // 氦检双工位面板:A/B 并排卡片 + 底部日志
         // 点位顺序:扫码成功、MES允许、检测开始、检测结束、泄露、本底、二维码
         // 本底、二维码点位待 PLC 确认,先留空
         stationA = new StationPanel("A 工位",
-                new String[]{"M205", "M206", "M207", "M208", "D200", "--", "--"});
-        stationA.setBounds(10, 10, 460, 440);
+                new String[]{"M205", "M206", "M207", "M208", "D200", "--", "D5000"});
+        stationA.setBounds(10, 10, 460, 375);
         indexPanelA.add(stationA);
 
         stationB = new StationPanel("B 工位",
-                new String[]{"M215", "M216", "M217", "M218", "D210", "--", "--"});
-        stationB.setBounds(485, 10, 460, 440);
+                new String[]{"M215", "M216", "M217", "M218", "D210", "--", "D6000"});
+        stationB.setBounds(485, 10, 460, 375);
         indexPanelA.add(stationB);
 
+        // A/B 工位之间的分隔竖线
+        final JPanel divider = new JPanel();
+        divider.setBackground(new Color(0xBD, 0xBD, 0xBD));
+        divider.setBounds(472, 10, 2, 375);
+        indexPanelA.add(divider);
+
         logArea = new JTextArea();
         logArea.setEditable(false);
         logArea.setFont(new Font("微软雅黑", Font.PLAIN, 16));
         logArea.setBackground(Color.WHITE);
         logArea.setForeground(Color.BLACK);
         logArea.setMargin(new Insets(6, 8, 6, 8));
-        JScrollPane logScrollPane = new JScrollPane(logArea);
-        logScrollPane.setBounds(10, 455, 935, 90);
+        final JScrollPane logScrollPane = new JScrollPane(logArea);
+        logScrollPane.setBounds(10, 390, 935, 150);
         indexPanelA.add(logScrollPane);
 
-        indexPanelA.setPreferredSize(new Dimension(955, 553));
+        // 面板宽度变化时整体居中,日志高度跟随窗口
+        indexPanelA.addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                int w = indexPanelA.getWidth();
+                int h = indexPanelA.getHeight();
+                int offset = Math.max(10, (w - 935) / 2);
+                stationA.setBounds(offset, 10, 460, 375);
+                stationB.setBounds(offset + 475, 10, 460, 375);
+                divider.setBounds(offset + 472, 10, 2, 375);
+                logScrollPane.setBounds(offset, 390, 935, Math.max(60, h - 400));
+            }
+        });
 
-        tabbedPane.addTab("检测面板", new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), indexScrollPaneA, null);
+        tabbedPane.addTab("检测面板", new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), indexPanelA, null);
         tabbedPane.setEnabledAt(0, true);
 
 //		searchScrollPane = new JScrollPane((Component) null);
@@ -540,6 +568,84 @@ public class MesClient extends JFrame {
         return station == 2 ? stationB : stationA;
     }
 
+    // ===== PLC 连接 =====
+
+    public static McPlcClient plcClient;
+    public static java.util.Timer plcTimer;
+
+    /** 点顶栏 PLC 状态,读取并显示所有点位当前值 */
+    public static void showPlcSignals() {
+        if (plcClient == null) {
+            plcClient = new McPlcClient(plc_ip, plc_port);
+        }
+        new Thread(() -> {
+            if (!plcClient.isConnected()) {
+                plcClient.connect();
+            }
+            if (!plcClient.isConnected()) {
+                SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(mesClientFrame,
+                        "PLC 未连接 " + plc_ip + ":" + plc_port, "PLC 信号", JOptionPane.WARNING_MESSAGE));
+                return;
+            }
+            StringBuilder sb = new StringBuilder();
+            appendSignals(sb, "A", "M205", "M206", "M207", "M208", "D200", plc_a_code_addr);
+            sb.append("\n");
+            appendSignals(sb, "B", "M215", "M216", "M217", "M218", "D210", plc_b_code_addr);
+
+            log.info("PLC 信号:\n" + sb);
+            JTextArea area = new JTextArea(sb.toString());
+            area.setEditable(false);
+            area.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+            area.setMargin(new Insets(8, 10, 8, 10));
+            SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(mesClientFrame,
+                    new JScrollPane(area), "PLC 信号  " + plc_ip + ":" + plc_port, JOptionPane.INFORMATION_MESSAGE));
+        }).start();
+    }
+
+    private static void appendSignals(StringBuilder sb, String name,
+                                      String scan, String allow, String start, String end,
+                                      String leak, String code) {
+        sb.append(name).append(" 扫码成功(").append(scan).append(") = ").append(plcClient.readBit(scan)).append("\n");
+        sb.append(name).append(" MES允许(").append(allow).append(") = ").append(plcClient.readBit(allow)).append("\n");
+        sb.append(name).append(" 检测开始(").append(start).append(") = ").append(plcClient.readBit(start)).append("\n");
+        sb.append(name).append(" 检测结束(").append(end).append(") = ").append(plcClient.readBit(end)).append("\n");
+        sb.append(name).append(" 泄露(").append(leak).append(") = ").append(plcClient.readString(leak, 8)).append("\n");
+        sb.append(name).append(" 条码(").append(code).append(") = ").append(plcClient.readString(code, plc_code_len)).append("\n");
+    }
+
+    /** 启动 PLC 连接/重连,登录成功后调用 */
+    public static void startPlcTimer() {
+        if (plcTimer != null) {
+            plcTimer.cancel();
+        }
+        plcClient = new McPlcClient(plc_ip, plc_port);
+        plcTimer = new java.util.Timer(true);
+        plcTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                if (!plcClient.isConnected()) {
+                    plcClient.connect();
+                    if (plcClient.isConnected()) {
+                        setPlcStatus(true);
+                        addLog("PLC 已连接 " + plc_ip + ":" + plc_port);
+                        String sig = "M205=" + plcClient.readBit("M205") + "  M215=" + plcClient.readBit("M215");
+                        addLog(sig);
+                        log.info("PLC 点位: " + sig);
+                    } else {
+                        setPlcStatus(false);
+                    }
+                } else {
+                    // 心跳检测:主动读一次,断线时读失败会自动标记断开,下一轮重连
+                    plcClient.readBit("M205");
+                    if (!plcClient.isConnected()) {
+                        setPlcStatus(false);
+                        addLog("PLC 连接断开,等待重连");
+                    }
+                }
+            }
+        }, 0, 3000);
+    }
+
     public static void getMaterailData(){
         try{
             JSONObject retObj = DataUtil.getBindMaterail();

+ 9 - 1
src/resources/config/config.properties

@@ -1,4 +1,12 @@
 mes.gw=OP205A
 #mes.server_ip=127.0.0.1
 mes.server_ip=192.168.17.170
-mes.line_sn=CZ060
+mes.line_sn=CZ060
+
+# PLC MC 3E
+plc.ip=192.168.3.120
+plc.port=3000
+# plc sacn code
+plc.a.code_addr=D5000
+plc.b.code_addr=D6000
+plc.code_len=24

BIN
氦检交互流程(1).doc