liuwei před 1 dnem
rodič
revize
4cc1ff86c4

+ 238 - 0
src/com/mes/plc/TjPlcConfig.java

@@ -0,0 +1,238 @@
+package com.mes.plc;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+/**
+ * 涂胶 PLC 配置:一台 PLC,a1/b1 为两把胶枪地址。
+ */
+public class TjPlcConfig {
+
+    public final boolean enabled;
+    public final String machine;
+    public String host;
+    public final int rack;
+    public final int slot;
+    public final int db;
+    public final GunAddr a1;
+    public final GunAddr b1;
+
+    public static class GunAddr {
+        public final String name;
+        public final int frameCodeOffset;
+        public final int frameCodeLen;
+        public final int scanFeedbackByte;
+        public final int scanFeedbackBit;
+        public final int allowEntryByte;
+        public final int allowEntryBit;
+        public final int pathDoneByte;
+        public final int pathDoneBit;
+        public final int allDoneByte;
+        public final int allDoneBit;
+        public final int seqOffset;
+        public final int glueSpeedOffset;
+        public final int glueLengthOffset;
+
+        public GunAddr(String name, Properties pro, String prefix) {
+            boolean a1 = "a1".equals(name);
+            this.name = name;
+            this.frameCodeOffset = parseInt(pro.getProperty(prefix + "frameCodeOffset"), a1 ? 0 : 32);
+            this.frameCodeLen = parseInt(pro.getProperty(prefix + "frameCodeLen"), 30);
+            this.scanFeedbackByte = parseInt(pro.getProperty(prefix + "scanFeedbackByte"), 64);
+            this.scanFeedbackBit = parseInt(pro.getProperty(prefix + "scanFeedbackBit"), a1 ? 0 : 1);
+            this.allowEntryByte = parseInt(pro.getProperty(prefix + "allowEntryByte"), 64);
+            this.allowEntryBit = parseInt(pro.getProperty(prefix + "allowEntryBit"), a1 ? 2 : 3);
+            this.pathDoneByte = parseInt(pro.getProperty(prefix + "pathDoneByte"), a1 ? 64 : 76);
+            this.pathDoneBit = parseInt(pro.getProperty(prefix + "pathDoneBit"), a1 ? 4 : 0);
+            this.allDoneByte = parseInt(firstNonBlank(pro.getProperty(prefix + "allDoneByte"), pro.getProperty(prefix + "glueDoneByte")), a1 ? 64 : 76);
+            this.allDoneBit = parseInt(firstNonBlank(pro.getProperty(prefix + "allDoneBit"), pro.getProperty(prefix + "glueDoneBit")), a1 ? 5 : 1);
+            this.seqOffset = parseInt(pro.getProperty(prefix + "seqOffset"), a1 ? 66 : 78);
+            this.glueSpeedOffset = parseInt(pro.getProperty(prefix + "glueSpeedOffset"), a1 ? 68 : 80);
+            this.glueLengthOffset = parseInt(pro.getProperty(prefix + "glueLengthOffset"), a1 ? 72 : 84);
+        }
+
+        public String frameCodeAddr(int db) {
+            return String.format("DB%d.DBB%d String[%d]", db, frameCodeOffset, frameCodeLen);
+        }
+
+        public String scanFeedbackAddr(int db) {
+            return String.format("DB%d.DBX%d.%d", db, scanFeedbackByte, scanFeedbackBit);
+        }
+
+        public String allowEntryAddr(int db) {
+            return String.format("DB%d.DBX%d.%d", db, allowEntryByte, allowEntryBit);
+        }
+
+        public String pathDoneAddr(int db) {
+            return String.format("DB%d.DBX%d.%d", db, pathDoneByte, pathDoneBit);
+        }
+
+        public String allDoneAddr(int db) {
+            return String.format("DB%d.DBX%d.%d", db, allDoneByte, allDoneBit);
+        }
+
+        public String seqAddr(int db) {
+            return String.format("DB%d.DBW%d", db, seqOffset);
+        }
+
+        public String glueSpeedAddr(int db) {
+            return String.format("DB%d.DBD%d", db, glueSpeedOffset);
+        }
+
+        public String glueLengthAddr(int db) {
+            return String.format("DB%d.DBD%d", db, glueLengthOffset);
+        }
+    }
+
+    private TjPlcConfig(Properties pro) {
+        this.enabled = "true".equalsIgnoreCase(trim(pro.getProperty("mes.tj.plc.enabled")));
+        this.machine = trim(pro.getProperty("mes.machine", "a1"));
+        this.host = trim(pro.getProperty("mes.tj.plc.host"));
+        this.rack = parseInt(pro.getProperty("mes.tj.plc.rack"), 0);
+        this.slot = parseInt(pro.getProperty("mes.tj.plc.slot"), 1);
+        this.db = parseInt(pro.getProperty("mes.tj.plc.db"), 1300);
+        this.a1 = new GunAddr("a1", pro, "mes.tj.a1.");
+        this.b1 = new GunAddr("b1", pro, "mes.tj.b1.");
+    }
+
+    public GunAddr activeGun() {
+        return "b1".equalsIgnoreCase(machine) ? b1 : a1;
+    }
+
+    public GunAddr gun(String name) {
+        return "b1".equalsIgnoreCase(name) ? b1 : a1;
+    }
+
+    public static TjPlcConfig load() {
+        try {
+            Properties pro = loadProperties();
+            return new TjPlcConfig(pro);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new TjPlcConfig(new Properties());
+        }
+    }
+
+    public static Properties loadProperties() throws Exception {
+        Properties pro = new Properties();
+        File file = resolveConfigFile();
+        if (file != null && file.exists()) {
+            try (InputStream is = new FileInputStream(file);
+                 BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                pro.load(br);
+            }
+            return pro;
+        }
+        InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
+        if (is != null) {
+            try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                pro.load(br);
+            }
+        }
+        return pro;
+    }
+
+    /**
+     * 保存 PLC IP,并尽量写回可写的 config.properties。
+     */
+    public static synchronized boolean saveHost(String newHost) {
+        try {
+            File file = resolveConfigFile();
+            if (file == null) {
+                return false;
+            }
+            if (!file.exists()) {
+                File parent = file.getParentFile();
+                if (parent != null && !parent.exists()) {
+                    parent.mkdirs();
+                }
+            }
+            String content;
+            if (file.exists()) {
+                try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
+                    StringBuilder sb = new StringBuilder();
+                    String line;
+                    boolean replaced = false;
+                    while ((line = br.readLine()) != null) {
+                        if (line.startsWith("mes.tj.plc.host=")) {
+                            sb.append("mes.tj.plc.host=").append(newHost.trim()).append("\n");
+                            replaced = true;
+                        } else {
+                            sb.append(line).append("\n");
+                        }
+                    }
+                    if (!replaced) {
+                        sb.append("mes.tj.plc.host=").append(newHost.trim()).append("\n");
+                    }
+                    content = sb.toString();
+                }
+            } else {
+                content = "mes.tj.plc.host=" + newHost.trim() + "\n";
+            }
+            try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
+                bw.write(content);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    public static File resolveConfigFile() {
+        try {
+            URL url = ClassLoader.getSystemResource("config/config.properties");
+            if (url != null && "file".equalsIgnoreCase(url.getProtocol())) {
+                return new File(url.toURI());
+            }
+        } catch (Exception ignored) {
+        }
+        File[] candidates = new File[]{
+                new File("src/resources/config/config.properties"),
+                new File("config/config.properties"),
+                new File("bin/config/config.properties")
+        };
+        for (File f : candidates) {
+            if (f.exists()) {
+                return f;
+            }
+        }
+        // 开发目录默认写入
+        File dev = new File("src/resources/config/config.properties");
+        if (dev.getParentFile() != null && (dev.getParentFile().exists() || dev.getParentFile().mkdirs())) {
+            return dev;
+        }
+        return new File("config/config.properties");
+    }
+
+    private static String firstNonBlank(String a, String b) {
+        if (a != null && !a.trim().isEmpty()) {
+            return a;
+        }
+        return b;
+    }
+
+    private static String trim(String v) {
+        return v == null ? "" : v.trim();
+    }
+
+    private static int parseInt(String v, int def) {
+        try {
+            if (v == null || v.trim().isEmpty()) {
+                return def;
+            }
+            return Integer.parseInt(v.trim());
+        } catch (Exception e) {
+            return def;
+        }
+    }
+}

