wangxichen 5 päivää sitten
vanhempi
commit
03e243fc97

+ 7 - 6
src/com/mes/component/PlcSignalMonitor.java

@@ -136,15 +136,16 @@ public class PlcSignalMonitor {
      * 创建信号详情面板
      */
     private JPanel createSignalDetailPanel() {
+        // 名称与点位表一致;1.0、1.1、2、4 为预留,不显示
         String[][] signals = {
-                {"请求扫码", plcDb + ".0.0", "PLC→MES"},
-                {"下料信号", plcDb + ".0.1", "PLC→MES"},
-                {"设备运行", plcDb + ".0.2", "PLC→MES"},
-                {"设备通讯", plcDb + ".0.3", "PLC状态"},
+                {"请求上料", plcDb + ".0.0", "PLC→MES"},
+                {"请求下料", plcDb + ".0.1", "PLC→MES"},
+                {"运行中", plcDb + ".0.2", "PLC→MES"},
+                {"设备状态", plcDb + ".0.3", "PLC→MES"},
                 {"MES允许上料", plcDb + ".0.4", "MES→PLC"},
                 {"MES允许下料", plcDb + ".0.5", "MES→PLC"},
-                {"心跳信号", plcDb + ".0.6", "MES→PLC"},
-                {"上料到设备", plcDb + ".0.7", "PLC→MES"}  // 保留了 0.7 信号
+                {"心跳", plcDb + ".0.6", "MES→PLC"},
+                {"上料到设备", plcDb + ".0.7", "PLC→MES"}
         };
 
         JPanel panel = new JPanel(new GridLayout(signals.length + 1, 4, 5, 5));

+ 183 - 0
src/com/mes/component/PlcSignalMonitor02.java

@@ -0,0 +1,183 @@
+package com.mes.component;
+
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * PLC信号监控组件02(扫码面板用)
+ * 显示 DB7010.0.0 请求扫码、DB7010.0.1 扫码完成反馈、DB7010.6 扫码枪号
+ */
+public class PlcSignalMonitor02 {
+
+    private static final Logger log = LoggerFactory.getLogger(PlcSignalMonitor02.class);
+
+    private S7PLC s7PLC;
+    private String plcDb;
+    private String gunAddr;
+    private String stationName;
+    private JFrame parentFrame;
+
+    private JLabel signal_requestScan;
+    private JLabel signal_scanComplete;
+    private JLabel signal_gunNo;
+
+    /**
+     * @param s7PLC PLC连接对象
+     * @param plcDb DB块名称(如 DB7010)
+     * @param gunAddr 扫码枪号地址(如 DB7010.6)
+     * @param stationName 工位名称
+     * @param parentFrame 父窗口
+     */
+    public PlcSignalMonitor02(S7PLC s7PLC, String plcDb, String gunAddr, String stationName, JFrame parentFrame) {
+        this.s7PLC = s7PLC;
+        this.plcDb = plcDb;
+        this.gunAddr = gunAddr;
+        this.stationName = stationName;
+        this.parentFrame = parentFrame;
+    }
+
+    public JButton createSignalButton() {
+        JButton signalBtn = new JButton("PLC信号");
+        signalBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        signalBtn.setForeground(new Color(0, 102, 204));
+        signalBtn.setToolTipText("点击查看扫码信号详情");
+        signalBtn.addActionListener(e -> showSignalDialog());
+        return signalBtn;
+    }
+
+    public void showSignalDialog() {
+        JDialog dialog = new JDialog(parentFrame, stationName + " - PLC信号监控", false);
+        dialog.setSize(500, 300);
+        dialog.setLocationRelativeTo(parentFrame);
+
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+        JLabel title = new JLabel("扫码信号实时监控");
+        title.setFont(new Font("微软雅黑", Font.BOLD, 16));
+        title.setAlignmentX(Component.CENTER_ALIGNMENT);
+        panel.add(title);
+        panel.add(Box.createVerticalStrut(10));
+
+        JLabel stationInfo = new JLabel("工位: " + stationName + " | DB块: " + plcDb);
+        stationInfo.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        stationInfo.setForeground(Color.GRAY);
+        stationInfo.setAlignmentX(Component.CENTER_ALIGNMENT);
+        panel.add(stationInfo);
+        panel.add(Box.createVerticalStrut(15));
+
+        panel.add(createSignalDetailPanel());
+        panel.add(Box.createVerticalStrut(15));
+
+        JButton refreshBtn = new JButton("刷新信号");
+        refreshBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        refreshBtn.setAlignmentX(Component.CENTER_ALIGNMENT);
+        refreshBtn.addActionListener(e -> updateSignalDisplay());
+        panel.add(refreshBtn);
+
+        dialog.add(panel);
+        updateSignalDisplay();
+        dialog.setVisible(true);
+    }
+
+    private JPanel createSignalDetailPanel() {
+        // 0.2、DB7010.2、DB7010.4 为预留,不显示
+        String[][] signals = {
+                {"请求扫码", plcDb + ".0.0", "PLC→MES"},
+                {"扫码完成反馈", plcDb + ".0.1", "MES→PLC"},
+                {"扫码枪号(INT)", gunAddr, "PLC→MES"}
+        };
+
+        JPanel panel = new JPanel(new GridLayout(signals.length + 1, 4, 5, 5));
+        panel.setBorder(BorderFactory.createTitledBorder(stationName + " 信号状态"));
+
+        panel.add(createHeaderLabel("信号名称"));
+        panel.add(createHeaderLabel("地址"));
+        panel.add(createHeaderLabel("方向"));
+        panel.add(createHeaderLabel("状态"));
+
+        signal_requestScan = createStatusLabel();
+        signal_scanComplete = createStatusLabel();
+        signal_gunNo = createStatusLabel();
+        JLabel[] statusLabels = {signal_requestScan, signal_scanComplete, signal_gunNo};
+
+        for (int i = 0; i < signals.length; i++) {
+            panel.add(new JLabel(signals[i][0], SwingConstants.CENTER));
+            panel.add(new JLabel(signals[i][1], SwingConstants.CENTER));
+            panel.add(new JLabel(signals[i][2], SwingConstants.CENTER));
+            panel.add(statusLabels[i]);
+        }
+        return panel;
+    }
+
+    private JLabel createHeaderLabel(String text) {
+        JLabel label = new JLabel(text, SwingConstants.CENTER);
+        label.setFont(new Font("微软雅黑", Font.BOLD, 12));
+        return label;
+    }
+
+    private JLabel createStatusLabel() {
+        JLabel label = new JLabel("--", SwingConstants.CENTER);
+        label.setOpaque(true);
+        label.setBackground(Color.LIGHT_GRAY);
+        label.setFont(new Font("微软雅黑", Font.PLAIN, 11));
+        return label;
+    }
+
+    private void updateSignalDisplay() {
+        if (s7PLC == null) {
+            showError(signal_requestScan);
+            showError(signal_scanComplete);
+            showError(signal_gunNo);
+            return;
+        }
+        try {
+            boolean requestScan = s7PLC.readBoolean(plcDb + ".0.0");
+            boolean scanComplete = s7PLC.readBoolean(plcDb + ".0.1");
+            SwingUtilities.invokeLater(() -> {
+                updateStatusLabel(signal_requestScan, requestScan);
+                updateStatusLabel(signal_scanComplete, scanComplete);
+            });
+        } catch (Exception e) {
+            log.error("[{}] 读取扫码信号异常: {}", stationName, e.getMessage());
+            SwingUtilities.invokeLater(() -> {
+                showError(signal_requestScan);
+                showError(signal_scanComplete);
+            });
+        }
+        // 枪号单独读,点位还没加时只显示 ERR,不影响其他信号
+        try {
+            int gunNo = s7PLC.readInt16(gunAddr);
+            SwingUtilities.invokeLater(() -> {
+                signal_gunNo.setText(String.valueOf(gunNo));
+                signal_gunNo.setBackground(new Color(0, 200, 0));
+                signal_gunNo.setForeground(Color.WHITE);
+            });
+        } catch (Exception e) {
+            log.warn("[{}] 读取扫码枪号 {} 失败: {}", stationName, gunAddr, e.getMessage());
+            SwingUtilities.invokeLater(() -> showError(signal_gunNo));
+        }
+    }
+
+    private void showError(JLabel label) {
+        if (label != null) {
+            label.setText("ERR");
+            label.setBackground(Color.ORANGE);
+            label.setForeground(Color.WHITE);
+        }
+    }
+
+    private void updateStatusLabel(JLabel label, boolean active) {
+        if (label == null) {
+            return;
+        }
+        label.setText(active ? "ON" : "OFF");
+        label.setBackground(active ? new Color(0, 200, 0) : new Color(200, 200, 200));
+        label.setForeground(active ? Color.WHITE : Color.DARK_GRAY);
+    }
+}

+ 191 - 0
src/com/mes/component/WorkJPanelBackSlot.java

@@ -0,0 +1,191 @@
+package com.mes.component;
+
+import com.mes.ui.MesClient;
+import com.mes.util.SnSlot;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 反面待上料码面板
+ * 正面机台下料上传成功后存入一个码,反面机台请求上料时取走;可人工清空或录入
+ */
+public class WorkJPanelBackSlot extends JPanel {
+
+    public SnSlot slot;
+
+    public Integer workWidth = 470;
+    public Integer workHeight = 300;
+
+    public JButton status_menu;
+    public JTextField snField;
+    public JTextField sourceField;
+    public JTextField timeField;
+    public JButton clearBtn;
+    public JButton inputBtn;
+
+    public Timer refreshTimer;
+
+    public WorkJPanelBackSlot() {
+        this.setLayout(null);
+        this.setBackground(Color.WHITE);
+        this.slot = MesClient.backSlot;
+
+        initUI();
+        refresh();
+        refreshTimer = new Timer();
+        refreshTimer.schedule(new TimerTask() {
+            public void run() {
+                SwingUtilities.invokeLater(() -> refresh());
+            }
+        }, 1000, 1000);
+    }
+
+    private void initUI() {
+        JLabel oprnoLabel = new JLabel("OP135");
+        oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        oprnoLabel.setForeground(Color.BLACK);
+        oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        oprnoLabel.setBounds(0, 0, workWidth / 5, 40);
+        this.add(oprnoLabel);
+
+        JLabel nameLabel = new JLabel("待上料码");
+        nameLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        nameLabel.setForeground(Color.BLACK);
+        nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        nameLabel.setBounds(0, 17, workWidth / 5, 40);
+        this.add(nameLabel);
+
+        status_menu = new JButton("");
+        status_menu.setBounds(workWidth / 5, 0, workWidth * 4 / 5, 40);
+        status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        status_menu.setBackground(Color.BLACK);
+        this.add(status_menu);
+
+        addRowLabel("码", 50);
+        snField = createField(50, workWidth * 3 / 5, 18);
+        snField.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                String sn = snField.getText().trim();
+                if (!sn.isEmpty()) {
+                    MesClient.createQRCode(sn, "反面待上料码");
+                }
+            }
+        });
+
+        clearBtn = createBtn("清空", 50);
+        clearBtn.addActionListener(e -> clearSn());
+
+        addRowLabel("来源", 100);
+        sourceField = createField(100, workWidth * 3 / 5, 16);
+
+        inputBtn = createBtn("录入", 100);
+        inputBtn.addActionListener(e -> inputSn());
+
+        addRowLabel("时间", 150);
+        timeField = createField(150, workWidth * 3 / 5, 16);
+
+        JLabel tip = new JLabel("正面下料上传成功后存入,反面机台请求上料时取走;未取走时正面暂不下料");
+        tip.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        tip.setForeground(Color.DARK_GRAY);
+        tip.setBounds(5, 230, workWidth - 10, 26);
+        this.add(tip);
+    }
+
+    private void addRowLabel(String text, int y) {
+        JLabel label = new JLabel(text);
+        label.setHorizontalAlignment(SwingConstants.CENTER);
+        label.setForeground(Color.BLACK);
+        label.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        label.setBounds(0, y, workWidth / 5, 40);
+        this.add(label);
+    }
+
+    private JTextField createField(int y, int width, int fontSize) {
+        JTextField field = new JTextField("");
+        field.setHorizontalAlignment(SwingConstants.CENTER);
+        field.setForeground(Color.BLACK);
+        field.setFont(new Font("微软雅黑", Font.PLAIN, fontSize));
+        field.setEditable(false);
+        field.setBounds(workWidth / 5, y, width, 40);
+        this.add(field);
+        return field;
+    }
+
+    private JButton createBtn(String text, int y) {
+        JButton btn = new JButton(text);
+        btn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
+        btn.setBounds(workWidth * 4 / 5 + 10, y, workWidth / 5 - 10, 40);
+        this.add(btn);
+        return btn;
+    }
+
+    public void refresh() {
+        snField.setText(slot.getSn());
+        sourceField.setText(slot.getSource());
+        timeField.setText(slot.getSaveTime());
+        if (slot.isEmpty()) {
+            status_menu.setForeground(Color.GREEN);
+            status_menu.setText("空,等待正面下料");
+        } else {
+            status_menu.setForeground(Color.YELLOW);
+            status_menu.setText("等待反面机台取码");
+        }
+    }
+
+    private void clearSn() {
+        String sn = slot.getSn();
+        if (sn.isEmpty()) {
+            return;
+        }
+        if (!confirm("确定清空反面待上料码" + sn + "吗?")) {
+            return;
+        }
+        if (!slot.tryLock()) {
+            JOptionPane.showMessageDialog(null, "机台正在取码/存码,请稍后再试");
+            return;
+        }
+        try {
+            slot.clear("人工清空");
+        } finally {
+            slot.unlock();
+        }
+        refresh();
+    }
+
+    private void inputSn() {
+        String input = JOptionPane.showInputDialog(null, "请输入反面待上料码(" + WorkJPanelScan.SN_LENGTH + "位)");
+        if (input == null) {
+            return;
+        }
+        String sn = input.trim();
+        if (sn.length() != WorkJPanelScan.SN_LENGTH) {
+            JOptionPane.showMessageDialog(null, "码长度" + sn.length() + "位,应为" + WorkJPanelScan.SN_LENGTH + "位");
+            return;
+        }
+        String old = slot.getSn();
+        String tip = old.isEmpty() ? "确定录入" + sn + "吗?" : "当前码" + old + "未取走,确定覆盖为" + sn + "吗?";
+        if (!confirm(tip)) {
+            return;
+        }
+        if (!slot.tryLock()) {
+            JOptionPane.showMessageDialog(null, "机台正在取码/存码,请稍后再试");
+            return;
+        }
+        try {
+            slot.put(sn, "人工录入");
+        } finally {
+            slot.unlock();
+        }
+        refresh();
+    }
+
+    private static boolean confirm(String msg) {
+        return JOptionPane.showConfirmDialog(null, msg, "操作确认", JOptionPane.YES_NO_OPTION,
+                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
+    }
+}

