package com.mes.component; import com.mes.ui.DataUtil; import com.mes.ui.MesClient; import com.mes.ui.Oprno; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Timer; import java.util.TimerTask; /** * OP170 单台 CNC 面板:独立轮询、独立工件数据。 *

* 状态:0 等待上料 → 1 质量检查 → 2 加工中等下料 → 3 已下料等 OK/NG → 4 已交结果并写下料反馈 */ public class MesClientComponent extends JPanel { public static final Logger log = LoggerFactory.getLogger(MesClientComponent.class); public Oprno oprnoClass = new Oprno(); public JTextField productSn; public JButton status_menu; public static Integer workWidth = 255; public static Integer workHeight = 320; public JButton scanBtn; public JButton resetBtn; /** * 0:等待上料完成 * 1:已读码,质量检查中 * 2:上料完成反馈已写,加工中,等待下料完成 * 3:已见下料,等待 OK/NG 并提交 MES * 4:结果已提交且下料反馈已写,等待信号复位 */ public Integer work_status = 0; /** 上料完成反馈(可手动点击翻转) */ public JLabel sllabel1; /** 下料完成反馈(可手动点击翻转) */ public JLabel sllabel2; /** 上料完成信号(仅展示,不可点击) */ public JLabel sllabelLoad; /** 下料完成信号(仅展示,不可点击) */ public JLabel sllabelUnload; /** 工位状态:正常/故障 */ public JLabel stationStatusLabel; /** 加工结果展示 */ public JLabel resultLabel; private boolean loadEdgeHandled = false; private boolean resultHandled = false; private boolean unloadEdgeHandled = false; private boolean lastLoadComplete = false; private boolean lastUnloadComplete = false; /** 本轮是否已发送过 AQDW,避免每秒重复发 */ private boolean aqdwSent = false; /** 上一次工位状态(用于故障恢复后还原状态栏) */ private Boolean lastStationOk = null; public MesClientComponent(Oprno opno) { oprnoClass = opno; int w = workWidth; int h = workHeight; this.setLayout(null); this.setBackground(Color.WHITE); this.setBorder(BorderFactory.createLineBorder(Color.GRAY)); int margin = Math.max(8, w / 40); int hTitle = Math.max(36, (int) Math.round(h * 0.12)); int hStatus = Math.max(32, (int) Math.round(h * 0.12)); int hSn = Math.max(32, (int) Math.round(h * 0.12)); int hBtn = Math.max(40, (int) Math.round(h * 0.12)); int hLab = Math.max(28, (int) Math.round(h * 0.1)); int hFeedback = Math.max(44, (int) Math.round(h * 0.14)); // 反馈行加高,保证圆点完整显示 int gap = Math.max(4, margin / 2); int y = 0; JLabel oprnoLabel = new JLabel(displayOprno(oprnoClass.getOprno())); oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER); oprnoLabel.setForeground(Color.BLACK); int titleFont = Math.max(16, Math.min(36, w / 11)); oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, titleFont)); oprnoLabel.setBounds(0, y, w, hTitle); this.add(oprnoLabel); y += hTitle; status_menu = new JButton("等待上料完成"); status_menu.setBounds(margin, y, w - 2 * margin, hStatus); status_menu.setForeground(Color.GREEN); status_menu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { } }); int statusFont = Math.max(14, Math.min(22, w / 16)); status_menu.setFont(new Font("微软雅黑", Font.PLAIN, statusFont)); status_menu.setBackground(Color.BLACK); this.add(status_menu); y += hStatus + gap; productSn = new JTextField(""); // 过长时右对齐,尾部顶满格,开头被裁切,便于看清后缀 productSn.setHorizontalAlignment(SwingConstants.RIGHT); productSn.setForeground(Color.BLACK); int snFont = Math.max(14, Math.min(22, w / 16)); productSn.setFont(new Font("微软雅黑", Font.PLAIN, snFont)); productSn.setEditable(false); productSn.setBounds(margin, y, w - 2 * margin, hSn); productSn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); productSn.setToolTipText("点击显示二维码"); productSn.setColumns(10); productSn.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { // 与 op300 一致:可点;工控触摸屏上 pressed 比 clicked 更稳 String sn = productSn.getText() == null ? "" : productSn.getText().trim(); if (!sn.isEmpty()) { MesClient.createQRCode(sn, oprnoClass.getOprno()); } } }); this.add(productSn); y += hSn + gap; // 工位状态(station_status)暂不显示:现场点位未就绪时会一直报故障 stationStatusLabel = new JLabel(""); stationStatusLabel.setVisible(false); resultLabel = new JLabel("结果: --"); resultLabel.setHorizontalAlignment(SwingConstants.CENTER); resultLabel.setFont(new Font("微软雅黑", Font.PLAIN, Math.max(12, w / 18))); resultLabel.setBounds(margin, y, w - 2 * margin, hLab); this.add(resultLabel); y += hLab + gap; scanBtn = new JButton("读码"); final MesClientComponent thisComponent = this; scanBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { thisComponent.scanBtnClick(); } }); try { ImageIcon raw = new ImageIcon(MesClient.class.getResource("/bg/scan_barcode.png")); Image scaled = raw.getImage().getScaledInstance(18, 18, Image.SCALE_SMOOTH); scanBtn.setIcon(new ImageIcon(scaled)); } catch (Exception ignored) { } int btnFont = Math.max(14, Math.min(18, w / 14)); scanBtn.setFont(new Font("微软雅黑", Font.PLAIN, btnFont)); scanBtn.setMargin(new Insets(2, 4, 2, 4)); scanBtn.setIconTextGap(4); int btnW = (w - 2 * margin - gap) / 2; scanBtn.setBounds(margin, y, btnW, hBtn); this.add(scanBtn); resetBtn = new JButton("刷新"); resetBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { resetBtnClick(); } }); try { ImageIcon raw = new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")); Image scaled = raw.getImage().getScaledInstance(18, 18, Image.SCALE_SMOOTH); resetBtn.setIcon(new ImageIcon(scaled)); } catch (Exception ignored) { } resetBtn.setFont(new Font("微软雅黑", Font.PLAIN, btnFont)); resetBtn.setMargin(new Insets(2, 4, 2, 4)); resetBtn.setIconTextGap(4); resetBtn.setBounds(margin + btnW + gap, y, btnW, hBtn); this.add(resetBtn); y += hBtn + gap; sllabel1 = new JLabel("上料完成反馈"); sllabel1.setHorizontalAlignment(SwingConstants.CENTER); sllabel1.setVerticalAlignment(SwingConstants.CENTER); sllabel1.setIcon(dot(false)); sllabel1.setForeground(Color.BLACK); sllabel1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); sllabel1.setToolTipText("点击手动翻转上料完成反馈信号"); sllabel1.setBounds(0, y, w / 2, hFeedback); sllabel1.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { toggleFeedback(true); } }); this.add(sllabel1); sllabel2 = new JLabel("下料完成反馈"); sllabel2.setHorizontalAlignment(SwingConstants.CENTER); sllabel2.setVerticalAlignment(SwingConstants.CENTER); sllabel2.setIcon(dot(false)); sllabel2.setForeground(Color.BLACK); sllabel2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); sllabel2.setToolTipText("点击手动翻转下料完成反馈信号"); sllabel2.setBounds(w / 2, y, w - w / 2, hFeedback); sllabel2.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { toggleFeedback(false); } }); this.add(sllabel2); y += hFeedback + gap; // 上料/下料信号:只读展示 PLC→MES 完成位,不可点击 sllabelLoad = new JLabel("上料信号"); sllabelLoad.setHorizontalAlignment(SwingConstants.CENTER); sllabelLoad.setVerticalAlignment(SwingConstants.CENTER); sllabelLoad.setIcon(dot(false)); sllabelLoad.setForeground(Color.BLACK); sllabelLoad.setToolTipText("上料完成信号(只读)"); sllabelLoad.setBounds(0, y, w / 2, hFeedback); this.add(sllabelLoad); sllabelUnload = new JLabel("下料信号"); sllabelUnload.setHorizontalAlignment(SwingConstants.CENTER); sllabelUnload.setVerticalAlignment(SwingConstants.CENTER); sllabelUnload.setIcon(dot(false)); sllabelUnload.setForeground(Color.BLACK); sllabelUnload.setToolTipText("下料完成信号(只读)"); sllabelUnload.setBounds(w / 2, y, w - w / 2, hFeedback); this.add(sllabelUnload); this.startOprnoTimer(); } /** 面板标题:MES工位号 + CNC序号 */ private static String displayOprno(String oprno) { if ("OP170D".equals(oprno)) { return "OP170D / CNC4"; } if ("OP170E".equals(oprno)) { return "OP170E / CNC5"; } if ("OP170F".equals(oprno)) { return "OP170F / CNC6"; } return oprno; } private ImageIcon dot(boolean on) { try { String path = on ? "/bg/green_dot.png" : "/bg/grey_dot.png"; return new ImageIcon(MesClient.class.getResource(path)); } catch (Exception e) { return null; } } /** 手动翻转上料/下料完成反馈(需输入操作密码) */ private void toggleFeedback(boolean load) { if (!MesClient.verifyManualPlcPassword(this)) { return; } try { String addr = load ? oprnoClass.getLoad_feedback() : oprnoClass.getUnload_feedback(); boolean cur = MesClient.s7PLC.readBoolean(addr); boolean next = !cur; MesClient.s7PLC.writeBoolean(addr, next); if (load) { sllabel1.setIcon(dot(next)); setMenuStatus("手动设置上料完成反馈=" + next, 0); } else { sllabel2.setIcon(dot(next)); setMenuStatus("手动设置下料完成反馈=" + next, 0); } } catch (Exception ex) { log.error("手动翻转反馈失败", ex); setMenuStatus("手动写 PLC 失败", -1); } } public void refreshFeedbackIcons() { try { boolean lf = MesClient.s7PLC.readBoolean(oprnoClass.getLoad_feedback()); boolean uf = MesClient.s7PLC.readBoolean(oprnoClass.getUnload_feedback()); boolean lc = MesClient.s7PLC.readBoolean(oprnoClass.getLoad_complete()); boolean uc = MesClient.s7PLC.readBoolean(oprnoClass.getUnload_complete()); sllabel1.setIcon(dot(lf)); sllabel2.setIcon(dot(uf)); sllabelLoad.setIcon(dot(lc)); sllabelUnload.setIcon(dot(uc)); } catch (Exception ignored) { } } /** 手动读本机二维码并进入质量检查(需输入操作密码) */ public void scanBtnClick() { if (!MesClient.verifyManualPlcPassword(this)) { return; } if (work_status != 0 && work_status != 1) { status_menu.setText("工件加工中,请勿重复读码"); return; } try { String sn = MesClient.s7PLC.readString(oprnoClass.getProduct_sn()); if (sn == null || sn.trim().isEmpty()) { setMenuStatus("扫码结果为空", -1); return; } sn = sn.trim(); productSn.setText(sn); work_status = 1; loadEdgeHandled = true; aqdwSent = false; setMenuStatus("已读取工件码,等待校验", 0); } catch (Exception e) { log.error("读取 PLC 工件码失败", e); setMenuStatus("读取 PLC 失败", -1); } } public Timer oprnoTimer; public void startOprnoTimer() { if (oprnoTimer != null) { oprnoTimer.cancel(); } oprnoTimer = new Timer(); oprnoTimer.schedule(new TimerTask() { public void run() { try { pollOnce(); } catch (Exception e) { log.error(oprnoClass.getOprno() + " 轮询异常", e); } } }, 100, 1000); } private void pollOnce() { refreshFeedbackIcons(); boolean loadComplete = false; boolean unloadComplete = false; try { loadComplete = MesClient.s7PLC.readBoolean(oprnoClass.getLoad_complete()); unloadComplete = MesClient.s7PLC.readBoolean(oprnoClass.getUnload_complete()); } catch (Exception e) { return; } // 上料完成下降沿:仅复位本轮软件标记(不清 PLC 反馈,由 PLC 自行复位) if (lastLoadComplete && !loadComplete) { loadEdgeHandled = false; } lastLoadComplete = loadComplete; // 下料完成下降沿:仅复位软件状态;已完成的一轮清码回到 0(不清 PLC 反馈) if (lastUnloadComplete && !unloadComplete) { unloadEdgeHandled = false; if (work_status == 4) { resetCycleSoft(); } } lastUnloadComplete = unloadComplete; if (work_status == 0) { // 面板尚无码时按电平触发,避免 loadEdgeHandled 卡住后 PLC 已有上料完成+码却一直“等待上料完成” String curSn = productSn.getText(); boolean noSnYet = curSn == null || curSn.trim().isEmpty(); if (loadComplete && (!loadEdgeHandled || noSnYet)) { String sn = ""; try { sn = MesClient.s7PLC.readString(oprnoClass.getProduct_sn()); } catch (Exception e) { setMenuStatus("读二维码失败", -1); return; } if (sn == null || sn.trim().isEmpty()) { setMenuStatus("二维码为空", -1); return; } sn = sn.trim(); productSn.setText(sn); resultLabel.setText("结果: --"); resultLabel.setForeground(Color.BLACK); loadEdgeHandled = true; resultHandled = false; unloadEdgeHandled = false; aqdwSent = false; work_status = 1; setMenuStatus("已读码,质量检查中", 0); } } else if (work_status == 1) { String sn = productSn.getText(); if (sn == null || sn.trim().isEmpty()) { setMenuStatus("读码结果为空", -1); productSn.setText(""); work_status = 0; loadEdgeHandled = false; return; } if (!aqdwSent) { Boolean ok = DataUtil.checkQualityByGw(MesClient.nettyClient, sn, MesClient.user20, oprnoClass.getOprno()); if (ok) { aqdwSent = true; setMenuStatus("质量检查已发送,等待回复", 0); } else { setMenuStatus("质量检查消息发送失败", -1); } } // 等 MesRevice 回包后切到 status=2 } else if (work_status == 2) { // 加工中:先等下料完成,再进结果提交(不下料反馈) if (unloadComplete) { work_status = 3; setMenuStatus("已下料,等待工位 OK/NG", 0); } else { setMenuStatus("加工中,等待下料", 0); } } else if (work_status == 3) { // 已见下料:等 OK/NG → 提交 MES → 再写下料反馈 try { boolean okBit = MesClient.s7PLC.readBoolean(oprnoClass.getProcess_result_ok()); boolean ngBit = MesClient.s7PLC.readBoolean(oprnoClass.getProcess_result_ng()); if (!resultHandled && (okBit || ngBit)) { String ret = ngBit ? "NG" : "OK"; String sn = productSn.getText(); Boolean sent = DataUtil.sendQualityByGW(MesClient.nettyClient, sn, ret, MesClient.user20, oprnoClass.getOprno()); if (sent) { resultHandled = true; final String show = ret; SwingUtilities.invokeLater(() -> { resultLabel.setText("结果: " + show); resultLabel.setForeground("NG".equals(show) ? Color.RED : new Color(0, 128, 0)); }); setMenuStatus("结果已提交(" + ret + "),写下料反馈", 0); writeUnloadFeedback(); } else { setMenuStatus("结果提交失败,请重试", -1); } } } catch (Exception e) { log.error("读取工位结果失败", e); } } else if (work_status == 4) { setMenuStatus("下料完成反馈已写,等待复位", 0); } } /** 结果已交 MES 后再写下料完成反馈 */ private void writeUnloadFeedback() { try { MesClient.s7PLC.writeBoolean(oprnoClass.getUnload_feedback(), true); unloadEdgeHandled = true; work_status = 4; setMenuStatus("结果已提交,下料反馈已写", 0); refreshFeedbackIcons(); } catch (Exception e) { log.error("写下料完成反馈失败", e); setMenuStatus("写下料完成反馈失败", -1); } } /** 质量检查通过后由 MesRevice 调用:写上料完成反馈 + MKSW 记开始时间 */ public void onQualityPass(String sn) { try { MesClient.s7PLC.writeBoolean(oprnoClass.getLoad_feedback(), true); DataUtil.startWorkByGw(MesClient.nettyClient, sn, MesClient.user20, oprnoClass.getOprno()); work_status = 2; setMenuStatus("上料完成反馈已写,加工中", 0); refreshFeedbackIcons(); } catch (Exception e) { log.error("写上料完成反馈失败", e); setMenuStatus("写上料完成反馈失败", -1); } } public void onQualityFail(String msg) { aqdwSent = false; setMenuStatus(msg + ",自动重试中…", -1); // 不写上料反馈;aqdwSent 已清,下一轮轮询会再次发质量检查 } /** 工位从故障恢复后,按当前流程阶段还原状态栏文案 */ private void restoreStatusAfterFault() { switch (work_status) { case 0: setMenuStatus("等待上料完成", 0); break; case 1: setMenuStatus(aqdwSent ? "质量检查已发送,等待回复" : "已读码,质量检查中", 0); break; case 2: setMenuStatus("加工中,等待下料", 0); break; case 3: setMenuStatus("已下料,等待工位 OK/NG", 0); break; case 4: setMenuStatus("结果已提交,下料反馈已写", 0); break; default: break; } } private void resetCycleSoft() { productSn.setText(""); resultLabel.setText("结果: --"); resultLabel.setForeground(Color.BLACK); work_status = 0; loadEdgeHandled = false; unloadEdgeHandled = false; resultHandled = false; aqdwSent = false; setMenuStatus("等待上料完成", 0); } /** 刷新复位本机面板状态(需输入操作密码):清空 SN/结果,状态回到等待上料,允许重新跑一轮 */ public void resetBtnClick() { if (!MesClient.verifyManualPlcPassword(this)) { return; } productSn.setText(""); resultLabel.setText("结果: --"); resultLabel.setForeground(Color.BLACK); work_status = 0; loadEdgeHandled = false; resultHandled = false; unloadEdgeHandled = false; aqdwSent = false; lastStationOk = null; setMenuStatus("等待上料完成", 0); } public void setMenuStatus(String msg, int error) { SwingUtilities.invokeLater(() -> { if (error == 0) { this.status_menu.setForeground(Color.GREEN); } else { this.status_menu.setForeground(Color.RED); } this.status_menu.setText(msg); }); } }