+ 343 - 0
src/com/mes/plc/TjPlcMonitorPanel.java

@@ -0,0 +1,343 @@
+package com.mes.plc;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * 涂胶 PLC 监控:可改 IP;同一台 PLC 同时显示 a1 / b1 两把胶枪数据。
+ */
+public class TjPlcMonitorPanel extends JPanel {
+
+    private final JLabel statusLabel = new JLabel("未连接");
+    private final JTextField hostField = new JTextField(16);
+    private final JLabel dbLabel = new JLabel("-");
+
+    private final GunView a1View = new GunView("胶枪 A1");
+    private final GunView b1View = new GunView("胶枪 B1");
+
+    private TjPlcConfig config;
+    private TjS7Client s7Client;
+    private Timer timer;
+    private final AtomicBoolean refreshing = new AtomicBoolean(false);
+    private volatile boolean active;
+
+    public TjPlcMonitorPanel() {
+        setLayout(new BorderLayout(10, 10));
+        setBorder(new EmptyBorder(12, 16, 12, 16));
+
+        JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 8));
+        JLabel ipTitle = new JLabel("PLC IP:");
+        ipTitle.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        hostField.setFont(new Font("Consolas", Font.PLAIN, 18));
+        hostField.setPreferredSize(new Dimension(180, 32));
+
+        JButton saveIpBtn = new JButton("保存IP");
+        saveIpBtn.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        saveIpBtn.addActionListener(e -> saveIp());
+
+        JButton refreshBtn = new JButton("立即刷新");
+        refreshBtn.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        refreshBtn.addActionListener(e -> refreshOnce());
+
+        statusLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
+        dbLabel.setFont(new Font("Consolas", Font.PLAIN, 16));
+
+        top.add(ipTitle);
+        top.add(hostField);
+        top.add(saveIpBtn);
+        top.add(refreshBtn);
+        top.add(dbLabel);
+        top.add(statusLabel);
+        add(top, BorderLayout.NORTH);
+
+        JPanel center = new JPanel(new GridLayout(1, 2, 12, 0));
+        center.add(a1View);
+        center.add(b1View);
+        add(center, BorderLayout.CENTER);
+
+        loadUiFromConfig();
+    }
+
+    private void loadUiFromConfig() {
+        config = TjPlcConfig.load();
+        hostField.setText(config.host);
+        dbLabel.setText("DB" + config.db + "  工作枪=" + config.machine);
+        a1View.bindAddr(config.a1, config.db);
+        b1View.bindAddr(config.b1, config.db);
+    }
+
+    private void saveIp() {
+        String ip = hostField.getText() == null ? "" : hostField.getText().trim();
+        if (ip.isEmpty()) {
+            JOptionPane.showMessageDialog(this, "请输入 PLC IP", "提示", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+        boolean ok = TjPlcConfig.saveHost(ip);
+        if (config != null) {
+            config.host = ip;
+        }
+        if (s7Client != null) {
+            s7Client.updateHost(ip);
+        }
+        TjPlcPoller.reloadHost(ip);
+        if (ok) {
+            statusLabel.setForeground(new Color(0, 128, 0));
+            statusLabel.setText("IP已保存: " + ip);
+            refreshOnce();
+        } else {
+            statusLabel.setForeground(Color.ORANGE.darker());
+            statusLabel.setText("IP已用于当前连接,但写配置文件失败");
+            refreshOnce();
+        }
+    }
+
+    public void startMonitor() {
+        active = true;
+        loadUiFromConfig();
+        if (s7Client == null) {
+            s7Client = new TjS7Client(config);
+        } else {
+            s7Client.updateHost(hostField.getText().trim());
+        }
+        if (timer != null) {
+            timer.cancel();
+        }
+        timer = new Timer("TjPlcMonitor", true);
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                if (active) {
+                    refreshOnce();
+                }
+            }
+        }, 200, 1000);
+    }
+
+    public void stopMonitor() {
+        active = false;
+        if (timer != null) {
+            timer.cancel();
+            timer = null;
+        }
+        if (s7Client != null) {
+            s7Client.close();
+            s7Client = null;
+        }
+        SwingUtilities.invokeLater(() -> statusLabel.setText("已停止刷新"));
+    }
+
+    private void refreshOnce() {
+        if (!refreshing.compareAndSet(false, true)) {
+            return;
+        }
+        try {
+            if (config == null) {
+                config = TjPlcConfig.load();
+            }
+            String ip = hostField.getText() == null ? "" : hostField.getText().trim();
+            if (!ip.isEmpty()) {
+                config.host = ip;
+            }
+            if (s7Client == null) {
+                s7Client = new TjS7Client(config);
+            } else {
+                s7Client.updateHost(config.host);
+            }
+
+            Exception a1Err = null;
+            Exception b1Err = null;
+            GunSnapshot a1 = null;
+            GunSnapshot b1 = null;
+            try {
+                a1 = readGun(config.a1);
+            } catch (Exception e) {
+                a1Err = e;
+            }
+            try {
+                b1 = readGun(config.b1);
+            } catch (Exception e) {
+                b1Err = e;
+            }
+
+            final GunSnapshot a1Final = a1;
+            final GunSnapshot b1Final = b1;
+            final String a1Msg = a1Err == null ? null : a1Err.getMessage();
+            final String b1Msg = b1Err == null ? null : b1Err.getMessage();
+            final boolean a1Ok = a1Err == null;
+            final boolean b1Ok = b1Err == null;
+
+            SwingUtilities.invokeLater(() -> {
+                if (a1Final != null) {
+                    a1View.apply(a1Final);
+                }
+                if (b1Final != null) {
+                    b1View.apply(b1Final);
+                }
+                if (a1Ok && b1Ok) {
+                    statusLabel.setForeground(new Color(0, 128, 0));
+                    statusLabel.setText("已连接 · 实时刷新中");
+                } else if (!a1Ok && !b1Ok) {
+                    statusLabel.setForeground(Color.RED);
+                    statusLabel.setText("A1/B1均失败(常见:DB不存在/优化块/越界): " + shorten(a1Msg));
+                } else if (!a1Ok) {
+                    statusLabel.setForeground(Color.RED);
+                    statusLabel.setText("A1失败: " + shorten(a1Msg));
+                } else {
+                    statusLabel.setForeground(Color.RED);
+                    statusLabel.setText("B1失败: " + shorten(b1Msg));
+                }
+            });
+
+            if (a1Err != null && b1Err != null) {
+                if (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();
+            SwingUtilities.invokeLater(() -> {
+                statusLabel.setForeground(Color.RED);
+                statusLabel.setText("读取失败: " + shorten(msg));
+            });
+        } finally {
+            refreshing.set(false);
+        }
+    }
+
+    private static String shorten(String msg) {
+        if (msg == null) {
+            return "";
+        }
+        String m = msg.replace('\n', ' ').trim();
+        if (m.length() > 120) {
+            return m.substring(0, 120) + "...";
+        }
+        return m;
+    }
+
+    private GunSnapshot readGun(TjPlcConfig.GunAddr gun) throws Exception {
+        GunSnapshot s = new GunSnapshot();
+        s.frameCode = s7Client.readFrameCode(gun);
+        s.scanFeedback = s7Client.readBit(gun.scanFeedbackByte, gun.scanFeedbackBit);
+        s.allowEntry = s7Client.readBit(gun.allowEntryByte, gun.allowEntryBit);
+        s.pathDone = s7Client.readBit(gun.pathDoneByte, gun.pathDoneBit);
+        s.allDone = s7Client.readBit(gun.allDoneByte, gun.allDoneBit);
+        s.seq = s7Client.readUInt16(gun.seqOffset);
+        s.glueSpeed = s7Client.readFloat(gun.glueSpeedOffset);
+        s.glueLength = s7Client.readFloat(gun.glueLengthOffset);
+        return s;
+    }
+
+    private static class GunSnapshot {
+        String frameCode;
+        boolean scanFeedback;
+        boolean allowEntry;
+        boolean pathDone;
+        boolean allDone;
+        int seq;
+        float glueSpeed;
+        float glueLength;
+    }
+
+    private static class GunView extends JPanel {
+        private final JLabel frameCode = valueLabel();
+        private final JLabel scanFeedback = valueLabel();
+        private final JLabel allowEntry = valueLabel();
+        private final JLabel pathDone = valueLabel();
+        private final JLabel allDone = valueLabel();
+        private final JLabel seq = valueLabel();
+        private final JLabel glueSpeed = valueLabel();
+        private final JLabel glueLength = valueLabel();
+
+        private final JLabel frameCodeAddr = addrLabel();
+        private final JLabel scanFeedbackAddr = addrLabel();
+        private final JLabel allowEntryAddr = addrLabel();
+        private final JLabel pathDoneAddr = addrLabel();
+        private final JLabel allDoneAddr = addrLabel();
+        private final JLabel seqAddr = addrLabel();
+        private final JLabel glueSpeedAddr = addrLabel();
+        private final JLabel glueLengthAddr = addrLabel();
+
+        GunView(String title) {
+            setLayout(new GridBagLayout());
+            setBorder(BorderFactory.createTitledBorder(
+                    BorderFactory.createEtchedBorder(), title,
+                    TitledBorder.LEFT, TitledBorder.TOP,
+                    new Font("微软雅黑", Font.BOLD, 18)));
+            GridBagConstraints c = new GridBagConstraints();
+            c.insets = new Insets(6, 6, 6, 6);
+            c.fill = GridBagConstraints.HORIZONTAL;
+            c.anchor = GridBagConstraints.WEST;
+            int row = 0;
+            row = addRow(c, row, "框架码", frameCode, frameCodeAddr);
+            row = addRow(c, row, "扫码反馈", scanFeedback, scanFeedbackAddr);
+            row = addRow(c, row, "允许进站", allowEntry, allowEntryAddr);
+            row = addRow(c, row, "单道结束", pathDone, pathDoneAddr);
+            row = addRow(c, row, "全部结束", allDone, allDoneAddr);
+            row = addRow(c, row, "涂胶序号", seq, seqAddr);
+            row = addRow(c, row, "涂胶速度", glueSpeed, glueSpeedAddr);
+            addRow(c, row, "涂胶长度", glueLength, glueLengthAddr);
+        }
+
+        private int addRow(GridBagConstraints c, int row, String title, JLabel value, JLabel addr) {
+            JLabel name = new JLabel(title);
+            name.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+            c.gridx = 0;
+            c.gridy = row;
+            c.weightx = 0;
+            add(name, c);
+            c.gridx = 1;
+            c.weightx = 1;
+            add(value, c);
+            c.gridx = 2;
+            c.weightx = 0.8;
+            add(addr, c);
+            return row + 1;
+        }
+
+        void bindAddr(TjPlcConfig.GunAddr gun, int db) {
+            frameCodeAddr.setText(gun.frameCodeAddr(db));
+            scanFeedbackAddr.setText(gun.scanFeedbackAddr(db));
+            allowEntryAddr.setText(gun.allowEntryAddr(db));
+            pathDoneAddr.setText(gun.pathDoneAddr(db));
+            allDoneAddr.setText(gun.allDoneAddr(db));
+            seqAddr.setText(gun.seqAddr(db));
+            glueSpeedAddr.setText(gun.glueSpeedAddr(db));
+            glueLengthAddr.setText(gun.glueLengthAddr(db));
+        }
+
+        void apply(GunSnapshot s) {
+            frameCode.setText(s.frameCode == null || s.frameCode.isEmpty() ? "(空)" : s.frameCode);
+            scanFeedback.setText(s.scanFeedback ? "1" : "0");
+            allowEntry.setText(s.allowEntry ? "1" : "0");
+            pathDone.setText(s.pathDone ? "1" : "0");
+            allDone.setText(s.allDone ? "1" : "0");
+            seq.setText(String.valueOf(s.seq));
+            glueSpeed.setText(String.format("%.3f", s.glueSpeed));
+            glueLength.setText(String.format("%.3f", s.glueLength));
+        }
+
+        private static JLabel valueLabel() {
+            JLabel l = new JLabel("-");
+            l.setFont(new Font("微软雅黑", Font.BOLD, 18));
+            return l;
+        }
+
+        private static JLabel addrLabel() {
+            JLabel l = new JLabel("-");
+            l.setFont(new Font("Consolas", Font.PLAIN, 13));
+            l.setForeground(Color.DARK_GRAY);
+            return l;
+        }
+    }
+}

+ 217 - 0
src/com/mes/plc/TjPlcPoller.java

@@ -0,0 +1,217 @@
+package com.mes.plc;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.ui.DataUtil;
+import com.mes.ui.MesClient;
+import com.mes.ui.ModbusRtu;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * 涂胶 PLC 定时轮询:扫码进站 + 单道采集 + 全部结束判定。
+ */
+public class TjPlcPoller {
+
+    private static Timer timer;
+    private static final AtomicBoolean running = new AtomicBoolean(false);
+
+    private static TjPlcConfig config;
+    private static TjS7Client s7Client;
+    private static TjPlcConfig.GunAddr gun;
+
+    private static boolean lastPathDone;
+    private static boolean lastAllDone;
+    private static boolean handshakeBusy;
+    private static String currentSn = "";
+    private static boolean entered;
+
+    public static void start() {
+        config = TjPlcConfig.load();
+        if (!config.enabled) {
+            System.out.println("涂胶PLC轮询未启用 mes.tj.plc.enabled=false");
+            return;
+        }
+        gun = config.activeGun();
+        s7Client = new TjS7Client(config);
+        MesClient.tjPlcEnabled = true;
+        MesClient.tjMachine = gun.name;
+
+        if (timer != null) {
+            timer.cancel();
+        }
+        timer = new Timer("TjPlcPoller", true);
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                pollOnce();
+            }
+        }, 1000, 1000);
+        System.out.println("涂胶PLC轮询已启动 gun=" + gun.name + " host=" + config.host);
+    }
+
+    /** 监控页改 IP 后刷新轮询连接 */
+    public static void reloadHost(String host) {
+        if (config != null && s7Client != null) {
+            s7Client.updateHost(host);
+        }
+    }
+
+    public static void stop() {
+        if (timer != null) {
+            timer.cancel();
+            timer = null;
+        }
+        if (s7Client != null) {
+            s7Client.close();
+        }
+    }
+
+    public static void resetCycle() {
+        currentSn = "";
+        entered = false;
+        lastPathDone = false;
+        lastAllDone = false;
+        handshakeBusy = false;
+        MesClient.clearTjPathCache();
+    }
+
+    private static void pollOnce() {
+        if (!running.compareAndSet(false, true)) {
+            return;
+        }
+        try {
+            handleScanFeedback();
+            handlePathDone();
+            handleAllDone();
+        } catch (Exception e) {
+            System.out.println("涂胶PLC轮询异常: " + e.getMessage());
+            if (s7Client != null) {
+                s7Client.close();
+            }
+        } finally {
+            running.set(false);
+        }
+    }
+
+    private static void handleScanFeedback() throws Exception {
+        if (handshakeBusy || entered || MesClient.work_status == 1) {
+            return;
+        }
+        boolean feedback = s7Client.readBit(gun.scanFeedbackByte, gun.scanFeedbackBit);
+        if (!feedback) {
+            return;
+        }
+
+        handshakeBusy = true;
+        try {
+            String sn = s7Client.readFrameCode(gun);
+            if (sn == null || sn.isEmpty()) {
+                s7Client.clearScanFeedback(gun);
+                updateStatus("PLC框架码为空,已清扫码反馈", false);
+                return;
+            }
+
+            JSONObject resp = DataUtil.checkQualityHttp(sn);
+            if (resp == null) {
+                updateStatus("质量校验请求失败: " + sn, false);
+                return;
+            }
+            boolean pass = "true".equalsIgnoreCase(String.valueOf(resp.get("result")));
+            if (pass) {
+                s7Client.allowEntryAndClearScanFeedback(gun);
+                currentSn = sn;
+                entered = true;
+                lastPathDone = s7Client.readBit(gun.pathDoneByte, gun.pathDoneBit);
+                lastAllDone = s7Client.readBit(gun.allDoneByte, gun.allDoneBit);
+                SwingUtilities.invokeLater(() -> {
+                    MesClient.product_sn.setText(sn);
+                    MesClient.check_quality_result = true;
+                    MesClient.f_scan_data_bt_1.setEnabled(false);
+                    MesClient.finish_ok_bt.setEnabled(false);
+                    MesClient.finish_ng_bt.setEnabled(false);
+                    MesClient.clearTjPathCache();
+                    MesClient.refreshTjPathUi();
+                });
+                updateStatus("[" + gun.name + "]允许进站: " + sn + ",等待涂胶", true);
+            } else {
+                s7Client.clearScanFeedback(gun);
+                String msg = resp.getString("message");
+                if (msg == null || msg.isEmpty()) {
+                    msg = "不可加工";
+                }
+                updateStatus(msg + " [" + sn + "]", false);
+            }
+        } finally {
+            handshakeBusy = false;
+        }
+    }
+
+    private static void handlePathDone() throws Exception {
+        if (!entered || currentSn == null || currentSn.isEmpty()) {
+            lastPathDone = false;
+            return;
+        }
+        boolean pathDone = s7Client.readBit(gun.pathDoneByte, gun.pathDoneBit);
+        boolean rising = pathDone && !lastPathDone;
+        lastPathDone = pathDone;
+        if (!rising) {
+            return;
+        }
+
+        int seq = s7Client.readUInt16(gun.seqOffset);
+        float speed = s7Client.readFloat(gun.glueSpeedOffset);
+        float length = s7Client.readFloat(gun.glueLengthOffset);
+        MesClient.addTjPath(seq, speed, length);
+        final int seqF = seq;
+        final float speedF = speed;
+        final float lengthF = length;
+        SwingUtilities.invokeLater(() -> {
+            MesClient.refreshTjPathUi();
+            MesClient.status_menu.setForeground(Color.GREEN);
+            MesClient.status_menu.setText("[" + gun.name + "]单道完成 序号=" + seqF
+                    + " 速度=" + String.format("%.2f", speedF)
+                    + " 长度=" + String.format("%.2f", lengthF));
+        });
+    }
+
+    private static void handleAllDone() throws Exception {
+        if (!entered || currentSn == null || currentSn.isEmpty()) {
+            lastAllDone = false;
+            return;
+        }
+        if (MesClient.work_status == 1) {
+            return;
+        }
+
+        boolean allDone = s7Client.readBit(gun.allDoneByte, gun.allDoneBit);
+        boolean rising = allDone && !lastAllDone;
+        lastAllDone = allDone;
+        if (!rising) {
+            return;
+        }
+
+        final String sn = currentSn;
+        SwingUtilities.invokeLater(() -> {
+            MesClient.product_sn.setText(sn);
+            MesClient.work_status = 1;
+            MesClient.check_quality_result = true;
+            MesClient.finish_ok_bt.setEnabled(true);
+            MesClient.finish_ng_bt.setEnabled(true);
+            MesClient.f_scan_data_bt_1.setEnabled(false);
+            MesClient.refreshTjPathUi();
+            ModbusRtu.openDevice(MesClient.serialPort);
+        });
+        updateStatus("[" + gun.name + "]全部涂胶结束,请判定 OK/NG: " + sn, true);
+    }
+
+    private static void updateStatus(final String msg, final boolean ok) {
+        SwingUtilities.invokeLater(() -> {
+            MesClient.status_menu.setForeground(ok ? Color.GREEN : Color.RED);
+            MesClient.status_menu.setText(msg);
+        });
+    }
+}