+ 548 - 0
src/com/mes/component/WorkJPanelCnc.java

@@ -0,0 +1,548 @@
+package com.mes.component;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+import com.mes.ui.CacheData;
+import com.mes.ui.DataUtil;
+import com.mes.ui.MesClient;
+import com.mes.util.JdbcUtils;
+import com.mes.util.SnSlot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 总成CNC工位面板:OP130A-E 正面(PLC 92),OP135A-E 反面(PLC 93),A-E 对应 DB7000-DB7004
+ *
+ * 点位 DB700X:0.0 请求上料 / 0.1 请求下料 / 0.2 运行中 / 0.3 设备状态 / 0.4 MES允许上料
+ *            0.5 MES允许下料 / 0.6 心跳 / 0.7 上料到设备;1.0、1.1、2、4 为预留,不读不写
+ *
+ * 上料:请求上料时从 sourceSlot 取码(正面=扫码槽,反面=反面待上料码),校验通过写 0.4
+ * 进设备:0.7 上升沿或 0.2 运行中,码从「上料」移到「加工」
+ * 下料:请求下料时上传结果;正面要等反面待上料码为空,上传成功后存码给反面,再写 0.5
+ */
+public class WorkJPanelCnc extends JPanel {
+
+    public static final Logger log = LoggerFactory.getLogger(WorkJPanelCnc.class);
+
+    public static final String PLC_FRONT = "192.168.90.92"; // 正面 OP130
+    public static final String PLC_BACK = "192.168.90.93";  // 反面 OP135
+
+    private static final String[] DBS = {"DB7000", "DB7001", "DB7002", "DB7003", "DB7004"};
+    private static final String[] CN_NO = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
+
+    public String oprno;      // OP130 / OP135
+    public String pos;        // A-E
+    public boolean front;     // true=正面
+    public String plcIp;
+    public String plcDb;
+    public String deviceName;
+
+    public SnSlot sourceSlot; // 上料取码来源
+    public SnSlot targetSlot; // 下料存码目标,反面为 null
+
+    public S7PLC s7PLC;
+
+    public Integer workWidth = 470;
+    public Integer workHeight = 300;
+
+    public Integer tjFeedingFlag = 0; // 0=未取码 1=已取码校验通过
+    public Integer tjFlag = 0;        // 0=空闲 1=已上料 2=加工中 3=加工结束已上传
+    public Integer tjStatus = 0;      // 下料上传标识 0=未上传 1=已上传
+    public String lastSn = "";
+
+    private boolean feedingReleased = true; // 请求上料复位过,才处理下一次上料
+    private boolean lastToDevice = false;   // 上一轮 0.7,用于取上升沿
+    private boolean plcHeart = false;
+
+    public JButton status_menu;
+    public JTextField productSnFeeding; // 上料码
+    public JTextField productSn;        // 加工码
+    public JButton resetFeedingBtn;
+    public JButton resetBtn;
+
+    public JLabel feedinglabel;         // 0.0 请求上料
+    public JLabel feedingmeslabel;      // 0.4 MES允许上料
+    public JLabel runlabel;             // 0.2 运行中
+    public JLabel blankinglabel;        // 0.1 请求下料
+    public JLabel blankingmeslabel;     // 0.5 MES允许下料
+    public JLabel feedingtodevicelabel; // 0.7 上料到设备
+
+    public ImageIcon imageGreen;
+    public ImageIcon imageGray;
+
+    public JButton signalBtn;
+    public PlcSignalMonitor signalMonitor;
+
+    public CacheData cacheData;
+    public String cacheName;
+    private String lastCacheJson = "";
+
+    public Timer oprnoTimer;
+    public Timer heartTimer;
+
+    public WorkJPanelCnc(String oprno, String pos) {
+        this.setLayout(null);
+        this.setBackground(Color.WHITE);
+        this.oprno = oprno;
+        this.pos = pos;
+        this.front = "OP130".equals(oprno);
+
+        int idx = pos.charAt(0) - 'A';
+        plcDb = DBS[idx];
+        plcIp = front ? PLC_FRONT : PLC_BACK;
+        deviceName = "东侧" + CN_NO[front ? idx : idx + 5] + "号机";
+        sourceSlot = front ? MesClient.frontSlot : MesClient.backSlot;
+        targetSlot = front ? MesClient.backSlot : null;
+
+        s7PLC = new S7PLC(EPlcType.S1500, plcIp);
+        s7PLC.setReceiveTimeout(1000);
+        s7PLC.setConnectTimeout(1000);
+
+        cacheName = "cache_" + oprno + pos;
+        cacheData = JdbcUtils.getCache(cacheName);
+        if (cacheData == null) {
+            cacheData = new CacheData();
+            cacheData.setLastSn("");
+            cacheData.setSn("");
+            cacheData.setFeedingSn("");
+            cacheData.setTjStatus(0);
+            cacheData.setTjFlag(0);
+            cacheData.setTjFeedingFlag(0);
+            cacheData.setError(-1);
+            cacheData.setMsg("");
+            JdbcUtils.setCache(cacheName, JSON.toJSONString(cacheData));
+        }
+
+        initUI();
+        formatElementUI();
+        startOprnoTimer();
+        heartData();
+    }
+
+    // 界面 -----------------------------------------------------------------------------------------------------
+
+    private void initUI() {
+        JLabel oprnoLabel = new JLabel(oprno + pos);
+        oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        oprnoLabel.setForeground(Color.BLACK);
+        oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        oprnoLabel.setBounds(0, 0, workWidth / 5, 40);
+        this.add(oprnoLabel);
+
+        JLabel nameLabel = new JLabel(deviceName);
+        nameLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        nameLabel.setForeground(Color.BLACK);
+        nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        nameLabel.setBounds(0, 17, workWidth / 5, 40);
+        this.add(nameLabel);
+
+        status_menu = new JButton("等待请求上料");
+        status_menu.setBounds(workWidth / 5, 0, workWidth * 4 / 5, 40);
+        status_menu.setForeground(Color.GREEN);
+        status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        status_menu.setBackground(Color.BLACK);
+        this.add(status_menu);
+
+        addRowLabel("上料", 50);
+        productSnFeeding = createSnField(50);
+        resetFeedingBtn = createResetBtn(50, () -> {
+            String sn = productSnFeeding.getText().trim();
+            if (confirm("确定刷新上料工件" + sn + "吗?\n刷新后该码不会退回待上料码")) {
+                resetFeeding();
+            }
+        });
+
+        addRowLabel("加工", 100);
+        productSn = createSnField(100);
+        resetBtn = createResetBtn(100, () -> {
+            String sn = productSn.getText().trim();
+            String tip = front ? "\n刷新后不上传该件结果,也不会存入反面待上料码" : "\n刷新后不上传该件结果";
+            if (confirm("确定刷新加工工件" + sn + "吗?" + tip)) {
+                resetBlanking("已手动清除加工工件");
+            }
+        });
+
+        imageGreen = scaledIcon("/bg/green_dot.png");
+        imageGray = scaledIcon("/bg/grey_dot.png");
+
+        feedinglabel = createLight("请求上料", 0, 150);
+        feedingmeslabel = createLight("MES允许上料", workWidth / 3, 150);
+        runlabel = createLight("运行中", workWidth * 2 / 3, 150);
+        blankinglabel = createLight("请求下料", 0, 190);
+        blankingmeslabel = createLight("MES允许下料", workWidth / 3, 190);
+        feedingtodevicelabel = createLight("上料到设备", workWidth * 2 / 3, 190);
+
+        // 点击 MES 写入的灯可手动切换信号
+        feedingmeslabel.addMouseListener(toggleSignal("MES允许上料", plcDb + ".0.4"));
+        blankingmeslabel.addMouseListener(toggleSignal("MES允许下料", plcDb + ".0.5"));
+
+        JLabel ipLabel = new JLabel("PLC " + plcIp + " | " + plcDb);
+        ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        ipLabel.setForeground(Color.DARK_GRAY);
+        ipLabel.setBounds(5, 230, workWidth * 2 / 3 + 55, 26);
+        this.add(ipLabel);
+
+        signalMonitor = new PlcSignalMonitor(s7PLC, plcDb, oprno + pos, MesClient.mesClientFrame);
+        signalBtn = signalMonitor.createSignalButton();
+        signalBtn.setBounds(workWidth * 2 / 3 + 63, 230, 90, 26);
+        this.add(signalBtn);
+    }
+
+    private void addRowLabel(String text, int y) {
+        JLabel label = new JLabel(text);
+        label.setHorizontalAlignment(SwingConstants.CENTER);
+        label.setForeground(Color.BLACK);
+        label.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        label.setBounds(0, y, workWidth / 5, 40);
+        this.add(label);
+    }
+
+    private JTextField createSnField(int y) {
+        JTextField field = new JTextField("");
+        field.setHorizontalAlignment(SwingConstants.CENTER);
+        field.setForeground(Color.BLACK);
+        field.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        field.setEditable(false);
+        field.setBounds(workWidth / 5, y, workWidth * 3 / 5, 40);
+        field.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                String sn = field.getText().trim();
+                if (!sn.isEmpty()) {
+                    MesClient.createQRCode(sn, oprno + pos);
+                }
+            }
+        });
+        this.add(field);
+        return field;
+    }
+
+    private JButton createResetBtn(int y, Runnable action) {
+        JButton btn = new JButton("刷新");
+        btn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
+        btn.setBounds(workWidth * 4 / 5 + 10, y, workWidth / 5 - 10, 40);
+        btn.addActionListener(e -> action.run());
+        this.add(btn);
+        return btn;
+    }
+
+    private JLabel createLight(String text, int x, int y) {
+        JLabel label = new JLabel(text);
+        label.setHorizontalAlignment(SwingConstants.LEFT);
+        label.setIcon(imageGray);
+        label.setForeground(Color.BLACK);
+        label.setBounds(x, y, workWidth / 3, 30);
+        this.add(label);
+        return label;
+    }
+
+    private MouseAdapter toggleSignal(String name, String addr) {
+        return new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                boolean ret = readBoolean(addr);
+                if (confirm("确定修改" + name + "(" + addr + ")为" + !ret + "吗?")) {
+                    writeBoolean(addr, !ret);
+                }
+            }
+        };
+    }
+
+    private static ImageIcon scaledIcon(String path) {
+        ImageIcon icon = new ImageIcon(MesClient.class.getResource(path));
+        icon.setImage(icon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
+        return icon;
+    }
+
+    private static boolean confirm(String msg) {
+        return JOptionPane.showConfirmDialog(null, msg, "操作确认", JOptionPane.YES_NO_OPTION,
+                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
+    }
+
+    private ImageIcon light(boolean on) {
+        return on ? imageGreen : imageGray;
+    }
+
+    public void formatElementUI() {
+        cacheData = JdbcUtils.getCache(cacheName);
+        if (cacheData == null) {
+            return;
+        }
+        productSn.setText(nvl(cacheData.getSn()));
+        productSnFeeding.setText(nvl(cacheData.getFeedingSn()));
+        tjFlag = nvl(cacheData.getTjFlag(), 0);
+        tjFeedingFlag = nvl(cacheData.getTjFeedingFlag(), 0);
+        lastSn = nvl(cacheData.getLastSn());
+        setMenuStatus(nvl(cacheData.getMsg()), nvl(cacheData.getError(), -1));
+        this.repaint();
+    }
+
+    public void setMenuStatus(String msg, int error) {
+        status_menu.setForeground(error == 0 ? Color.GREEN : Color.RED);
+        status_menu.setText(msg);
+
+        cacheData.setMsg(msg);
+        cacheData.setError(error);
+        cacheData.setTjFlag(tjFlag);
+        cacheData.setTjFeedingFlag(tjFeedingFlag);
+        cacheData.setTjStatus(tjStatus);
+        cacheData.setLastSn(lastSn);
+        cacheData.setSn(productSn.getText());
+        cacheData.setFeedingSn(productSnFeeding.getText());
+        // 内容没变不重复写库(等待取码时每轮都会刷新状态)
+        String json = JSON.toJSONString(cacheData);
+        if (!json.equals(lastCacheJson)) {
+            JdbcUtils.setCache(cacheName, json);
+            lastCacheJson = json;
+        }
+    }
+
+    // 定时任务 -----------------------------------------------------------------------------------------------------
+
+    public void heartData() {
+        if (heartTimer != null) {
+            heartTimer.cancel();
+        }
+        heartTimer = new Timer();
+        heartTimer.schedule(new TimerTask() {
+            public void run() {
+                if (MesClient.isLogin == 1) {
+                    plcHeart = !plcHeart;
+                    writeBoolean(plcDb + ".0.6", plcHeart, false);
+                }
+            }
+        }, 1000, 1000);
+    }
+
+    public void startOprnoTimer() {
+        if (oprnoTimer != null) {
+            oprnoTimer.cancel();
+        }
+        oprnoTimer = new Timer();
+        oprnoTimer.schedule(new TimerTask() {
+            public void run() {
+                try {
+                    if (MesClient.isLogin == 1) {
+                        cycle();
+                    }
+                } catch (Exception e) {
+                    log.error("<{}{}>轮询异常", oprno, pos, e);
+                }
+            }
+        }, 100, 3000);
+    }
+
+    private void cycle() {
+        boolean retFeeding = readBoolean(plcDb + ".0.0");
+        boolean retBlanking = readBoolean(plcDb + ".0.1");
+        boolean retRunning = readBoolean(plcDb + ".0.2");
+        boolean retAllowFeeding = readBoolean(plcDb + ".0.4");
+        boolean retAllowBlanking = readBoolean(plcDb + ".0.5");
+        boolean retToDevice = readBoolean(plcDb + ".0.7");
+        boolean toDeviceRise = retToDevice && !lastToDevice;
+        lastToDevice = retToDevice;
+
+        feedinglabel.setIcon(light(retFeeding));
+        feedingmeslabel.setIcon(light(retAllowFeeding));
+        runlabel.setIcon(light(retRunning));
+        blankinglabel.setIcon(light(retBlanking));
+        blankingmeslabel.setIcon(light(retAllowBlanking));
+        feedingtodevicelabel.setIcon(light(retToDevice));
+
+        // 请求上料撤销后复位 MES允许上料
+        if (!retFeeding && retAllowFeeding) {
+            writeAllowFeeding(false);
+        }
+
+        // ---------- 上料 ----------
+        if (!retFeeding) {
+            feedingReleased = true;
+        }
+        if (tjFeedingFlag == 0) {
+            if (retFeeding && feedingReleased) {
+                takeSn();
+            }
+        } else if (tjFlag == 0 && (toDeviceRise || retRunning)) {
+            // 工件已进设备:码从上料移到加工
+            tjFlag = 1;
+            tjFeedingFlag = 0;
+            productSn.setText(productSnFeeding.getText());
+            productSnFeeding.setText("");
+            feedingReleased = false; // 本次请求上料复位后才处理下一次,防止拿走下一件的码
+            setMenuStatus("工件已上料到设备", 0);
+        }
+
+        // ---------- 下料 ----------
+        // 加工码为空时直接放行下料(无码件或已上传完成)
+        if (retBlanking && productSn.getText().isEmpty() && !retAllowBlanking) {
+            writeAllowBlanking(true);
+        }
+        if (tjFlag == 0 && !retBlanking && retAllowBlanking) {
+            writeAllowBlanking(false);
+        }
+
+        if (tjFlag == 1 && retRunning) {
+            tjFlag = 2;
+            writeAllowFeeding(false);
+            writeAllowBlanking(false);
+            setMenuStatus("工件加工中", 0);
+        } else if (tjFlag == 2 && retBlanking) {
+            finishWork();
+        }
+    }
+
+    // 取码并校验:通过后清掉码槽里的码,写 MES允许上料
+    private void takeSn() {
+        // 同一个码校验失败后不重复请求,等人工刷新或码槽换成新码
+        String failedSn = productSnFeeding.getText().trim();
+        if (!failedSn.isEmpty() && (sourceSlot.isEmpty() || failedSn.equals(sourceSlot.getSn()))) {
+            return;
+        }
+        if (!sourceSlot.tryLock()) {
+            return; // 其他机台正在取码,下一轮再试
+        }
+        try {
+            String sn = sourceSlot.getSn();
+            if (sn.isEmpty()) {
+                setMenuStatus(front ? "暂无扫码结果,等待扫码" : "反面待上料码为空,等待正面下料", -1);
+                return;
+            }
+            productSnFeeding.setText(sn);
+            lastSn = sn;
+            JSONObject retObj = DataUtil.checkQuality(sn, oprno + pos, MesClient.user_menu.getText());
+            if (!isOk(retObj)) {
+                writeAllowFeeding(false);
+                setMenuStatus(failMsg(retObj), -1);
+                return;
+            }
+            sourceSlot.clearIf(sn, oprno + pos + "取码");
+            tjFeedingFlag = 1;
+            writeAllowFeeding(true);
+            setMenuStatus("工件可以加工", 0);
+        } finally {
+            sourceSlot.unlock();
+        }
+    }
+
+    // 请求下料:上传结果,正面成功后存码给反面
+    private void finishWork() {
+        String sn = productSn.getText().trim();
+        if (targetSlot == null) {
+            if (upload(sn)) {
+                resetBlanking("工件结果已提交,等待下一件");
+                writeAllowBlanking(true);
+            }
+            return;
+        }
+        if (!targetSlot.tryLock()) {
+            return; // 反面正在取码或其他正面机正在存码,下一轮再试
+        }
+        try {
+            if (!targetSlot.isEmpty()) {
+                setMenuStatus("反面待上料码" + targetSlot.getSn() + "未取走,暂不允许下料", -1);
+                return;
+            }
+            if (upload(sn)) {
+                targetSlot.put(sn, oprno + pos);
+                resetBlanking("结果已提交,码已存入反面待上料码");
+                writeAllowBlanking(true);
+            }
+        } finally {
+            targetSlot.unlock();
+        }
+    }
+
+    // 上传加工结果,失败停在 tjFlag=3 等人工刷新(与原逻辑一致)
+    private boolean upload(String sn) {
+        tjFlag = 3;
+        tjStatus = 1;
+        setMenuStatus("工件加工结束,结果上传中", 0);
+        log.info("<{}{}>上传加工结果 sn={}", oprno, pos, sn);
+        JSONObject retObj = DataUtil.sendQuality(sn, oprno + pos, "OK", MesClient.user_menu.getText());
+        if (isOk(retObj)) {
+            return true;
+        }
+        setMenuStatus(failMsg(retObj), -1);
+        return false;
+    }
+
+    public void resetBlanking(String msg) {
+        tjFlag = 0;
+        tjStatus = 0;
+        productSn.setText("");
+        setMenuStatus(msg, 0);
+    }
+
+    public void resetFeeding() {
+        tjFeedingFlag = 0;
+        productSnFeeding.setText("");
+        writeAllowFeeding(false);
+        setMenuStatus("等待请求上料", 0);
+    }
+
+    private static boolean isOk(JSONObject retObj) {
+        return retObj != null && "true".equalsIgnoreCase(String.valueOf(retObj.get("result")));
+    }
+
+    private static String failMsg(JSONObject retObj) {
+        if (retObj == null || retObj.get("result") == null) {
+            return "请求失败,请重试";
+        }
+        String msg = retObj.getString("message");
+        return msg == null || msg.isEmpty() ? "校验失败" : msg;
+    }
+
+    private static String nvl(String s) {
+        return s == null ? "" : s;
+    }
+
+    private static int nvl(Integer v, int def) {
+        return v == null ? def : v;
+    }
+
+    // 与设备交互 -----------------------------------------------------------------------------------------
+
+    public void writeAllowFeeding(boolean b) {
+        writeBoolean(plcDb + ".0.4", b);
+    }
+
+    public void writeAllowBlanking(boolean b) {
+        writeBoolean(plcDb + ".0.5", b);
+    }
+
+    public boolean readBoolean(String addr) {
+        try {
+            boolean ret = s7PLC.readBoolean(addr);
+            log.info("<{}{}>读取{}-->成功,结果:{}", oprno, pos, addr, ret);
+            return ret;
+        } catch (Exception e) {
+            log.error("<{}{}>读取{}-->失败:{}", oprno, pos, addr, e.getMessage());
+            return false;
+        }
+    }
+
+    public boolean writeBoolean(String addr, boolean value) {
+        return writeBoolean(addr, value, true);
+    }
+
+    // 心跳不打成功日志
+    private boolean writeBoolean(String addr, boolean value, boolean logOk) {
+        try {
+            s7PLC.writeBoolean(addr, value);
+            if (logOk) {
+                log.info("<{}{}>写入{}-->成功,写入内容:{}", oprno, pos, addr, value);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("<{}{}>写入{}-->失败,写入内容:{},{}", oprno, pos, addr, value, e.getMessage());
+            return false;
+        }
+    }
+}

+ 0 - 861
src/com/mes/component/WorkJPanelOP150.java

@@ -1,861 +0,0 @@
-package com.mes.component;
-
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONObject;
-import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
-import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
-import com.mes.ui.CacheData;
-import com.mes.ui.DataUtil;
-import com.mes.ui.MesClient;
-import com.mes.util.JdbcUtils;
-import com.mes.util.TCPClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.*;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class WorkJPanelOP150 extends JPanel {
-
-    public static final Logger log =  LoggerFactory.getLogger(WorkJPanelOP150.class);
-
-    public String oprno = "OP150";
-
-    public String pos = "A";
-
-    public String lastSn = ""; // 最近的码
-
-    public String lineSn = "CZ060";
-
-    public JLabel feedinglabel;
-
-    public JLabel feedingmeslabel;
-
-    public JLabel runlabel; //运行反馈,
-
-    public JLabel blankinglabel; // 下料反馈
-
-    public JLabel blankingmeslabel; // 下料MES反馈
-
-    public JLabel feedingtodevicelabel; // 上料到设备
-
-    public JLabel feedingtotransferlabel; // 上料到中转车
-
-    public JLabel transfertodevicelabel; // 上料到中转车
-
-    public JButton scanBtn;
-
-    public JTextField productSn;
-
-    public JTextField productSnFeeding;
-
-    public JButton status_menu;
-
-    public JButton resetBtn;
-
-    public JButton resetFeedingBtn;
-
-    public ImageIcon imageGreen;
-
-    public ImageIcon imageGray;
-
-    public String plcIp12 = "192.168.90.92"; // plc92
-    public String plcIp13 = "192.168.90.93"; // plc93
-
-    public S7PLC s7PLC = null;
-
-    public Integer workWidth = 470;
-    public Integer workHeight = 300;
-
-    public String plcDb = "";
-
-    public Integer tjFeedingFlag = 0; // 上料状态标识
-    public Integer tjFlag = 0; // 1=可加工 2=设备运行中 3=加工结束
-    public Integer tjStatus = 0; // 提交标识
-    public Boolean toDevice = false;
-    public Boolean toTransfer = false;
-    public Boolean transferToDevice = false;
-    public Integer tjScan = 0;
-
-    public Boolean plcHeart = false;
-
-    public Integer isLocal = 0; // 0=正式 1=本地
-
-    public String cncDeviceName = "";
-
-    public JLabel oprnoLabel2;
-
-    public JButton signalBtn; // 信号监控按钮
-
-    public PlcSignalMonitor signalMonitor; // 信号监控组件
-
-    public CacheData cacheData = null;
-    public String cacheName = "";
-
-    public WorkJPanelOP150(String position){
-        this.setLayout(null);
-        this.setBackground(Color.WHITE);
-        this.pos = position;
-
-        cacheName = "cache_"+oprno+pos;
-        cacheData = JdbcUtils.getCache(cacheName);
-        if(cacheData == null){
-            cacheData = new CacheData();
-            cacheData.setLastSn("");
-            cacheData.setSn("");
-            cacheData.setFeedingSn("");
-            cacheData.setTjStatus(0);
-            cacheData.setTjFlag(0);
-            cacheData.setError(-1);
-            cacheData.setMsg("");
-            JdbcUtils.setCache(cacheName, JSON.toJSONString(cacheData));
-        }
-
-        if(position.equals("A") || position.equals("B") || position.equals("C") || position.equals("D") || position.equals("E")){
-            s7PLC = new S7PLC(EPlcType.S1500, plcIp12);
-        }else{
-            s7PLC = new S7PLC(EPlcType.S1500, plcIp13);
-        }
-
-        s7PLC.setReceiveTimeout(1000);
-        s7PLC.setConnectTimeout(1000);
-
-        switch (position){
-            case "A":
-                plcDb = "DB7000";
-                cncDeviceName = "东侧一号机";
-                break;
-            case "B":
-                plcDb = "DB7001";
-                cncDeviceName = "东侧二号机";
-                break;
-            case "C":
-                plcDb = "DB7002";
-                cncDeviceName = "东侧三号机";
-                break;
-            case "D":
-                plcDb = "DB7003";
-                cncDeviceName = "东侧四号机";
-                break;
-            case "E":
-                plcDb = "DB7004";
-                cncDeviceName = "东侧五号机";
-                break;
-            case "F":
-                plcDb = "DB7000";
-                cncDeviceName = "东侧六号机";
-                break;
-            case "G":
-                plcDb = "DB7001";
-                cncDeviceName = "东侧七号机";
-                break;
-            case "H":
-                plcDb = "DB7002";
-                cncDeviceName = "东侧八号机";
-                break;
-            case "I":
-                plcDb = "DB7003";
-                cncDeviceName = "东侧九号机";
-                break;
-            case "J":
-                plcDb = "DB7004";
-                cncDeviceName = "东侧十号机";
-                break;
-        }
-
-        JLabel oprnoLabel = new JLabel(oprno+pos);
-        oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
-        oprnoLabel.setForeground(Color.BLACK);
-        oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
-        oprnoLabel.setBounds(0, 0, workWidth/5, 40);
-        this.add(oprnoLabel);
-
-        oprnoLabel2 = new JLabel(cncDeviceName);
-        oprnoLabel2.setHorizontalAlignment(SwingConstants.CENTER);
-        oprnoLabel2.setForeground(Color.BLACK);
-        oprnoLabel2.setFont(new Font("微软雅黑", Font.PLAIN, 14));
-        oprnoLabel2.setBounds(0, 17, workWidth / 5, 40);
-        this.add(oprnoLabel2);
-
-        status_menu = new JButton("等待请求扫码");
-        status_menu.setBounds(workWidth/5, 0, workWidth*4/5, 40);
-        status_menu.setForeground(Color.GREEN);
-        status_menu.setForeground(Color.GREEN);
-        status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
-        status_menu.setBackground(Color.BLACK);
-        this.add(status_menu);
-
-        JLabel oprnoLabelFeeding = new JLabel("上料");
-        oprnoLabelFeeding.setHorizontalAlignment(SwingConstants.CENTER);
-        oprnoLabelFeeding.setForeground(Color.BLACK);
-        oprnoLabelFeeding.setFont(new Font("微软雅黑", Font.PLAIN, 20));
-        oprnoLabelFeeding.setBounds(0, 50, workWidth/5, 40);
-        this.add(oprnoLabelFeeding);
-
-        productSnFeeding = new JTextField("");
-        productSnFeeding.setHorizontalAlignment(SwingConstants.CENTER);
-        productSnFeeding.setForeground(Color.BLACK);
-        productSnFeeding.setFont(new Font("微软雅黑", Font.PLAIN, 16));
-        productSnFeeding.setEditable(false);
-        productSnFeeding.setBounds(workWidth/5, 50, workWidth*3/5, 40);
-        productSnFeeding.addMouseListener(new MouseAdapter() {
-            public void mouseClicked(MouseEvent e) {
-                String sn = productSnFeeding.getText().trim();
-                if(!sn.isEmpty()){
-                    MesClient.createQRCode(productSnFeeding.getText(),oprno+pos);
-                }
-            }
-        });
-        this.add(productSnFeeding);
-
-        JLabel oprnoLabelWork = new JLabel("加工");
-        oprnoLabelWork.setHorizontalAlignment(SwingConstants.CENTER);
-        oprnoLabelWork.setForeground(Color.BLACK);
-        oprnoLabelWork.setFont(new Font("微软雅黑", Font.PLAIN, 20));
-        oprnoLabelWork.setBounds(0, 100, workWidth/5, 40);
-        this.add(oprnoLabelWork);
-
-        productSn = new JTextField("");
-        productSn.setHorizontalAlignment(SwingConstants.CENTER);
-        productSn.setForeground(Color.BLACK);
-        productSn.setFont(new Font("微软雅黑", Font.PLAIN, 16));
-        productSn.setEditable(false);
-        productSn.setBounds(workWidth/5, 100, workWidth*3/5, 40);
-        productSn.addMouseListener(new MouseAdapter() {
-            public void mouseClicked(MouseEvent e) {
-                String sn = productSn.getText().trim();
-                if(!sn.isEmpty()){
-                    MesClient.createQRCode(productSn.getText(),oprno+pos);
-                }
-            }
-        });
-        this.add(productSn);
-
-
-//        scanBtn = new JButton("扫码");
-//        scanBtn.addActionListener(new ActionListener() {
-//            public void actionPerformed(ActionEvent e) {
-//                log.info("test2:"+productSn.getText());
-//                barCode();
-//            }
-//        });
-//        scanBtn.setIcon(new ImageIcon(MesClient.class.getResource("/bg/scan_barcode.png")));
-//        scanBtn.setFont(new Font("微软雅黑", Font.PLAIN, 32));
-//        scanBtn.setBounds(0, 160, workWidth/2 - 5, 50);
-//        this.add(scanBtn);
-
-        resetFeedingBtn = new JButton("刷新");
-        resetFeedingBtn.addActionListener(new ActionListener() {
-            public void actionPerformed(ActionEvent e) {
-                resetFeeding();
-//                scanQrcode();
-//                MesClient.scanQrcode1();
-            }
-        });
-//        resetFeedingBtn.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")));
-        resetFeedingBtn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
-        resetFeedingBtn.setBounds(workWidth*4/5 + 10, 50, workWidth/5 - 10, 40);
-        this.add(resetFeedingBtn);
-
-        resetBtn = new JButton("刷新");
-        resetBtn.addActionListener(new ActionListener() {
-            public void actionPerformed(ActionEvent e) {
-//                log.info("test2:"+productSn.getText());
-               resetBlanking();
-            }
-        });
-//        resetBtn.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")));
-        resetBtn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
-        resetBtn.setBounds(workWidth*4/5 + 10, 100, workWidth/5 - 10, 40);
-        this.add(resetBtn);
-
-        imageGreen = new ImageIcon(MesClient.class.getResource("/bg/green_dot.png"));
-        imageGreen.setImage(imageGreen.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
-
-        imageGray = new ImageIcon(MesClient.class.getResource("/bg/grey_dot.png"));
-        imageGray.setImage(imageGray.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
-
-        feedinglabel = new JLabel("请求扫码");
-        feedinglabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedinglabel.setIcon(imageGreen);
-        feedinglabel.setForeground(Color.BLACK);
-        feedinglabel.setBounds(0, 150, workWidth/3, 30);
-        this.add(feedinglabel);
-
-        feedingmeslabel = new JLabel("MES扫码反馈");
-        feedingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingmeslabel.setIcon(imageGreen);
-        feedingmeslabel.setForeground(Color.BLACK);
-        feedingmeslabel.setBounds(workWidth/3, 150, workWidth/3, 30);
-        feedingmeslabel.addMouseListener(new MouseAdapter() {
-            public void mouseClicked(MouseEvent e) {
-                boolean ret = readAllowFeeding();
-                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES扫码反馈结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
-                if (choice == JOptionPane.YES_OPTION) {
-                    writeAllowFeeding(!ret);
-                }
-            }
-        });
-        this.add(feedingmeslabel);
-
-        runlabel = new JLabel("设备运行中");
-        runlabel.setHorizontalAlignment(SwingConstants.LEFT);
-        runlabel.setIcon(imageGreen);
-        runlabel.setForeground(Color.BLACK);
-        runlabel.setBounds(workWidth*2/3, 150, workWidth/3, 30);
-        this.add(runlabel);
-
-        blankinglabel = new JLabel("下料请求");
-        blankinglabel.setHorizontalAlignment(SwingConstants.LEFT);
-        blankinglabel.setIcon(imageGreen);
-        blankinglabel.setForeground(Color.BLACK);
-        blankinglabel.setBounds(0, 190, workWidth/3, 30);
-        this.add(blankinglabel);
-
-        blankingmeslabel = new JLabel("MES下料反馈");
-        blankingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
-        blankingmeslabel.setIcon(imageGreen);
-        blankingmeslabel.setForeground(Color.BLACK);
-        blankingmeslabel.setBounds(workWidth/3, 190, workWidth/3, 30);
-        this.add(blankingmeslabel);
-        blankingmeslabel.addMouseListener(new MouseAdapter() {
-            public void mouseClicked(MouseEvent e) {
-                boolean ret = readAllowBlanking();
-                int choice = JOptionPane.showConfirmDialog(null, "确定修改MES下料反馈结果为"+(ret?"false":"true")+"吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
-                if (choice == JOptionPane.YES_OPTION) {
-                    writeAllowBlanking(!ret);
-                }
-            }
-        });
-
-        feedingtodevicelabel = new JLabel("上料到设备");
-        feedingtodevicelabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingtodevicelabel.setIcon(imageGreen);
-        feedingtodevicelabel.setForeground(Color.BLACK);
-        feedingtodevicelabel.setBounds(workWidth*2/3, 190, workWidth/3, 30);
-        this.add(feedingtodevicelabel);
-
-        feedingtotransferlabel = new JLabel("上料到中转车");
-        feedingtotransferlabel.setHorizontalAlignment(SwingConstants.LEFT);
-        feedingtotransferlabel.setIcon(imageGreen);
-        feedingtotransferlabel.setForeground(Color.BLACK);
-        feedingtotransferlabel.setBounds(0, 230, workWidth/3, 30);
-//        this.add(feedingtotransferlabel);
-
-        transfertodevicelabel = new JLabel("中转车上料到设备");
-        transfertodevicelabel.setHorizontalAlignment(SwingConstants.LEFT);
-        transfertodevicelabel.setIcon(imageGreen);
-        transfertodevicelabel.setForeground(Color.BLACK);
-        transfertodevicelabel.setBounds(workWidth/3, 230, workWidth/3, 30);
-//        this.add(transfertodevicelabel);
-
-
-        formatElementUI();
-
-        this.startOprnoTimer();
-        heartData();
-
-//        resetScanA();
-
-        // 信号监控按钮
-        signalMonitor = new PlcSignalMonitor(s7PLC, plcDb, oprno + pos, MesClient.mesClientFrame);
-        signalBtn = signalMonitor.createSignalButton();
-        signalBtn.setBounds(workWidth*2/3 + 63, 230, 90, 26);
-        this.add(signalBtn);
-    }
-
-    public void formatElementUI(){
-        cacheData = JdbcUtils.getCache(cacheName);
-        if(cacheData == null){
-            return;
-        }
-        String msg = cacheData.getMsg();
-        int error = cacheData.getError();
-        String sn = cacheData.getSn();
-        productSn.setText(sn);
-
-        String feedingSn = cacheData.getFeedingSn();
-        productSnFeeding.setText(feedingSn);
-
-        tjFlag = cacheData.getTjFlag();
-        lastSn = cacheData.getLastSn();
-
-        setMenuStatus(msg,error);
-
-        this.repaint();
-    }
-
-    public void setMenuStatus(String msg,int error){
-        if(error == 0){
-            this.status_menu.setForeground(Color.GREEN);
-        }else{
-            this.status_menu.setForeground(Color.RED);
-        }
-        this.status_menu.setText(msg);
-
-        cacheData.setMsg(msg);
-        cacheData.setError(error);
-        cacheData.setTjFlag(tjFlag);
-        cacheData.setTjStatus(tjStatus);
-        cacheData.setLastSn(lastSn);
-        cacheData.setSn(productSn.getText());
-        cacheData.setFeedingSn(productSnFeeding.getText());
-        JdbcUtils.setCache(cacheName, JSON.toJSONString(cacheData));
-    }
-
-    // 定时任务 -----------------------------------------------------------------------------------------------------
-
-    public Timer cjTimerText2;
-    public void heartData() {
-        if(cjTimerText2!=null) {
-            cjTimerText2.cancel();
-        }
-        cjTimerText2 = new Timer();
-        cjTimerText2.schedule(new TimerTask() {
-            public void run() {
-                if(MesClient.isLogin == 1 && isLocal == 0){
-                    plcHeart = !plcHeart;
-                    writeBoolean(plcDb+".0.6",plcHeart);
-                }
-            }
-        }, 1000,1000);
-    }
-
-    public Timer oprnoTimer;
-    public void startOprnoTimer() {
-
-        //心跳显示图标
-        if(oprnoTimer!=null) {
-            oprnoTimer.cancel();
-        }
-        oprnoTimer = new Timer();
-        oprnoTimer.schedule(new TimerTask() {
-            public void run() {
-                try{
-
-                    if(MesClient.isLogin == 1){
-                        Boolean retFeeding = readFeeding();
-//                        Boolean retFeeding = true;
-                        feedinglabel.setIcon(retFeeding?imageGreen:imageGray);
-
-                        transferToDevice = readFeedingTransferToDevice();
-                        transfertodevicelabel.setIcon(transferToDevice?imageGreen:imageGray);
-
-                        Boolean retFeedingAllow = readAllowFeeding();
-                        feedingmeslabel.setIcon(retFeedingAllow?imageGreen:imageGray);
-
-                        if(!retFeeding && retFeedingAllow){
-                            writeAllowFeeding(false);
-                        }
-
-                        Boolean retBlanking = readBlanking();
-                        blankinglabel.setIcon(retBlanking?imageGreen:imageGray);
-
-                        Boolean retAllowBlanking = readAllowBlanking();
-                        blankingmeslabel.setIcon(retAllowBlanking?imageGreen:imageGray);
-
-                        toDevice = readFeedingToDevice();
-                        feedingtodevicelabel.setIcon(toDevice?imageGreen:imageGray);
-
-                        toTransfer = readFeedingToTransfer();
-                        feedingtotransferlabel.setIcon(toTransfer?imageGreen:imageGray);
-
-                        Boolean retRunning = readRunning();
-                        runlabel.setIcon(retRunning?imageGreen:imageGray);
-
-                        if(retBlanking && productSn.getText().isEmpty()){
-                            writeAllowBlanking(true);
-                        }
-
-                        if(tjFeedingFlag == 0){
-                            if(retFeeding){
-                                barCode();
-                            }
-
-                        }else if(tjFeedingFlag == 1){
-                            // 中转车已隐藏:不再走 toTransfer 存码,统一按去设备处理
-//                            if(toDevice){ // 去设备
-                                if(tjFlag == 0 && retRunning){
-                                    tjFlag = 1;
-                                    productSn.setText(productSnFeeding.getText());
-//                                    resetBtn.setEnabled(false);
-                                    resetFeeding();
-                                }
-//                            }
-//                            if(toTransfer){ // 去中转台
-//                                int transferSn = readTransferNo();
-//                                int transferFloor = readTransferFloor();
-//                                if(transferSn > 0 && transferFloor > 0){
-//                                    if(tjStatus == 0) {
-//                                        tjStatus = 1;
-//                                        JSONObject retObj = DataUtil.saveSn(oprno+pos,productSnFeeding.getText().trim(),transferSn,transferFloor,MesClient.user_menu.getText());
-//                                        if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-//                                            resetFeeding();
-//                                        }else{
-//                                            if(retObj.get("result")==null){
-//                                                setMenuStatus("请求失败,请重试",-1);
-//                                            }else{
-//                                                if(retObj.get("result").toString().equalsIgnoreCase("false")){
-//                                                    setMenuStatus(retObj.getString("message"),-1);
-//                                                }
-//                                            }
-//                                        }
-//                                    }
-//                                }
-//                            }
-                        }
-
-                        if(tjFlag == 0 && !retBlanking){
-                            writeAllowBlanking(false);
-                        }
-
-                        if(tjFlag == 1){
-                            // 中转车已隐藏:不再走 toTransfer 存码,统一按去设备处理
-//                            if(toDevice){ // 去设备
-                                if(retRunning){
-                                    tjFlag = 2;
-                                    setMenuStatus("工件加工中", 0);
-                                    // 运行中清除下料反馈
-                                    writeAllowBlanking(false);
-                                }
-//                            }
-//                            if(toTransfer){ // 去中转台
-//                                int transferSn = readTransferNo();
-//                                int transferFloor = readTransferFloor();
-//                                if(transferSn > 0 && transferFloor > 0){
-//                                    if(tjStatus == 0) {
-//                                        tjStatus = 1;
-//                                        JSONObject retObj = DataUtil.saveSn(oprno+pos,productSn.getText().trim(),transferSn,transferFloor,MesClient.user_menu.getText());
-//                                        if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-//                                            resetFeeding();
-//                                        }else{
-//                                            if(retObj.get("result")==null){
-//                                                setMenuStatus("请求失败,请重试",-1);
-//                                            }else{
-//                                                if(retObj.get("result").toString().equalsIgnoreCase("false")){
-//                                                    setMenuStatus(retObj.getString("message"),-1);
-//                                                }
-//                                            }
-//                                        }
-//                                    }
-//                                }
-//                            }
-                        }else if(tjFlag == 2){
-                            if(retBlanking){
-                                tjFlag = 3;
-                                setMenuStatus("工件加工结束,结果上传中", 0);
-
-                                if(tjStatus == 0){
-                                    tjStatus = 1;
-
-                                    // 异步执行上传任务,防止阻塞
-                                    ExecutorService executor = Executors.newCachedThreadPool();
-                                    executor.submit(() -> {
-                                        // 你的异步任务代码
-                                        log.info("["+oprno+pos+"]质量上传任务开始");
-                                        try {
-                                            JSONObject retObj = DataUtil.sendQuality(productSn.getText(),oprno+pos,"OK",MesClient.user_menu.getText());
-                                            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                                                resetBlanking();
-                                            }else{
-                                                if(retObj.get("result")==null){
-                                                    setMenuStatus("请求失败,请重试",-1);
-                                                }else{
-                                                    if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                                                        setMenuStatus(retObj.getString("message"),-1);
-                                                    }
-                                                }
-                                            }
-                                        } catch (Exception e) {
-                                            Thread.currentThread().interrupt();
-                                        }
-                                        log.info("["+oprno+pos+"]质量上传任务结束");
-                                    });
-                                    // 关闭ExecutorService,不再接受新任务,等待已提交的任务完成
-                                    executor.shutdown();
-                                }
-                            }
-                        }
-                    }
-
-
-
-                }catch (Exception e) {
-                    log.error(e.getMessage());
-                }
-            }
-        }, 100,3000);
-    }
-
-    public void barCode(){
-        if(!productSnFeeding.getText().isEmpty()){
-            return;
-        }
-        String sn = readQrcode();
-        if(sn.equals("")){
-            if(productSnFeeding.getText().isEmpty()){
-                this.setMenuStatus("扫码结果为空", -1);
-            }
-        }else{
-            productSnFeeding.setText(sn);
-            lastSn = sn;
-            // 查询工件质量
-            JSONObject retObj = DataUtil.checkQuality(sn,oprno+pos,MesClient.user_menu.getText());
-            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                tjFeedingFlag = 1;//可加工
-                setMenuStatus("工件可以加工", 0);
-                writeAllowFeeding(true);
-//                resetFeedingBtn.setEnabled(false);
-            }else{
-                writeAllowFeeding(false);
-                resetFeedingBtn.setEnabled(true);
-                if(retObj.get("result")==null){
-                    setMenuStatus("请求失败,请重试",-1);
-                }else{
-                    if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                        setMenuStatus(retObj.getString("message"),-1);
-                    }
-                }
-            }
-        }
-    }
-
-    public void resetBlanking() {
-        tjFlag = 0;
-        tjStatus = 0;
-        productSn.setText("");
-        setMenuStatus("工件结果已提交,等待下一件",0);
-        resetBtn.setEnabled(true);
-//        writeAllowFeeding(false);
-//        writeAllowBlanking(false);
-    }
-
-    public void resetFeeding() {
-        tjScan = 0;
-        productSnFeeding.setText("");
-        tjFeedingFlag = 0;
-        setMenuStatus("等待请求扫码",0);
-        resetFeedingBtn.setEnabled(true);
-        writeAllowFeeding(false);
-    }
-
-    public void resetScanA() {
-        tjFeedingFlag = 0;
-        tjFlag = 0;
-        tjStatus = 0;
-        productSn.setText("");
-        productSnFeeding.setText("");
-        setMenuStatus("等待请求扫码",0);
-        resetBtn.setEnabled(true);
-        resetFeedingBtn.setEnabled(true);
-        writeAllowFeeding(false);
-        writeAllowBlanking(false);
-    }
-    // 与设备交互-----------------------------------------------------------------------------------------
-
-    // 请求扫码
-    public boolean readFeeding(){
-        if(isLocal == 1){
-            return false;
-        }else{
-            return readBoolean(plcDb+".0.0");
-        }
-    }
-
-    // 读取上料到设备
-    public boolean readFeedingToDevice(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".0.7");
-        }
-    }
-
-    // 读取上料到中转车
-    public boolean readFeedingToTransfer(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".1.0");
-        }
-    }
-
-    // 读取中转车上料到设备
-    public boolean readFeedingTransferToDevice(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".1.1");
-        }
-    }
-
-    // 读取下料信号
-    public boolean readBlanking(){
-        if(isLocal == 1){
-            return false;
-        }else{
-            return readBoolean(plcDb+".0.1");
-        }
-    }
-
-    // 读取设备运行信号
-    public boolean readRunning(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".0.2");
-        }
-    }
-
-    // 读取设备通讯状态
-    public boolean readDeviceStatus(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".0.3");
-        }
-    }
-
-    // 读取MES允许上料信号
-    public boolean readAllowFeeding(){
-        if(isLocal == 1){
-            return false;
-        }else{
-            return readBoolean(plcDb+".0.4");
-        }
-    }
-
-    // 写入MES允许上料信号
-    public boolean writeAllowFeeding(Boolean b){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return writeBoolean(plcDb+".0.4",b);
-        }
-    }
-
-    // 读取MES允许下料信号
-    public boolean readAllowBlanking(){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return readBoolean(plcDb+".0.5");
-        }
-    }
-
-    // 写入MES允许下料信号
-    public boolean writeAllowBlanking(Boolean b){
-        if(isLocal == 1){
-            return true;
-        }else{
-            return writeBoolean(plcDb+".0.5",b);
-        }
-    }
-
-    // 读取中转车号
-    public int readTransferNo(){
-        if(isLocal == 1){
-            return 1;
-        }else{
-            return s7PLC.readInt16(plcDb+".2.0");
-        }
-    }
-
-    // 读取中转车层数
-    public int readTransferFloor(){
-        if(isLocal == 1){
-            return 1;
-        }else{
-            return s7PLC.readInt16(plcDb+".4.0");
-        }
-    }
-
-    // 控制扫码枪读取二维码
-    public String readQrcode(){
-        String sn = "";
-        if(isLocal == 1){
-            return "";
-        }else{
-            try{
-                // 中转车已隐藏:不再从中转车取码,统一走扫码枪
-//                if(transferToDevice){ // 中转车上料到设备
-//                    int transferSn = readTransferNo();
-//                    int transferFloor = readTransferFloor();
-//                    JSONObject retObj = DataUtil.getSn(oprno+pos,transferSn,transferFloor,MesClient.user_menu.getText());
-//                    if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-//                        sn = retObj.getString("data");
-//                    }else{
-//                        writeAllowFeeding(false);
-//                        if(retObj.get("result")==null){
-//                            this.setMenuStatus("请求失败,请重试",-1);
-//                        }else{
-//                            if(retObj.get("result").toString().equalsIgnoreCase("false")){
-//                                this.setMenuStatus(retObj.getString("message"),-1);
-//                            }
-//                        }
-//                    }
-//                }else{
-                    int transferSn = readTransferNo();
-                    if(transferSn == 1){
-                        if(pos.equals("A") || pos.equals("B") || pos.equals("C") || pos.equals("D") || pos.equals("E")){
-                            sn = MesClient.scanQrcode1();
-                        }else{
-                            sn = MesClient.scanQrcode3();
-                        }
-                    }else{
-                        if(pos.equals("A") || pos.equals("B") || pos.equals("C") || pos.equals("D") || pos.equals("E")){
-                            sn = MesClient.scanQrcode2();
-                        }else{
-                            sn = MesClient.scanQrcode4();
-                        }
-                    }
-//                }
-            }catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-
-        return sn;
-    }
-
-    public void scanQrcode(){
-        log.info("scanQrcode");
-        // 异步执行上传任务,防止阻塞
-        ExecutorService executor = Executors.newCachedThreadPool();
-        executor.submit(() -> {
-            MesClient.scanQrcode4();
-        });
-        // 关闭ExecutorService,不再接受新任务,等待已提交的任务完成
-        executor.shutdown();
-
-    }
-
-    public boolean readBoolean(String db){
-        boolean permit;
-        try {
-            permit = s7PLC.readBoolean(db);
-            log.info("<"+this.oprno+this.pos+">读取"+db+"-->成功,结果:{}", permit ? "true" : "false");
-        } catch (Exception e) {
-            log.error("<"+this.oprno+this.pos+">读取"+db+"-->失败", e);
-            return false;
-        }
-        return permit;
-    }
-
-    public boolean writeBoolean(String db, boolean permit){
-        try {
-            s7PLC.writeBoolean(db,permit);
-            // 心跳日志已关闭
-        } catch (Exception e) {
-            log.error("<"+this.oprno+this.pos+">写入"+db+"-->失败,写入内容:{}", permit ? "true" : "false");
-            log.error(e.getMessage(), e);
-            return false;
-        }
-        return true;
-    }
-}

