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; } } }