+ 137 - 0
src/com/mes/plc/TjS7Client.java

@@ -0,0 +1,137 @@
+package com.mes.plc;
+
+import com.github.s7connector.api.DaveArea;
+import com.github.s7connector.api.S7Connector;
+import com.github.s7connector.api.factory.S7ConnectorFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 涂胶工位 S7 读写封装(一台 PLC,按胶枪地址读写)
+ */
+public class TjS7Client {
+
+    private final TjPlcConfig config;
+    private volatile S7Connector connector;
+
+    public TjS7Client(TjPlcConfig config) {
+        this.config = config;
+    }
+
+    public void updateHost(String host) {
+        if (host != null && !host.equals(config.host)) {
+            config.host = host.trim();
+            close();
+        }
+    }
+
+    public synchronized void ensureConnected() {
+        if (connector != null) {
+            return;
+        }
+        if (config.host == null || config.host.isEmpty() || "10.0.20.xxx".equals(config.host)) {
+            throw new IllegalStateException("未配置有效的 mes.tj.plc.host");
+        }
+        connector = S7ConnectorFactory
+                .buildTCPConnector()
+                .withHost(config.host)
+                .withRack(config.rack)
+                .withSlot(config.slot)
+                .withTimeout(3000)
+                .build();
+        System.out.println("涂胶PLC已连接: " + config.host);
+    }
+
+    public synchronized void close() {
+        if (connector != null) {
+            try {
+                connector.close();
+            } catch (Exception ignored) {
+            }
+            connector = null;
+        }
+    }
+
+    public 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 {
+        ensureConnected();
+        byte[] data = connector.read(DaveArea.DB, config.db, 1, byteOffset);
+        data[0] = setBit(data[0], bit, value);
+        connector.write(DaveArea.DB, config.db, byteOffset, data);
+    }
+
+    public void allowEntryAndClearScanFeedback(TjPlcConfig.GunAddr gun) throws Exception {
+        ensureConnected();
+        if (gun.allowEntryByte == gun.scanFeedbackByte) {
+            byte[] data = connector.read(DaveArea.DB, config.db, 1, gun.allowEntryByte);
+            data[0] = setBit(data[0], gun.allowEntryBit, true);
+            data[0] = setBit(data[0], gun.scanFeedbackBit, false);
+            connector.write(DaveArea.DB, config.db, gun.allowEntryByte, data);
+        } else {
+            writeBit(gun.allowEntryByte, gun.allowEntryBit, true);
+            writeBit(gun.scanFeedbackByte, gun.scanFeedbackBit, false);
+        }
+    }
+
+    public void clearScanFeedback(TjPlcConfig.GunAddr gun) throws Exception {
+        writeBit(gun.scanFeedbackByte, gun.scanFeedbackBit, false);
+    }
+
+    public String readFrameCode(TjPlcConfig.GunAddr gun) throws Exception {
+        ensureConnected();
+        int readLen = gun.frameCodeLen + 2;
+        byte[] data = connector.read(DaveArea.DB, config.db, readLen, gun.frameCodeOffset);
+        int curLen = data[1] & 0xFF;
+        if (curLen < 0) {
+            curLen = 0;
+        }
+        if (curLen > gun.frameCodeLen) {
+            curLen = gun.frameCodeLen;
+        }
+        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);
+                }
+            }
+            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();
+    }
+
+    public int readUInt16(int offset) throws Exception {
+        ensureConnected();
+        byte[] data = connector.read(DaveArea.DB, config.db, 2, offset);
+        return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getShort() & 0xFFFF;
+    }
+
+    public static boolean getBit(byte b, int n) {
+        return ((b >> n) & 1) == 1;
+    }
+
+    public static byte setBit(byte b, int n, boolean value) {
+        if (value) {
+            return (byte) (b | (1 << n));
+        }
+        return (byte) (b & ~(1 << n));
+    }
+}