+ 316 - 0
src/com/mes/component/WorkJPanelScan.java

@@ -0,0 +1,316 @@
+package com.mes.component;
+
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+import com.mes.ui.MesClient;
+import com.mes.util.SnSlot;
+import com.mes.util.TCPClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 正面扫码面板(PLC 92 DB7010)
+ * DB7010.0.0 请求扫码 / DB7010.0.1 扫码完成反馈 / DB7010.6(INT) 扫码枪号;0.2、2、4 为预留,不读不写
+ * 请求扫码时按枪号触发对应扫码枪,读到 16 位码存入正面扫码槽,正面机台请求上料时取用
+ */
+public class WorkJPanelScan extends JPanel {
+
+    public static final Logger log = LoggerFactory.getLogger(WorkJPanelScan.class);
+
+    public static final int SN_LENGTH = 16;
+
+    public String oprno = "OP130";
+    public String plcIp = WorkJPanelCnc.PLC_FRONT;
+    public String plcDb = "DB7010";
+    public String gunAddr = "DB7010.6"; // 扫码枪号 INT,1 对应 scan.ips 第一个
+
+    public S7PLC s7PLC;
+    public SnSlot slot;
+
+    public Integer workWidth = 470;
+    public Integer workHeight = 300;
+
+    private boolean scanDone = false; // 本次请求已扫码成功,等 PLC 复位请求
+
+    public JButton status_menu;
+    public JTextField productSnFeeding; // 扫码槽当前码
+    public JTextField gunField;         // 当前扫码枪
+    public JButton resetFeedingBtn;
+
+    public JLabel feedinglabel;    // 0.0 请求扫码
+    public JLabel feedingmeslabel; // 0.1 扫码完成反馈
+
+    public ImageIcon imageGreen;
+    public ImageIcon imageGray;
+
+    public JButton signalBtn;
+    public PlcSignalMonitor02 signalMonitor;
+
+    public Timer oprnoTimer;
+
+    public WorkJPanelScan() {
+        this.setLayout(null);
+        this.setBackground(Color.WHITE);
+        this.slot = MesClient.frontSlot;
+
+        s7PLC = new S7PLC(EPlcType.S1500, plcIp);
+        s7PLC.setConnectTimeout(1000);
+        s7PLC.setReceiveTimeout(1000);
+
+        initUI();
+        productSnFeeding.setText(slot.getSn());
+        setMenuStatus("等待请求扫码", 0);
+        startOprnoTimer();
+    }
+
+    private void initUI() {
+        JLabel oprnoLabel = new JLabel(oprno);
+        oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        oprnoLabel.setForeground(Color.BLACK);
+        oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        oprnoLabel.setBounds(0, 0, workWidth / 5, 40);
+        this.add(oprnoLabel);
+
+        JLabel nameLabel = new JLabel("正面扫码");
+        nameLabel.setHorizontalAlignment(SwingConstants.CENTER);
+        nameLabel.setForeground(Color.BLACK);
+        nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        nameLabel.setBounds(0, 17, workWidth / 5, 40);
+        this.add(nameLabel);
+
+        status_menu = new JButton("等待请求扫码");
+        status_menu.setBounds(workWidth / 5, 0, workWidth * 4 / 5, 40);
+        status_menu.setForeground(Color.GREEN);
+        status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        status_menu.setBackground(Color.BLACK);
+        this.add(status_menu);
+
+        addRowLabel("扫码", 50);
+        productSnFeeding = createField(50, workWidth * 3 / 5);
+        productSnFeeding.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                String sn = productSnFeeding.getText().trim();
+                if (!sn.isEmpty()) {
+                    MesClient.createQRCode(sn, oprno + "扫码");
+                }
+            }
+        });
+
+        resetFeedingBtn = new JButton("清空");
+        resetFeedingBtn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
+        resetFeedingBtn.setBounds(workWidth * 4 / 5 + 10, 50, workWidth / 5 - 10, 40);
+        resetFeedingBtn.addActionListener(e -> {
+            String sn = slot.getSn();
+            if (sn.isEmpty()) {
+                return;
+            }
+            if (confirm("确定清空扫码结果" + sn + "吗?\n请求扫码仍在时会重新扫码")) {
+                resetScan();
+            }
+        });
+        this.add(resetFeedingBtn);
+
+        addRowLabel("扫码枪", 100);
+        gunField = createField(100, workWidth * 4 / 5);
+        gunField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        gunField.setText("枪号读 " + gunAddr + ",共 " + MesClient.scanClients.length + " 把");
+
+        imageGreen = scaledIcon("/bg/green_dot.png");
+        imageGray = scaledIcon("/bg/grey_dot.png");
+
+        feedinglabel = createLight("请求扫码", 0, 150);
+        feedingmeslabel = createLight("扫码完成反馈", workWidth / 3, 150);
+        feedingmeslabel.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                boolean ret = readBoolean(plcDb + ".0.1");
+                if (confirm("确定修改扫码完成反馈(" + plcDb + ".0.1)为" + !ret + "吗?")) {
+                    writeScanFinish(!ret);
+                }
+            }
+        });
+
+        JLabel ipLabel = new JLabel("PLC " + plcIp + " | " + plcDb + " | 枪号 " + gunAddr);
+        ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
+        ipLabel.setForeground(Color.DARK_GRAY);
+        ipLabel.setBounds(5, 230, workWidth * 2 / 3 + 55, 26);
+        this.add(ipLabel);
+
+        signalMonitor = new PlcSignalMonitor02(s7PLC, plcDb, gunAddr, oprno + "扫码", MesClient.mesClientFrame);
+        signalBtn = signalMonitor.createSignalButton();
+        signalBtn.setBounds(workWidth * 2 / 3 + 63, 230, 90, 26);
+        this.add(signalBtn);
+    }
+
+    private void addRowLabel(String text, int y) {
+        JLabel label = new JLabel(text);
+        label.setHorizontalAlignment(SwingConstants.CENTER);
+        label.setForeground(Color.BLACK);
+        label.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        label.setBounds(0, y, workWidth / 5, 40);
+        this.add(label);
+    }
+
+    private JTextField createField(int y, int width) {
+        JTextField field = new JTextField("");
+        field.setHorizontalAlignment(SwingConstants.CENTER);
+        field.setForeground(Color.BLACK);
+        field.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        field.setEditable(false);
+        field.setBounds(workWidth / 5, y, width, 40);
+        this.add(field);
+        return field;
+    }
+
+    private JLabel createLight(String text, int x, int y) {
+        JLabel label = new JLabel(text);
+        label.setHorizontalAlignment(SwingConstants.LEFT);
+        label.setIcon(imageGray);
+        label.setForeground(Color.BLACK);
+        label.setBounds(x, y, workWidth / 3, 30);
+        this.add(label);
+        return label;
+    }
+
+    private static ImageIcon scaledIcon(String path) {
+        ImageIcon icon = new ImageIcon(MesClient.class.getResource(path));
+        icon.setImage(icon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
+        return icon;
+    }
+
+    private static boolean confirm(String msg) {
+        return JOptionPane.showConfirmDialog(null, msg, "操作确认", JOptionPane.YES_NO_OPTION,
+                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
+    }
+
+    public void setMenuStatus(String msg, int error) {
+        status_menu.setForeground(error == 0 ? Color.GREEN : Color.RED);
+        status_menu.setText(msg);
+    }
+
+    // 定时任务 -----------------------------------------------------------------------------------------------------
+
+    public void startOprnoTimer() {
+        if (oprnoTimer != null) {
+            oprnoTimer.cancel();
+        }
+        oprnoTimer = new Timer();
+        oprnoTimer.schedule(new TimerTask() {
+            public void run() {
+                try {
+                    if (MesClient.isLogin == 1) {
+                        cycle();
+                    }
+                } catch (Exception e) {
+                    log.error("<{}扫码>轮询异常", oprno, e);
+                }
+            }
+        }, 100, 1500);
+    }
+
+    private void cycle() {
+        boolean retScan = readBoolean(plcDb + ".0.0");
+        boolean retFinish = readBoolean(plcDb + ".0.1");
+        feedinglabel.setIcon(retScan ? imageGreen : imageGray);
+        feedingmeslabel.setIcon(retFinish ? imageGreen : imageGray);
+
+        // 显示扫码槽当前码,被正面机台取走后变空
+        productSnFeeding.setText(slot.getSn());
+
+        if (!retScan) {
+            scanDone = false;
+            if (retFinish) {
+                writeScanFinish(false);
+            }
+            return;
+        }
+        if (scanDone) {
+            if (!retFinish) {
+                writeScanFinish(true);
+            }
+            return;
+        }
+        doScan();
+    }
+
+    private void doScan() {
+        Integer plcGunNo = readGunNo();
+        int gunNo = plcGunNo == null ? 1 : plcGunNo; // 读不到枪号(点位还没加)时用 1 号枪
+        int gunCount = MesClient.scanClients.length;
+        if (gunNo < 1 || gunNo > gunCount) {
+            gunField.setText("枪号 " + gunNo + " 无效");
+            setMenuStatus("扫码枪号无效:" + gunNo + "(" + gunAddr + " 应为 1-" + gunCount + ")", -1);
+            return;
+        }
+
+        TCPClient client = MesClient.scanClients[gunNo - 1];
+        gunField.setText(gunNo + "号枪 " + client.serverIP + ":" + client.serverPort
+                + (plcGunNo == null ? "(默认)" : ""));
+        setMenuStatus(gunNo + "号枪扫码中...", 0);
+
+        String sn = MesClient.scanQrcode(gunNo);
+        if (sn.length() != SN_LENGTH) {
+            String reason = sn.isEmpty() ? "未读到码" : "码长度" + sn.length() + "位";
+            setMenuStatus(gunNo + "号枪扫码失败(" + reason + "),重试中", -1);
+            return;
+        }
+
+        slot.put(sn, gunNo + "号枪");
+        productSnFeeding.setText(sn);
+        writeScanFinish(true);
+        scanDone = true;
+        setMenuStatus("扫码成功" + (plcGunNo == null ? "(未读到" + gunAddr + ",用1号枪)" : ""), 0);
+    }
+
+    public void resetScan() {
+        slot.clear("人工清空");
+        productSnFeeding.setText("");
+        scanDone = false;
+        writeScanFinish(false);
+        setMenuStatus("扫码结果已清空,等待请求扫码", 0);
+    }
+
+    // 与设备交互 -----------------------------------------------------------------------------------------
+
+    // 读扫码枪号,读失败返回 null
+    private Integer readGunNo() {
+        try {
+            int no = s7PLC.readInt16(gunAddr);
+            log.info("<{}扫码>读取{}-->成功,枪号:{}", oprno, gunAddr, no);
+            return no;
+        } catch (Exception e) {
+            log.warn("<{}扫码>读取{}-->失败,使用1号枪:{}", oprno, gunAddr, e.getMessage());
+            return null;
+        }
+    }
+
+    public void writeScanFinish(boolean b) {
+        writeBoolean(plcDb + ".0.1", b);
+    }
+
+    public boolean readBoolean(String addr) {
+        try {
+            return s7PLC.readBoolean(addr);
+        } catch (Exception e) {
+            log.error("<{}扫码>读取{}-->失败:{}", oprno, addr, e.getMessage());
+            return false;
+        }
+    }
+
+    public boolean writeBoolean(String addr, boolean value) {
+        try {
+            s7PLC.writeBoolean(addr, value);
+            log.info("<{}扫码>写入{}-->成功,写入内容:{}", oprno, addr, value);
+            return true;
+        } catch (Exception e) {
+            log.error("<{}扫码>写入{}-->失败,写入内容:{},{}", oprno, addr, value, e.getMessage());
+            return false;
+        }
+    }
+}

+ 27 - 0
src/com/mes/ui/CacheData.java

@@ -9,6 +9,33 @@ public class CacheData {
     private Integer error;
     private Integer tjStatus;
     private String preSn;
+    private Integer tjFeedingFlag; // 上料取码状态 0=未取码 1=已取码校验通过
+    private String source;         // 码槽:码的来源(扫码枪N / OP130X)
+    private String saveTime;       // 码槽:存入时间
+
+    public Integer getTjFeedingFlag() {
+        return tjFeedingFlag;
+    }
+
+    public void setTjFeedingFlag(Integer tjFeedingFlag) {
+        this.tjFeedingFlag = tjFeedingFlag;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public String getSaveTime() {
+        return saveTime;
+    }
+
+    public void setSaveTime(String saveTime) {
+        this.saveTime = saveTime;
+    }
 
     public Integer getTjFlag() {
         return tjFlag;

+ 0 - 71
src/com/mes/ui/CncParamReq.java

@@ -1,71 +0,0 @@
-package com.mes.ui;
-
-/**
- * CNC过程参数请求类
- * 用于存储从OPC UA读取的CNC参数,并上传到服务端
- */
-public class CncParamReq {
-    private Integer id;
-    private String oprno;       // 工位号
-    private String lineSn;      // 产线编号
-    private String sn;          // 工件码
-    private String deviceCode;  // 设备编号
-    private String content;     // JSON格式的完整参数
-    private Integer sync;       // 同步状态 0=未同步 1=已同步
-
-    public Integer getId() {
-        return id;
-    }
-
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    public String getOprno() {
-        return oprno;
-    }
-
-    public void setOprno(String oprno) {
-        this.oprno = oprno;
-    }
-
-    public String getLineSn() {
-        return lineSn;
-    }
-
-    public void setLineSn(String lineSn) {
-        this.lineSn = lineSn;
-    }
-
-    public String getSn() {
-        return sn;
-    }
-
-    public void setSn(String sn) {
-        this.sn = sn;
-    }
-
-    public String getDeviceCode() {
-        return deviceCode;
-    }
-
-    public void setDeviceCode(String deviceCode) {
-        this.deviceCode = deviceCode;
-    }
-
-    public String getContent() {
-        return content;
-    }
-
-    public void setContent(String content) {
-        this.content = content;
-    }
-
-    public Integer getSync() {
-        return sync;
-    }
-
-    public void setSync(Integer sync) {
-        this.sync = sync;
-    }
-}

+ 0 - 79
src/com/mes/ui/DataUtil.java

@@ -96,85 +96,6 @@ public class DataUtil {
         }
     }
 
-    public static JSONObject getSn(String oprno,int transferSn,int transferFloor,String user){
-        try{
-            String enconding = "UTF-8";
-            InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
-            Properties pro = new Properties();
-            BufferedReader br = new BufferedReader(new InputStreamReader(is, enconding));
-            pro.load(br);
-            String mes_server_ip = pro.getProperty("mes.server_ip");
-            String lineSn = pro.getProperty("mes.line_sn").trim();
-            String url = "http://"+mes_server_ip+":8980/js/a/mes/mesTransfer/getSn";
-            String params = "__ajax=json&oprno="+oprno+"&lineSn="+lineSn+"&transferSn="+transferSn+"&transferFloor="+transferFloor+"&ucode="+user;
-            log.info("params="+params);
-            String result = doPost(url,params);
-            log.info("result="+result);
-
-            if(result.equalsIgnoreCase("false")) {
-                return null;
-            }else {
-                JdbcUtils.insertData(oprno, "100000", params, "MQDW", "");
-                return JSONObject.parseObject(result);
-            }
-        }catch (Exception e){
-            log.info("e="+e.getMessage());
-            return null;
-        }
-    }
-
-    public static JSONObject saveSn(String oprno,String sn,int transferSn,int transferFloor,String user){
-        try{
-            String enconding = "UTF-8";
-            InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
-            Properties pro = new Properties();
-            BufferedReader br = new BufferedReader(new InputStreamReader(is, enconding));
-            pro.load(br);
-            String mes_server_ip = pro.getProperty("mes.server_ip");
-            String lineSn = pro.getProperty("mes.line_sn").trim();
-            String url = "http://"+mes_server_ip+":8980/js/a/mes/mesTransfer/saveSn";
-            String params = "__ajax=json&oprno="+oprno+"&lineSn="+lineSn+"&sn="+sn+"&transferSn="+transferSn+"&transferFloor="+transferFloor+"&ucode="+user;
-            log.info("params="+params);
-            String result = doPost(url,params);
-            log.info("result="+result);
-
-            if(result.equalsIgnoreCase("false")) {
-                return null;
-            }else {
-                JdbcUtils.insertData(oprno, "100000", params, "MQDW", "");
-                return JSONObject.parseObject(result);
-            }
-        }catch (Exception e){
-            log.info("e="+e.getMessage());
-            return null;
-        }
-    }
-
-    public static JSONObject upCncParams(String upparams) {
-        try {
-            String enconding = "UTF-8";
-            InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
-            Properties pro = new Properties();
-            BufferedReader br = new BufferedReader(new InputStreamReader(is, enconding));
-            pro.load(br);
-            String mes_server_ip = pro.getProperty("mes.server_ip");
-            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductCncParam/batchsave";
-            String params = "__ajax=json&params=" + upparams;
-            log.info("上传CNC参数: params=" + params);
-            String result = doPost(url, params);
-            log.info("上传CNC参数结果: result=" + result);
-
-            if (result == null || result.equalsIgnoreCase("false")) {
-                return null;
-            } else {
-                return JSONObject.parseObject(result);
-            }
-        } catch (Exception e) {
-            log.error("上传CNC参数异常: " + e.getMessage());
-            return null;
-        }
-    }
-
     /**
      * 查询工作记录
      * @param oprno 工位号(5位如OP150会查询所有子工位,空则查所有)

+ 1 - 15
src/com/mes/ui/LoginFarme.java

@@ -26,7 +26,7 @@ public class LoginFarme extends JFrame {
     static JButton scanLoginButton = new JButton("扫  码  登  录");
 
     public LoginFarme(){
-        setTitle("MES系统客户端:OP150总成CNC");
+        setTitle("MES系统客户端:OP130/OP135总成CNC");
 
         ImageIcon bg = new ImageIcon(MesClient.class.getResource("/background.png"));
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -188,20 +188,6 @@ public class LoginFarme extends JFrame {
                     //     MesClient.indexPanelWork.add(MesClient.jfxPanel);
                     // }
                     MesClient.initWarehouseData();
-
-                    // 异步初始化OPC UA连接和启动CNC监控
-                    new Thread(() -> {
-                        try {
-                            log.info("开始异步初始化OPC UA连接...");
-                            MesClient.initOpcUaConnection();
-                            MesClient.startCncParamUpload();  // 定时上传CNC参数到服务端
-                            MesClient.startCncParamRead(); // 定时采集CNC参数保存到本地
-                            MesClient.startCncDisplayRefresh(); // 启动CNC数据显示刷新
-                            log.info("OPC UA异步初始化完成");
-                        } catch (Exception e) {
-                            log.error("OPC UA异步初始化异常: {}", e.getMessage());
-                        }
-                    }, "OpcUa-Init-Thread").start();
                 }
 
             }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 94 - 1251
src/com/mes/ui/MesClient.java


+ 0 - 673
src/com/mes/util/CncData.java

@@ -1,673 +0,0 @@
-package com.mes.util;
-
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * CNC 数控系统数据模型
- * 用于存储从 OPC UA 读取的所有 CNC 参数
- * 
- * @author MES
- * @version 1.0
- */
-public class CncData implements Serializable {
-    
-    private static final long serialVersionUID = 1L;
-    
-    // ==================== 基本信息 ====================
-    
-    /** 最大轴数 */
-    private Integer maxAxisNum;
-    
-    /** 有效轴数 */
-    private Integer validAxisNum;
-    
-    /** 主轴数 */
-    private Integer spindleNum;
-    
-    /** 主轴最高转速 */
-    private Double spindleMaxSpeed;
-    
-    // ==================== 状态信息 ====================
-    
-    /** CNC 状态码 (1=中断, 2=停止, 3=运行中, 4=等待, 5=中止) */
-    private Integer cncState;
-    
-    /** CNC 状态描述 */
-    private String cncStateDesc;
-    
-    /** 工作模式码 (0=JOG, 1=MDI, 2=AUTO) */
-    private Integer workMode;
-    
-    /** 工作模式描述 */
-    private String workModeDesc;
-    
-    /** 急停状态 */
-    private Integer emergency;
-    
-    // ==================== 程序信息 ====================
-    
-    /** 主程序号 */
-    private String mainProgNo;
-    
-    /** 主程序注释 */
-    private String mainProgNote;
-    
-    /** 子程序号 */
-    private String subProgNo;
-    
-    /** 子程序注释 */
-    private String subProgNote;
-    
-    /** 执行行号 */
-    private String exeLine;
-    
-    /** 执行代码 */
-    private String exeProgCode;
-    
-    // ==================== 时间信息 ====================
-    
-    /** 切削时间(毫秒) */
-    private Long cycleTime;
-    
-    /** 程序加工时间(毫秒) */
-    private Long operationTime;
-    
-    /** 系统累计开机时间(毫秒) */
-    private Long sumBootTime;
-    
-    /** 系统累计加工时间(毫秒) */
-    private Long sumRunTime;
-    
-    /** 系统累计切削时间(毫秒) */
-    private Long sumCutTime;
-    
-    // ==================== 加工信息 ====================
-    
-    /** 加工件数 */
-    private Integer quantity;
-    
-    // ==================== 速度信息 ====================
-    
-    /** 切削倍率(百分比) */
-    private Double cutRate;
-    
-    /** 主轴倍率(百分比) */
-    private Double spindleRate;
-    
-    /** 切削指定速度 F */
-    private Double cutOrderSpeed;
-    
-    /** 切削实际速度 */
-    private Double cutActSpeed;
-    
-    /** 主轴指定速度 S */
-    private Double spindleOrderSpeed;
-    
-    /** 主轴实际速度 */
-    private Double spindleActSpeed;
-    
-    // ==================== 负载温度信息 ====================
-    
-    /** 伺服负载(百分比) */
-    private Double servoLoad;
-    
-    /** 伺服温度(摄氏度) */
-    private Integer servoTemp;
-    
-    /** 主轴负载(百分比) */
-    private Double spindleLoad;
-    
-    /** 主轴温度(摄氏度) */
-    private Integer spindleTemp;
-    
-    // ==================== 刀具信息 ====================
-    
-    /** 刀具名称 */
-    private String toolIdent;
-    
-    /** 刀号 */
-    private Integer toolNo;
-    
-    // ==================== 刀具寿命信息 ====================
-    
-    /** 刀具寿命目标值(初始总寿命) */
-    private Integer toolLifeTarget;
-    
-    /** 刀具寿命剩余值(会递减) */
-    private Integer toolLifeRemain;
-    
-    /** 刀具寿命预警值 */
-    private Integer toolLifeWarning;
-    
-    // ==================== 坐标信息 ====================
-    
-    /** 机械坐标 Map (轴索引 -> 坐标值) */
-    private Map<Integer, Double> machineCoordinates;
-    
-    /** 绝对编程坐标 Map (轴索引 -> 坐标值) */
-    private Map<Integer, Double> absoluteCoordinates;
-    
-    /** X轴机械坐标 */
-    private Double machineX;
-    
-    /** Y轴机械坐标 */
-    private Double machineY;
-    
-    /** Z轴机械坐标 */
-    private Double machineZ;
-    
-    // ==================== 报警信息 ====================
-    
-    /** 报警数量 */
-    private Integer alarmCount;
-    
-    /** 报警号(第一个报警) */
-    private Long alarmNo;
-    
-    /** 报警类型(第一个报警) */
-    private Long alarmType;
-    
-    /** 报警信息 */
-    private String alarmMsg;
-    
-    /** 所有报警号(逗号分隔),如 "3000,3001" */
-    private String alarmNoList;
-    
-    // ==================== 读取时间戳 ====================
-    
-    /** 数据读取时间戳 */
-    private Long timestamp;
-    
-    // ==================== 构造函数 ====================
-    
-    public CncData() {
-        this.timestamp = System.currentTimeMillis();
-    }
-    
-    // ==================== Getter & Setter ====================
-    
-    public Integer getMaxAxisNum() {
-        return maxAxisNum;
-    }
-    
-    public void setMaxAxisNum(Integer maxAxisNum) {
-        this.maxAxisNum = maxAxisNum;
-    }
-    
-    public Integer getValidAxisNum() {
-        return validAxisNum;
-    }
-    
-    public void setValidAxisNum(Integer validAxisNum) {
-        this.validAxisNum = validAxisNum;
-    }
-    
-    public Integer getSpindleNum() {
-        return spindleNum;
-    }
-    
-    public void setSpindleNum(Integer spindleNum) {
-        this.spindleNum = spindleNum;
-    }
-    
-    public Double getSpindleMaxSpeed() {
-        return spindleMaxSpeed;
-    }
-    
-    public void setSpindleMaxSpeed(Double spindleMaxSpeed) {
-        this.spindleMaxSpeed = spindleMaxSpeed;
-    }
-    
-    public Integer getCncState() {
-        return cncState;
-    }
-    
-    public void setCncState(Integer cncState) {
-        this.cncState = cncState;
-    }
-    
-    public String getCncStateDesc() {
-        return cncStateDesc;
-    }
-    
-    public void setCncStateDesc(String cncStateDesc) {
-        this.cncStateDesc = cncStateDesc;
-    }
-    
-    public Integer getWorkMode() {
-        return workMode;
-    }
-    
-    public void setWorkMode(Integer workMode) {
-        this.workMode = workMode;
-    }
-    
-    public String getWorkModeDesc() {
-        return workModeDesc;
-    }
-    
-    public void setWorkModeDesc(String workModeDesc) {
-        this.workModeDesc = workModeDesc;
-    }
-    
-    public Integer getEmergency() {
-        return emergency;
-    }
-    
-    public void setEmergency(Integer emergency) {
-        this.emergency = emergency;
-    }
-    
-    public String getMainProgNo() {
-        return mainProgNo;
-    }
-    
-    public void setMainProgNo(String mainProgNo) {
-        this.mainProgNo = mainProgNo;
-    }
-    
-    public String getMainProgNote() {
-        return mainProgNote;
-    }
-    
-    public void setMainProgNote(String mainProgNote) {
-        this.mainProgNote = mainProgNote;
-    }
-    
-    public String getSubProgNo() {
-        return subProgNo;
-    }
-    
-    public void setSubProgNo(String subProgNo) {
-        this.subProgNo = subProgNo;
-    }
-    
-    public String getSubProgNote() {
-        return subProgNote;
-    }
-    
-    public void setSubProgNote(String subProgNote) {
-        this.subProgNote = subProgNote;
-    }
-    
-    public String getExeLine() {
-        return exeLine;
-    }
-    
-    public void setExeLine(String exeLine) {
-        this.exeLine = exeLine;
-    }
-    
-    public String getExeProgCode() {
-        return exeProgCode;
-    }
-    
-    public void setExeProgCode(String exeProgCode) {
-        this.exeProgCode = exeProgCode;
-    }
-    
-    public Long getCycleTime() {
-        return cycleTime;
-    }
-    
-    public void setCycleTime(Long cycleTime) {
-        this.cycleTime = cycleTime;
-    }
-    
-    public Long getOperationTime() {
-        return operationTime;
-    }
-    
-    public void setOperationTime(Long operationTime) {
-        this.operationTime = operationTime;
-    }
-    
-    public Long getSumBootTime() {
-        return sumBootTime;
-    }
-    
-    public void setSumBootTime(Long sumBootTime) {
-        this.sumBootTime = sumBootTime;
-    }
-    
-    public Long getSumRunTime() {
-        return sumRunTime;
-    }
-    
-    public void setSumRunTime(Long sumRunTime) {
-        this.sumRunTime = sumRunTime;
-    }
-    
-    public Long getSumCutTime() {
-        return sumCutTime;
-    }
-    
-    public void setSumCutTime(Long sumCutTime) {
-        this.sumCutTime = sumCutTime;
-    }
-    
-    public Integer getQuantity() {
-        return quantity;
-    }
-    
-    public void setQuantity(Integer quantity) {
-        this.quantity = quantity;
-    }
-    
-    public Double getCutRate() {
-        return cutRate;
-    }
-    
-    public void setCutRate(Double cutRate) {
-        this.cutRate = cutRate;
-    }
-    
-    public Double getSpindleRate() {
-        return spindleRate;
-    }
-    
-    public void setSpindleRate(Double spindleRate) {
-        this.spindleRate = spindleRate;
-    }
-    
-    public Double getCutOrderSpeed() {
-        return cutOrderSpeed;
-    }
-    
-    public void setCutOrderSpeed(Double cutOrderSpeed) {
-        this.cutOrderSpeed = cutOrderSpeed;
-    }
-    
-    public Double getCutActSpeed() {
-        return cutActSpeed;
-    }
-    
-    public void setCutActSpeed(Double cutActSpeed) {
-        this.cutActSpeed = cutActSpeed;
-    }
-    
-    public Double getSpindleOrderSpeed() {
-        return spindleOrderSpeed;
-    }
-    
-    public void setSpindleOrderSpeed(Double spindleOrderSpeed) {
-        this.spindleOrderSpeed = spindleOrderSpeed;
-    }
-    
-    public Double getSpindleActSpeed() {
-        return spindleActSpeed;
-    }
-    
-    public void setSpindleActSpeed(Double spindleActSpeed) {
-        this.spindleActSpeed = spindleActSpeed;
-    }
-    
-    public Double getServoLoad() {
-        return servoLoad;
-    }
-    
-    public void setServoLoad(Double servoLoad) {
-        this.servoLoad = servoLoad;
-    }
-    
-    public Integer getServoTemp() {
-        return servoTemp;
-    }
-    
-    public void setServoTemp(Integer servoTemp) {
-        this.servoTemp = servoTemp;
-    }
-    
-    public Double getSpindleLoad() {
-        return spindleLoad;
-    }
-    
-    public void setSpindleLoad(Double spindleLoad) {
-        this.spindleLoad = spindleLoad;
-    }
-    
-    public Integer getSpindleTemp() {
-        return spindleTemp;
-    }
-    
-    public void setSpindleTemp(Integer spindleTemp) {
-        this.spindleTemp = spindleTemp;
-    }
-    
-    public String getToolIdent() {
-        return toolIdent;
-    }
-    
-    public void setToolIdent(String toolIdent) {
-        this.toolIdent = toolIdent;
-    }
-    
-    public Integer getToolNo() {
-        return toolNo;
-    }
-    
-    public void setToolNo(Integer toolNo) {
-        this.toolNo = toolNo;
-    }
-    
-    public Integer getToolLifeTarget() {
-        return toolLifeTarget;
-    }
-    
-    public void setToolLifeTarget(Integer toolLifeTarget) {
-        this.toolLifeTarget = toolLifeTarget;
-    }
-    
-    public Integer getToolLifeRemain() {
-        return toolLifeRemain;
-    }
-    
-    public void setToolLifeRemain(Integer toolLifeRemain) {
-        this.toolLifeRemain = toolLifeRemain;
-    }
-    
-    public Integer getToolLifeWarning() {
-        return toolLifeWarning;
-    }
-    
-    public void setToolLifeWarning(Integer toolLifeWarning) {
-        this.toolLifeWarning = toolLifeWarning;
-    }
-    
-    /**
-     * 判断刀具寿命是否处于预警状态
-     */
-    public boolean isToolLifeWarning() {
-        if (toolLifeRemain != null && toolLifeWarning != null) {
-            return toolLifeRemain <= toolLifeWarning && toolLifeRemain > 0;
-        }
-        return false;
-    }
-    
-    /**
-     * 判断刀具是否需要更换(寿命耗尽)
-     */
-    public boolean isToolLifeExpired() {
-        return toolLifeRemain != null && toolLifeRemain <= 0;
-    }
-    
-    /**
-     * 获取刀具寿命显示字符串
-     * 格式: "剩余值" 或 "剩余值 [预警]" 或 "0 [需换刀]"
-     */
-    public String getToolLifeDisplay() {
-        if (toolLifeRemain == null) {
-            return "-";
-        }
-        StringBuilder sb = new StringBuilder();
-        sb.append(toolLifeRemain);
-        if (isToolLifeExpired()) {
-            sb.append(" [需换刀]");
-        } else if (isToolLifeWarning()) {
-            sb.append(" [预警]");
-        }
-        return sb.toString();
-    }
-    
-    public Map<Integer, Double> getMachineCoordinates() {
-        return machineCoordinates;
-    }
-    
-    public void setMachineCoordinates(Map<Integer, Double> machineCoordinates) {
-        this.machineCoordinates = machineCoordinates;
-        // 同时设置 X, Y, Z 坐标
-        if (machineCoordinates != null) {
-            this.machineX = machineCoordinates.get(1);
-            this.machineY = machineCoordinates.get(2);
-            this.machineZ = machineCoordinates.get(3);
-        }
-    }
-    
-    public Map<Integer, Double> getAbsoluteCoordinates() {
-        return absoluteCoordinates;
-    }
-    
-    public void setAbsoluteCoordinates(Map<Integer, Double> absoluteCoordinates) {
-        this.absoluteCoordinates = absoluteCoordinates;
-    }
-    
-    public Double getMachineX() {
-        return machineX;
-    }
-    
-    public void setMachineX(Double machineX) {
-        this.machineX = machineX;
-    }
-    
-    public Double getMachineY() {
-        return machineY;
-    }
-    
-    public void setMachineY(Double machineY) {
-        this.machineY = machineY;
-    }
-    
-    public Double getMachineZ() {
-        return machineZ;
-    }
-    
-    public void setMachineZ(Double machineZ) {
-        this.machineZ = machineZ;
-    }
-    
-    public Integer getAlarmCount() {
-        return alarmCount;
-    }
-    
-    public void setAlarmCount(Integer alarmCount) {
-        this.alarmCount = alarmCount;
-    }
-    
-    public Long getAlarmNo() {
-        return alarmNo;
-    }
-    
-    public void setAlarmNo(Long alarmNo) {
-        this.alarmNo = alarmNo;
-    }
-    
-    public Long getAlarmType() {
-        return alarmType;
-    }
-    
-    public void setAlarmType(Long alarmType) {
-        this.alarmType = alarmType;
-    }
-    
-    public String getAlarmMsg() {
-        return alarmMsg;
-    }
-    
-    public void setAlarmMsg(String alarmMsg) {
-        this.alarmMsg = alarmMsg;
-    }
-    
-    public String getAlarmNoList() {
-        return alarmNoList;
-    }
-    
-    public void setAlarmNoList(String alarmNoList) {
-        this.alarmNoList = alarmNoList;
-    }
-    
-    public Long getTimestamp() {
-        return timestamp;
-    }
-    
-    public void setTimestamp(Long timestamp) {
-        this.timestamp = timestamp;
-    }
-    
-    // ==================== 便捷方法 ====================
-    
-    /**
-     * 判断是否在运行中
-     */
-    public boolean isRunning() {
-        return cncState != null && cncState == 3;
-    }
-    
-    /**
-     * 判断是否停止
-     */
-    public boolean isStopped() {
-        return cncState != null && cncState == 2;
-    }
-    
-    /**
-     * 判断是否有报警
-     */
-    public boolean hasAlarm() {
-        return alarmCount != null && alarmCount > 0;
-    }
-    
-    /**
-     * 判断是否是自动模式
-     */
-    public boolean isAutoMode() {
-        return workMode != null && workMode == 2;
-    }
-    
-    /**
-     * 获取切削时间(秒)
-     */
-    public Long getCycleTimeSeconds() {
-        return cycleTime != null ? cycleTime / 1000 : null;
-    }
-    
-    /**
-     * 获取格式化的切削时间 (HH:MM:SS)
-     */
-    public String getCycleTimeFormatted() {
-        if (cycleTime == null) return null;
-        long seconds = cycleTime / 1000;
-        long hours = seconds / 3600;
-        long minutes = (seconds % 3600) / 60;
-        long secs = seconds % 60;
-        return String.format("%02d:%02d:%02d", hours, minutes, secs);
-    }
-    
-    @Override
-    public String toString() {
-        return "CncData{" +
-                "cncState=" + cncStateDesc +
-                ", workMode=" + workModeDesc +
-                ", mainProgNo='" + mainProgNo + '\'' +
-                ", exeLine='" + exeLine + '\'' +
-                ", spindleActSpeed=" + spindleActSpeed +
-                ", cutRate=" + cutRate +
-                ", quantity=" + quantity +
-                ", alarmCount=" + alarmCount +
-                ", machineX=" + machineX +
-                ", machineY=" + machineY +
-                ", machineZ=" + machineZ +
-                '}';
-    }
-}
-

+ 0 - 101
src/com/mes/util/JdbcUtils.java

@@ -2,7 +2,6 @@ package com.mes.util;
 
 import com.alibaba.fastjson2.JSON;
 import com.mes.ui.CacheData;
-import com.mes.ui.CncParamReq;
 import com.mes.ui.ParamReq;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -24,7 +23,6 @@ public class JdbcUtils {
             conn = DriverManager.getConnection("jdbc:sqlite:mes_db.db");//连接数据库zhou.db,不存在则创建
             log.info("连接到SQLite数据库成功!");
             create_bw_record();//初始化结构表
-            create_cnc_data();//初始化CNC参数表
             //create_measure_data();//初始化测试数据表
         } catch (Exception e) {
             // TODO Auto-generated catch block
@@ -197,105 +195,6 @@ public class JdbcUtils {
 		return ret;
 	}
 
-	// ==================== CNC参数表相关方法 ====================
-
-	/**
-	 * 创建CNC参数表
-	 */
-	public static void create_cnc_data() throws SQLException {
-		Statement statement = conn.createStatement();
-		String sqlEquipment = "CREATE TABLE if not exists bw_cnc("
-				+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
-				+ "oprno VARCHAR(20),"
-				+ "line_sn VARCHAR(20),"
-				+ "sn VARCHAR(100),"
-				+ "device_code VARCHAR(50),"
-				+ "content MEDIUMTEXT,"
-				+ "create_time DATETIME,"
-				+ "sync int(10) NULL DEFAULT 0"
-				+ ")";
-		statement.executeUpdate(sqlEquipment);
-		statement.close();
-		log.info("CNC参数表创建成功!");
-	}
-
-	/**
-	 * 插入CNC参数数据
-	 */
-	public static boolean insertCncData(String oprno, String lineSn, String sn, String deviceCode, String content) {
-		boolean ret = false;
-		String record_time = DateLocalUtils.getCurrentTime();
-		if (conn == null) {
-			ret = false;
-		} else {
-			try {
-				Statement statement = conn.createStatement();
-				String insertSQL = "INSERT INTO bw_cnc (oprno, line_sn, sn, device_code, content, create_time, sync) VALUES"
-						+ " ('" + oprno + "', '" + lineSn + "', '" + sn + "', '" + deviceCode + "', '" + content + "', '" + record_time + "', 0)";
-				statement.executeUpdate(insertSQL);
-				statement.close();
-				ret = true;
-				log.info("CNC参数插入成功");
-			} catch (SQLException e) {
-				ret = false;
-				log.error("CNC参数插入失败: " + e.getMessage());
-			}
-		}
-		return ret;
-	}
-
-	/**
-	 * 获取未同步的CNC参数列表
-	 */
-	public static List<CncParamReq> getCncParams() {
-		String sql = "select id, oprno, line_sn, sn, device_code, content, sync from bw_cnc where sync = 0 order by id asc limit 100";
-		List<CncParamReq> params = new ArrayList<>();
-		try {
-			if (conn == null || conn.isClosed()) {
-				getConn();
-			}
-			Statement stmt = conn.createStatement();
-			ResultSet ret = stmt.executeQuery(sql);
-			while (ret.next()) {
-				CncParamReq req = new CncParamReq();
-				req.setId(ret.getInt(1));
-				req.setOprno(ret.getString(2));
-				req.setLineSn(ret.getString(3));
-				req.setSn(ret.getString(4));
-				req.setDeviceCode(ret.getString(5));
-				req.setContent(ret.getString(6));
-				req.setSync(ret.getInt(7));
-				params.add(req);
-			}
-			ret.close();
-			stmt.close();
-		} catch (SQLException e) {
-			log.error("获取CNC参数失败: " + e.getMessage());
-		}
-		return params;
-	}
-
-	/**
-	 * 更新CNC参数同步状态
-	 */
-	public static void updateCncSync(Integer id, Integer sync) {
-		try {
-			if (conn == null || conn.isClosed()) {
-				getConn();
-			}
-			Statement statement = conn.createStatement();
-			statement.executeUpdate("update bw_cnc set sync = " + sync + " where id = " + id);
-			statement.close();
-			log.info("CNC参数同步状态更新成功, id=" + id);
-		} catch (SQLException e) {
-			log.error("CNC参数同步状态更新失败: " + e.getMessage());
-		}
-	}
-
-
-
-
-    
     public static void create_measure_data() throws SQLException {
     	Statement statement=conn.createStatement();   //创建连接对象,是Java的一个操作数据库的重要接口
     	//statement.executeUpdate("drop table if exists measure_data");//判断是否有表tables的存在。有则删除

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 1100
src/com/mes/util/OpcUaUtils.java


+ 115 - 0
src/com/mes/util/SnSlot.java

@@ -0,0 +1,115 @@
+package com.mes.util;
+
+import com.alibaba.fastjson2.JSON;
+import com.mes.ui.CacheData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 单码暂存槽(只存一个码)
+ * 正面扫码槽:扫码面板扫到码后存入,正面机台请求上料时取走
+ * 反面待上料码:正面下料上传成功后存入,反面机台请求上料时取走
+ * 内容写入 SQLite cache 表,重启不丢
+ */
+public class SnSlot {
+
+    public static final Logger log = LoggerFactory.getLogger(SnSlot.class);
+
+    private final String name;      // 显示名称,用于日志
+    private final String cacheKey;  // SQLite 缓存 key
+
+    // 业务锁:机台取码校验 / 下料上传存码期间持有,防止多台机同时拿到或写入同一个码
+    private final ReentrantLock lock = new ReentrantLock();
+
+    private volatile String sn = "";
+    private volatile String source = "";
+    private volatile String saveTime = "";
+
+    public SnSlot(String name, String cacheKey) {
+        this.name = name;
+        this.cacheKey = cacheKey;
+        CacheData data = JdbcUtils.getCache(cacheKey);
+        if (data == null) {
+            return;
+        }
+        sn = nvl(data.getSn());
+        source = nvl(data.getSource());
+        saveTime = nvl(data.getSaveTime());
+        log.info("[{}] 恢复缓存 sn={} 来源={} 时间={}", name, sn, source, saveTime);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public String getSaveTime() {
+        return saveTime;
+    }
+
+    public boolean isEmpty() {
+        return sn.isEmpty();
+    }
+
+    // 拿不到锁说明其他机台正在取码/存码,本轮跳过即可
+    public boolean tryLock() {
+        return lock.tryLock();
+    }
+
+    public void unlock() {
+        if (lock.isHeldByCurrentThread()) {
+            lock.unlock();
+        }
+    }
+
+    /** 存码,旧码未取走时直接覆盖并记日志 */
+    public synchronized void put(String newSn, String from) {
+        String value = nvl(newSn).trim();
+        if (!sn.isEmpty() && !sn.equals(value)) {
+            log.warn("[{}] 旧码 {}(来源 {})未被取走,被新码 {} 覆盖", name, sn, source, value);
+        }
+        sn = value;
+        source = nvl(from);
+        saveTime = DateLocalUtils.getCurrentTime();
+        save();
+        log.info("[{}] 存码 sn={} 来源={}", name, sn, source);
+    }
+
+    /** 当前码等于 expected 才清空,防止把期间新存入的码误清掉 */
+    public synchronized boolean clearIf(String expected, String reason) {
+        if (sn.isEmpty() || !sn.equals(expected)) {
+            return false;
+        }
+        clear(reason);
+        return true;
+    }
+
+    public synchronized void clear(String reason) {
+        log.info("[{}] 清空 sn={} 来源={} 原因={}", name, sn, source, reason);
+        sn = "";
+        source = "";
+        saveTime = "";
+        save();
+    }
+
+    private void save() {
+        CacheData data = new CacheData();
+        data.setSn(sn);
+        data.setSource(source);
+        data.setSaveTime(saveTime);
+        JdbcUtils.setCache(cacheKey, JSON.toJSONString(data));
+    }
+
+    private static String nvl(String s) {
+        return s == null ? "" : s;
+    }
+}

+ 1 - 2
src/com/mes/util/TCPClient.java

@@ -1,6 +1,5 @@
 package com.mes.util;
 
-import com.mes.component.WorkJPanelOP150;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -15,7 +14,7 @@ public class TCPClient {
     public PrintWriter out;
     public BufferedReader in;
     private ExecutorService executor = Executors.newSingleThreadExecutor();
-    public static final Logger log =  LoggerFactory.getLogger(WorkJPanelOP150.class);
+    public static final Logger log =  LoggerFactory.getLogger(TCPClient.class);
 
     public TCPClient(String serverIP, int serverPort) {
         this.serverIP = serverIP;

+ 5 - 10
src/resources/config/config.properties

@@ -2,13 +2,8 @@
 mes.server_ip=192.168.17.170
 mes.line_sn=CZ060
 
-# OPC UA Configuration (CNC)
-# 6台CNC设备(80.32.10.42-47)
-opcua.user=OPCUA
-opcua.password=OPCUA123
-
-# 所有CNC地址
-opcua.urls=opc.tcp://80.32.10.42:4840,opc.tcp://80.32.10.43:4840,opc.tcp://80.32.10.44:4840,opc.tcp://80.32.10.45:4840,opc.tcp://80.32.10.46:4840,opc.tcp://80.32.10.47:4840
-
-# 对应的工位号(42->F, 43->G, 44->H, 45->I, 46->J, 47->K)
-opcua.oprnos=OP150F,OP150G,OP150H,OP150I,OP150J,OP150K
+# 正面扫码枪(PLC 92 DB7010 请求扫码时触发)
+# 按顺序对应 DB7010.6 扫码枪号:第1个=1号枪,第2个=2号枪,第3个=3号枪
+# 读不到 DB7010.6 时默认用 1 号枪
+scan.ips=192.168.90.61,192.168.90.62,192.168.90.63
+scan.port=9004