+ 65 - 2
src/com/mes/ui/DataUtil.java

@@ -240,6 +240,69 @@ public class DataUtil {
         }
     }
 
+    /**
+     * HTTP 同步质量校验(涂胶 PLC 进站用)
+     */
+    public static JSONObject checkQualityHttp(String sn) {
+        try {
+            InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
+            Properties pro = new Properties();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+            pro.load(br);
+            br.close();
+            String mes_server_ip = pro.getProperty("mes.server_ip");
+            String oprno = pro.getProperty("mes.gw").trim();
+            String lineSn = pro.getProperty("mes.line_sn").trim();
+            String userCode = MesClient.user_menu != null ? MesClient.user_menu.getText() : "";
+            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductRecord/checkQuality";
+            String params = "__ajax=json&sn=" + sn.trim()
+                    + "&oprno=" + oprno
+                    + "&lineSn=" + lineSn
+                    + "&userCode=" + userCode;
+            System.out.println("checkQualityHttp params=" + params);
+            String result = doPost(url, params);
+            System.out.println("checkQualityHttp result=" + result);
+            if (result == null || result.equalsIgnoreCase("false")) {
+                return null;
+            }
+            return JSONObject.parseObject(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 上传涂胶长串到 mes_product_tj.tj_data,格式:序号#速度#长度#序号#速度#长度#...
+     */
+    public static JSONObject uploadTjData(String sn, String tjData) {
+        try {
+            InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
+            Properties pro = new Properties();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+            pro.load(br);
+            br.close();
+            String mes_server_ip = pro.getProperty("mes.server_ip");
+            String oprno = pro.getProperty("mes.gw").trim();
+            String lineSn = pro.getProperty("mes.line_sn").trim();
+            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductTj/upload";
+            String params = "__ajax=json&sn=" + java.net.URLEncoder.encode(sn.trim(), "UTF-8")
+                    + "&oprno=" + java.net.URLEncoder.encode(oprno, "UTF-8")
+                    + "&lineSn=" + java.net.URLEncoder.encode(lineSn, "UTF-8")
+                    + "&tjData=" + java.net.URLEncoder.encode(tjData == null ? "" : tjData, "UTF-8");
+            System.out.println("uploadTjData params=" + params);
+            String result = doPost(url, params);
+            System.out.println("uploadTjData result=" + result);
+            if (result == null || result.equalsIgnoreCase("false")) {
+                return null;
+            }
+            return JSONObject.parseObject(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
     public static String doPost(String httpUrl, String param) {
         HttpURLConnection connection = null;
         InputStream is = null;
@@ -254,10 +317,10 @@ public class DataUtil {
             connection.setReadTimeout(60000);
             connection.setDoOutput(true);
             connection.setDoInput(true);
-            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
             connection.setRequestProperty("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
             os = connection.getOutputStream();
-            os.write(param.getBytes());
+            os.write(param.getBytes("UTF-8"));
             if (connection.getResponseCode() == 200) {
                 is = connection.getInputStream();
                 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));

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

@@ -5,6 +5,8 @@ import com.fazecast.jSerialComm.SerialPort;
 import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.netty.NettyClient;
+import com.mes.plc.TjPlcMonitorPanel;
+import com.mes.plc.TjPlcPoller;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.JdbcUtils;
 import javafx.embed.swing.JFXPanel;
@@ -22,6 +24,7 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 import java.util.Timer;
@@ -89,6 +92,76 @@ public class MesClient extends JFrame {
 
     public static SerialPort serialPort; // 串口对象
 
+    /** 涂胶 PLC 直连模式 */
+    public static boolean tjPlcEnabled = false;
+    public static String tjMachine = "";
+    public static final List<TjPathRecord> tjPathCache = new ArrayList<TjPathRecord>();
+    public static JLabel tjSeqLabel;
+    public static JLabel tjSpeedLabel;
+    public static JLabel tjLengthLabel;
+    public static JLabel tjPathCountLabel;
+    public static JLabel tjDataPreviewLabel;
+    public static TjPlcMonitorPanel tjPlcMonitorPanel;
+
+    public static class TjPathRecord {
+        public int seq;
+        public double speed;
+        public double length;
+        public TjPathRecord(int seq, double speed, double length) {
+            this.seq = seq;
+            this.speed = speed;
+            this.length = length;
+        }
+    }
+
+    public static synchronized void addTjPath(int seq, double speed, double length) {
+        tjPathCache.add(new TjPathRecord(seq, speed, length));
+    }
+
+    public static synchronized void clearTjPathCache() {
+        tjPathCache.clear();
+    }
+
+    public static synchronized String buildTjDataString() {
+        if (tjPathCache.isEmpty()) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < tjPathCache.size(); i++) {
+            TjPathRecord r = tjPathCache.get(i);
+            if (i > 0) {
+                sb.append('#');
+            }
+            sb.append(r.seq).append('#').append(formatNum(r.speed)).append('#').append(formatNum(r.length));
+        }
+        return sb.toString();
+    }
+
+    private static String formatNum(double v) {
+        return String.format(java.util.Locale.US, "%.2f", v);
+    }
+
+    public static void refreshTjPathUi() {
+        String data = buildTjDataString();
+        int count = tjPathCache.size();
+        TjPathRecord last = count == 0 ? null : tjPathCache.get(count - 1);
+        if (tjSeqLabel != null) {
+            tjSeqLabel.setText(last == null ? "涂胶序号: -" : ("涂胶序号: " + last.seq));
+        }
+        if (tjSpeedLabel != null) {
+            tjSpeedLabel.setText(last == null ? "涂胶速度: -" : String.format("涂胶速度: %.2f", last.speed));
+        }
+        if (tjLengthLabel != null) {
+            tjLengthLabel.setText(last == null ? "涂胶长度: -" : String.format("涂胶长度: %.2f", last.length));
+        }
+        if (tjPathCountLabel != null) {
+            tjPathCountLabel.setText("已采集道次: " + count);
+        }
+        if (tjDataPreviewLabel != null) {
+            tjDataPreviewLabel.setText(data.isEmpty() ? "上传串: -" : ("上传串: " + data));
+        }
+    }
+
     public static void main(String[] args) {
         EventQueue.invokeLater(new Runnable() {
             @Override
@@ -111,6 +184,7 @@ public class MesClient extends JFrame {
                     serialPort = ModbusRtu.connect();
 
                     startJsTimer();
+                    TjPlcPoller.start();
 
                 }catch (Exception e){
                     e.printStackTrace();
@@ -307,7 +381,11 @@ public class MesClient extends JFrame {
 
         MesClient.f_scan_data_bt_1.setEnabled(true);
 //		DataUtil.stopWork(sessionid);
-        MesClient.status_menu.setText("请扫工件码");
+        if (tjPlcEnabled) {
+            MesClient.status_menu.setText("等待PLC扫码");
+        } else {
+            MesClient.status_menu.setText("请扫工件码");
+        }
 //		product_result_text.setText("");
 //		work_status_text.setText("");
 //        MesClient.setMenuStatus("上件牙套数据个数不符合要求",-1);
@@ -315,6 +393,9 @@ public class MesClient extends JFrame {
         MesClient.checkState = false;
         MesClient.jsFlag = 0;
         MesClient.jsCount = 0;
+        clearTjPathCache();
+        refreshTjPathUi();
+        TjPlcPoller.resetCycle();
 //        MesClient.formatDeviceState();
         ModbusRtu.closeDevice(serialPort);
         updateMaterailData();
@@ -574,6 +655,31 @@ public class MesClient extends JFrame {
         f_scan_data_bt_1.setBounds(693, 70, 198, 70);
         indexPanelA.add(f_scan_data_bt_1);
 
+        tjSeqLabel = new JLabel("涂胶序号: -");
+        tjSeqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        tjSeqLabel.setBounds(81, 155, 240, 32);
+        indexPanelA.add(tjSeqLabel);
+
+        tjSpeedLabel = new JLabel("涂胶速度: -");
+        tjSpeedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        tjSpeedLabel.setBounds(330, 155, 260, 32);
+        indexPanelA.add(tjSpeedLabel);
+
+        tjLengthLabel = new JLabel("涂胶长度: -");
+        tjLengthLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        tjLengthLabel.setBounds(600, 155, 260, 32);
+        indexPanelA.add(tjLengthLabel);
+
+        tjPathCountLabel = new JLabel("已采集道次: 0");
+        tjPathCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        tjPathCountLabel.setBounds(81, 190, 240, 28);
+        indexPanelA.add(tjPathCountLabel);
+
+        tjDataPreviewLabel = new JLabel("上传串: -");
+        tjDataPreviewLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        tjDataPreviewLabel.setBounds(330, 190, 560, 28);
+        indexPanelA.add(tjDataPreviewLabel);
+
 //        String[] hjtitles = new String[]{"焊机1","焊机2","焊机3"};
 //        String[] hjvals = new String[]{"HJ001","HJ002","HJ003"};
 //        mesRadioHj = new MesRadio(hjtitles,hjvals);
@@ -596,6 +702,9 @@ public class MesClient extends JFrame {
                         JOptionPane.showMessageDialog(mesClientFrame,"消息发送失败,请重试","提示窗口", JOptionPane.INFORMATION_MESSAGE);
                         return;
                     }
+                    if (tjPlcEnabled) {
+                        DataUtil.uploadTjData(product_sn.getText().trim(), buildTjDataString());
+                    }
                 }
             }
         });
@@ -619,6 +728,9 @@ public class MesClient extends JFrame {
                         JOptionPane.showMessageDialog(mesClientFrame,"消息发送失败,请重试","提示窗口", JOptionPane.INFORMATION_MESSAGE);
                         return;
                     }
+                    if (tjPlcEnabled) {
+                        DataUtil.uploadTjData(product_sn.getText().trim(), buildTjDataString());
+                    }
                 }
             }
         });
@@ -631,6 +743,10 @@ public class MesClient extends JFrame {
         tabbedPane.addTab("工作面板", new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), indexScrollPaneA, null);
         tabbedPane.setEnabledAt(0, true);
 
+        tjPlcMonitorPanel = new TjPlcMonitorPanel();
+        JScrollPane tjMonitorScroll = new JScrollPane(tjPlcMonitorPanel);
+        tabbedPane.addTab("PLC监控", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), tjMonitorScroll, null);
+
 //		searchScrollPane = new JScrollPane((Component) null);
 
         indexPanelC = new JPanel();
@@ -650,12 +766,14 @@ public class MesClient extends JFrame {
 		tabbedPane.addChangeListener(new ChangeListener() {
             @Override
             public void stateChanged(ChangeEvent e) {
-                JTabbedPane tabbedPane = (JTabbedPane) e.getSource();
-                int selectedIndex = tabbedPane.getSelectedIndex();
+                JTabbedPane pane = (JTabbedPane) e.getSource();
+                int selectedIndex = pane.getSelectedIndex();
                 System.out.println("selectedIndex:"+selectedIndex);
-
-                if(selectedIndex == 0){
-
+                String title = pane.getTitleAt(selectedIndex);
+                if ("PLC监控".equals(title)) {
+                    tjPlcMonitorPanel.startMonitor();
+                } else if (tjPlcMonitorPanel != null) {
+                    tjPlcMonitorPanel.stopMonitor();
                 }
             }
         });

+ 7 - 4
src/com/mes/ui/MesRevice.java

@@ -120,10 +120,13 @@ public class MesRevice {
             if(processMsgRet.equalsIgnoreCase("OK")) {
 
                 MesClient.resetScanA();
-//                MesClient.status_menu.setText("结果提交成功,请扫下一件");
-                MesClient.setMenuStatus("结果提交成功,请扫下一件",0);
-                MesClient.scan_type = 1;
-                MesClient.scanBarcode();
+                if (MesClient.tjPlcEnabled) {
+                    MesClient.setMenuStatus("结果提交成功,等待PLC下一件", 0);
+                } else {
+                    MesClient.setMenuStatus("结果提交成功,请扫下一件",0);
+                    MesClient.scan_type = 1;
+                    MesClient.scanBarcode();
+                }
 
             }else{
                 MesClient.setMenuStatus("结果提交失败,请重试",-1);

+ 3 - 3
src/com/mes/ui/OprnoUtil.java

@@ -13,10 +13,10 @@ public class OprnoUtil {
             "OP360","OP370","OP380","OP390","OP400","OP410"
     };
     public static String[] xtoprnodes = new String[]{
-            "镭雕二维码","单部件压套筒、装牙套","安装支架、单部件拉铆","预拼装","CMT焊接","人工焊接","焊道检验","焊道打磨",
+            "镭雕二维码","单部件压套筒、装牙套","安装支架、单部件涂胶","预拼装","CMT焊接","人工焊接","焊道检验","焊道打磨",
             "框架气密","安装后挂载点、加强件","CNC总成正面加工","CNC总成反面加工","去毛刺、清洁","总成压套筒、压铆","焊接封堵片","框架GP12",
-            "总成正面拉铆","总成反面拉铆","框架涂胶","FDS","手工反面拉铆、安装牙套","正反面清理溢胶","FDS钉头涂胶","固化1",
-            "安装中吊点、涂封堵胶","正面拉铆","固化2","半成品气密","泡棉安装、底护板安装、定位销","成品气密","液冷板气密","内腔装配",
+            "总成正面涂胶","总成反面涂胶","框架涂胶","FDS","涂胶","正反面清理溢胶","FDS钉头涂胶","固化1",
+            "安装中吊点、涂封堵胶","正面涂胶","固化2","半成品气密","泡棉安装、底护板安装、定位销","成品气密","液冷板气密","内腔装配",
             "总成检具","清洁、模拟客户装配","CCD","终检、卸螺栓","GP12","包装"
     };
     public static String[] lboprnos = new String[]{

+ 7 - 5
src/com/mes/util/Base64Utils.java

@@ -2,14 +2,16 @@ package com.mes.util;
 
 import org.apache.commons.codec.binary.Base64;
 
+import java.nio.charset.StandardCharsets;
+
 public class Base64Utils {
 	public static String getBase64(String str) {
-		// 待编码的二进制数据
-        byte[] binaryData = str.getBytes();
-        // 使用Base64.encodeBase64String进行编码
+		// 待编码的二进制数据
+        byte[] binaryData = str.getBytes(StandardCharsets.UTF_8);
+        // 浣跨敤Base64.encodeBase64String杩涜�缂栫爜
         String encodedString = Base64.encodeBase64String(binaryData);
-        // 输出编码后的结果
-        System.out.println("Base64编码结果: " + encodedString);
+        // 杈撳嚭缂栫爜鍚庣殑缁撴灉
+        System.out.println("Base64编码结果: " + encodedString);
         return encodedString;
 	}
 }

+ 2 - 2
src/com/mes/util/DateLocalUtils.java

@@ -18,14 +18,14 @@ public class DateLocalUtils {
 		return currentTime;
 	}
 	
-	//获取当前时间,年月日
+	//鑾峰彇褰撳墠鏃堕棿锛屽勾鏈堟棩
 	public static SimpleDateFormat DATA_FORMAT2 = new SimpleDateFormat("yyyy-MM-dd");
 	public static String getCurrentDate() {
 		String currentTime = DATA_FORMAT2.format(new Date());
 		return currentTime;
 	}
 	
-	//获取当前时间,时分秒
+	//鑾峰彇褰撳墠鏃堕棿锛屾椂鍒嗙�
 	public static SimpleDateFormat DATA_FORMAT3 = new SimpleDateFormat("HH:mm:ss");
 	public static String getCurrentTimeHMS() {
 		String currentTime = DATA_FORMAT3.format(new Date());

+ 40 - 0
src/resources/config/config.properties

@@ -4,3 +4,43 @@ mes.server_ip=10.0.20.10
 mes.tcp_port=3000
 mes.heart_beat_cycle=60
 mes.line_sn=XT
+
+# 工作面板当前处理哪把胶枪:a1 或 b1(同一台 PLC)
+mes.machine=a1
+
+# 涂胶 PLC:一台设备,a1/b1 为两把胶枪的不同地址
+mes.tj.plc.enabled=false
+mes.tj.plc.host=10.0.20.xxx
+mes.tj.plc.rack=0
+mes.tj.plc.slot=1
+mes.tj.plc.db=1300
+
+# ---- 胶枪 A1 (R1) ----
+mes.tj.a1.frameCodeOffset=0
+mes.tj.a1.frameCodeLen=30
+mes.tj.a1.scanFeedbackByte=64
+mes.tj.a1.scanFeedbackBit=0
+mes.tj.a1.allowEntryByte=64
+mes.tj.a1.allowEntryBit=2
+mes.tj.a1.pathDoneByte=64
+mes.tj.a1.pathDoneBit=4
+mes.tj.a1.allDoneByte=64
+mes.tj.a1.allDoneBit=5
+mes.tj.a1.seqOffset=66
+mes.tj.a1.glueSpeedOffset=68
+mes.tj.a1.glueLengthOffset=72
+
+# ---- 胶枪 B1 (R2) ----
+mes.tj.b1.frameCodeOffset=32
+mes.tj.b1.frameCodeLen=30
+mes.tj.b1.scanFeedbackByte=64
+mes.tj.b1.scanFeedbackBit=1
+mes.tj.b1.allowEntryByte=64
+mes.tj.b1.allowEntryBit=3
+mes.tj.b1.pathDoneByte=76
+mes.tj.b1.pathDoneBit=0
+mes.tj.b1.allDoneByte=76
+mes.tj.b1.allDoneBit=1
+mes.tj.b1.seqOffset=78
+mes.tj.b1.glueSpeedOffset=80
+mes.tj.b1.glueLengthOffset=84