| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- 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.nio.charset.StandardCharsets;
- import java.util.Timer;
- import java.util.TimerTask;
- /**
- * 扫码面板(每台 PLC 一个,92 发 1、2 号枪,93 发 3 号枪,由 PLC 决定)
- * DB7010.0.0 请求扫码 / DB7010.0.1 扫码完成反馈 / DB7010.6(INT) 扫码枪号;0.2、2、4 为预留,不读不写
- * 枪号 0=未发;正常同一时间 92、93 只有一个非 0,PLC 收到扫码完成反馈后清零
- * 请求扫码时 PLC 给几号就触发几号枪,读到码(不校验长度)存入本 PLC 的正面扫码码
- * 92 扫的码给 OP130A-E 取,93 扫的码给 OP130F(93 DB7004)取
- * 推码给 PLC(pushAddrs 按枪号对应 S7String 地址):扫到码后留存 15s 写入,到期清空,独立定时器,失败只记日志
- */
- public class WorkJPanelScan extends JPanel {
- public static final Logger log = LoggerFactory.getLogger(WorkJPanelScan.class);
- public String oprno = "OP130";
- public String plcIp;
- public String plcName; // 92 / 93,用于界面和日志
- public String plcDb = "DB7010";
- public String gunAddr = "DB7010.6";
- public S7PLC s7PLC;
- public SnSlot slot;
- public Integer workWidth = 470;
- public Integer workHeight = 300;
- private boolean scanDone = false; // 本次请求已扫码成功,等 PLC 复位请求
- private String lastGunState = ""; // 上次读到的枪号状态,变化时才记日志
- public JButton status_menu;
- public JTextField productSnFeeding; // 扫码槽当前码
- public JTextField gunField; // 本次扫码使用的枪
- public JButton resetFeedingBtn;
- public JLabel feedinglabel; // 0.0 请求扫码
- public JLabel feedingmeslabel; // 0.1 扫码完成反馈
- public JLabel gunNoLabel; // DB7010.6 实时枪号
- // 手动输入(参考 auto3,模拟扫码枪),由 设置->扫码手动输入 开关控制显示
- public JLabel manualLabel;
- public JTextField manualSnField;
- public JButton manualSubmitBtn;
- public volatile String pendingManualSn = ""; // 待使用的手动码,下次请求扫码时代替扫码枪
- public ImageIcon imageGreen;
- public ImageIcon imageGray;
- public JButton signalBtn;
- public PlcSignalMonitor02 signalMonitor;
- public Timer oprnoTimer;
- // 推码给 PLC:S7String 布局 [最大长度][实际长度][字符...]
- public static final int PUSH_SN_LENGTH = 24;
- public static final long PUSH_HOLD_MS = 15000;
- public PushPort[] pushPorts;
- public Timer pushTimer;
- // 一个推码地址的状态,holdSn/holdUntil 由扫码线程写,其余仅推码定时器线程使用
- public static class PushPort {
- public final int gunNo;
- public final String addr; // 起始地址,如 DB7020.1034
- public final String lenAddr; // 实际长度字节,起始地址+1
- public volatile String holdSn = "";
- public volatile long holdUntil = 0;
- public String writtenSn = null; // 初始 null,登录后首次运行必定清零一次,清掉上次残留
- public JTextField echoField;
- PushPort(int gunNo, String addr) {
- this.gunNo = gunNo;
- this.addr = addr;
- int dot = addr.lastIndexOf('.');
- this.lenAddr = addr.substring(0, dot + 1) + (Integer.parseInt(addr.substring(dot + 1)) + 1);
- }
- }
- /**
- * @param plcIp PLC 地址
- * @param slot 扫到的码存入的码槽(92 给 92 正面机,93 给 93 DB7004 正面机)
- * @param pushAddrs 推码地址,第 i 个对应 i+1 号枪;不传则不推码
- */
- public WorkJPanelScan(String plcIp, SnSlot slot, String... pushAddrs) {
- this.setLayout(null);
- this.setBackground(Color.WHITE);
- this.plcIp = plcIp;
- this.plcName = plcIp.substring(plcIp.lastIndexOf('.') + 1);
- this.slot = slot;
- this.pushPorts = new PushPort[pushAddrs.length];
- for (int i = 0; i < pushAddrs.length; i++) {
- pushPorts[i] = new PushPort(i + 1, pushAddrs[i]);
- }
- s7PLC = new S7PLC(EPlcType.S1500, plcIp);
- s7PLC.setConnectTimeout(1000);
- s7PLC.setReceiveTimeout(1000);
- initUI();
- productSnFeeding.setText(slot.getSn());
- setMenuStatus("等待请求扫码", 0);
- startOprnoTimer();
- if (pushPorts.length > 0) {
- startPushTimer();
- }
- }
- private String tag() {
- return oprno + "扫码" + plcName;
- }
- 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(plcName + "扫码");
- 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, tag());
- }
- }
- });
- 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("未扫码");
- 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("确定修改" + plcName + "扫码完成反馈(" + plcDb + ".0.1)为" + !ret + "吗?")) {
- writeScanFinish(!ret);
- }
- }
- });
- // 实时显示 PLC 给的枪号(DB7010.6),每轮刷新
- gunNoLabel = new JLabel("PLC枪号:-");
- gunNoLabel.setHorizontalAlignment(SwingConstants.LEFT);
- gunNoLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
- gunNoLabel.setForeground(Color.DARK_GRAY);
- gunNoLabel.setBounds(workWidth * 2 / 3, 150, workWidth / 3, 30);
- this.add(gunNoLabel);
- // 手动输入行
- manualLabel = new JLabel("手动");
- manualLabel.setHorizontalAlignment(SwingConstants.CENTER);
- manualLabel.setForeground(Color.BLACK);
- manualLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
- manualLabel.setBounds(0, 185, workWidth / 5, 40);
- this.add(manualLabel);
- manualSnField = new JTextField("");
- manualSnField.setHorizontalAlignment(SwingConstants.CENTER);
- manualSnField.setForeground(Color.BLACK);
- manualSnField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
- manualSnField.setBounds(workWidth / 5, 185, workWidth * 3 / 5, 40);
- manualSnField.addActionListener(e -> submitManualSn()); // 回车提交
- this.add(manualSnField);
- manualSubmitBtn = new JButton("确认");
- manualSubmitBtn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
- manualSubmitBtn.setBounds(workWidth * 4 / 5 + 10, 185, workWidth / 5 - 10, 40);
- manualSubmitBtn.addActionListener(e -> submitManualSn());
- this.add(manualSubmitBtn);
- setManualMode(MesClient.manualScanEnabled);
- // 回显行:每个推码地址一个框,显示从 PLC 读回的值
- int footerY = 230;
- if (pushPorts.length > 0) {
- JLabel echoLabel = new JLabel("回显");
- echoLabel.setHorizontalAlignment(SwingConstants.CENTER);
- echoLabel.setForeground(Color.BLACK);
- echoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
- echoLabel.setBounds(0, 230, workWidth / 5, 32);
- this.add(echoLabel);
- int w = (workWidth * 4 / 5 - 5 * (pushPorts.length - 1)) / pushPorts.length;
- for (int i = 0; i < pushPorts.length; i++) {
- PushPort p = pushPorts[i];
- p.echoField = new JTextField("");
- p.echoField.setHorizontalAlignment(SwingConstants.CENTER);
- p.echoField.setForeground(Color.BLACK);
- p.echoField.setFont(new Font("微软雅黑", Font.PLAIN, 12));
- p.echoField.setEditable(false);
- p.echoField.setToolTipText(p.gunNo + "号枪 → " + p.addr);
- p.echoField.setBounds(workWidth / 5 + i * (w + 5), 230, w, 32);
- this.add(p.echoField);
- }
- footerY = 268;
- }
- JLabel ipLabel = new JLabel("PLC " + plcIp + " | " + plcDb + " | 枪号 " + gunAddr);
- ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
- ipLabel.setForeground(Color.DARK_GRAY);
- ipLabel.setBounds(5, footerY, workWidth * 2 / 3 + 55, 26);
- this.add(ipLabel);
- signalMonitor = new PlcSignalMonitor02(s7PLC, plcDb, gunAddr, tag(), MesClient.mesClientFrame);
- signalBtn = signalMonitor.createSignalButton();
- signalBtn.setBounds(workWidth * 2 / 3 + 63, footerY, 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 setManualMode(boolean enabled) {
- manualLabel.setVisible(enabled);
- manualSnField.setVisible(enabled);
- manualSubmitBtn.setVisible(enabled);
- if (enabled) {
- return;
- }
- manualSnField.setText("");
- if (!pendingManualSn.isEmpty()) {
- pendingManualSn = "";
- setMenuStatus("已关闭手动输入,未使用的手动码已丢弃", 0);
- }
- }
- // 提交手动码:非空即挂起,等下一次请求扫码时当作扫码结果
- public void submitManualSn() {
- String sn = manualSnField.getText().trim();
- if (sn.isEmpty()) {
- setMenuStatus("手动码不能为空", -1);
- return;
- }
- pendingManualSn = sn;
- manualSnField.setText("");
- log.info("<{}>手动输入工件码:{},操作人:{}", tag(), sn, MesClient.user_menu.getText());
- setMenuStatus("手动码已就绪,等待请求扫码:" + sn, 0);
- }
- // scan.ips 里配了这把枪即有效,PLC 发几号就用几号
- private boolean isValidGun(int no) {
- return no >= 1 && no <= MesClient.scanClients.length;
- }
- // 定时任务 -----------------------------------------------------------------------------------------------------
- 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("<{}>轮询异常", tag(), e);
- }
- }
- }, 100, 1500);
- }
- // 推码定时器(独立于扫码主流程):留存期内写码,到期清空,并读回显
- public void startPushTimer() {
- if (pushTimer != null) {
- pushTimer.cancel();
- }
- pushTimer = new Timer();
- pushTimer.schedule(new TimerTask() {
- public void run() {
- if (MesClient.isLogin != 1) {
- return;
- }
- for (PushPort p : pushPorts) {
- pushCycle(p);
- }
- }
- }, 1000, 500);
- }
- private void pushCycle(PushPort p) {
- try {
- String target = System.currentTimeMillis() <= p.holdUntil ? p.holdSn : "";
- if (!target.equals(p.writtenSn)) {
- if (target.isEmpty()) {
- // writeString 不支持空串:从实际长度字节起连同字符一起写 0,最大长度字节不动
- s7PLC.writeByte(p.lenAddr, new byte[1 + PUSH_SN_LENGTH]);
- } else {
- s7PLC.writeString(p.addr, target);
- }
- p.writtenSn = target;
- log.info("<{}>推码{}-->成功,写入内容:{}", tag(), p.addr, target.isEmpty() ? "清空" : target);
- }
- p.echoField.setText(readPushSn(p));
- } catch (Exception e) {
- p.echoField.setText("通讯失败");
- log.error("<{}>推码{}-->失败:{}", tag(), p.addr, e.getMessage());
- }
- }
- // 按原始字节读 S7String,实际长度为 0 时返回空串(不用 readString)
- private String readPushSn(PushPort p) {
- byte[] bs = s7PLC.readByte(p.addr, 2 + PUSH_SN_LENGTH);
- int len = Math.min(bs[1] & 0xFF, PUSH_SN_LENGTH);
- return new String(bs, 2, len, StandardCharsets.US_ASCII).trim();
- }
- // 留存扫到的码,只改内存不碰 PLC;没有对应推码地址的枪号不推
- private void holdPushSn(int gunNo, String sn) {
- if (gunNo < 1 || gunNo > pushPorts.length) {
- return;
- }
- PushPort p = pushPorts[gunNo - 1];
- p.holdSn = sn; // 先写码再写截止时间,定时器不会读到新时间配旧码
- p.holdUntil = System.currentTimeMillis() + PUSH_HOLD_MS;
- }
- 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);
- Integer plcGunNo = readGunNo();
- showGunNo(plcGunNo);
- // 显示扫码槽当前码,被正面机台取走后变空
- productSnFeeding.setText(slot.getSn());
- if (!retScan) {
- scanDone = false;
- if (retFinish) {
- writeScanFinish(false);
- }
- return;
- }
- if (scanDone) {
- if (!retFinish) {
- writeScanFinish(true);
- }
- return;
- }
- doScan(plcGunNo);
- }
- // 刷新界面上的 PLC 枪号:0=未发(灰),1-3 绿色,其他值/读不到红色
- private void showGunNo(Integer no) {
- if (no == null) {
- gunNoLabel.setForeground(Color.RED);
- gunNoLabel.setText("PLC枪号:读取失败");
- gunNoLabel.setToolTipText(gunAddr + " 读取失败,不扫码");
- return;
- }
- if (no == 0) {
- gunNoLabel.setForeground(Color.GRAY);
- gunNoLabel.setText("PLC枪号:0(未发)");
- gunNoLabel.setToolTipText("本PLC未发枪号");
- return;
- }
- boolean valid = isValidGun(no);
- gunNoLabel.setForeground(valid ? new Color(0, 128, 0) : Color.RED);
- gunNoLabel.setText("PLC枪号:" + no + (valid ? "" : "(无效)"));
- gunNoLabel.setToolTipText(valid ? no + "号枪 " + MesClient.scanClients[no - 1].serverIP : "有效范围 1-" + MesClient.scanClients.length);
- }
- private void doScan(Integer plcGunNo) {
- // 有手动码时优先使用并跳过本次扫码枪,用完即清(同 auto3)
- String manualSn = pendingManualSn;
- if (!manualSn.isEmpty()) {
- pendingManualSn = "";
- log.info("<{}>手动码执行:{}", tag(), manualSn);
- gunField.setText("手动输入");
- // 手动码按 PLC 当前枪号推码,枪号 0 或读不到则不推
- saveScan(manualSn, "手动输入(" + plcName + ")", plcGunNo == null ? 0 : plcGunNo);
- return;
- }
- // 读不到枪号不扫码
- if (plcGunNo == null) {
- gunField.setText("未读到枪号");
- setMenuStatus("读取" + gunAddr + "失败,不扫码", -1);
- return;
- }
- int gunNo = plcGunNo;
- // 0 = PLC 还没发枪号,等下一轮
- if (gunNo == 0) {
- setMenuStatus("请求扫码,等待PLC发枪号", 0);
- return;
- }
- if (!isValidGun(gunNo)) {
- gunField.setText("枪号 " + gunNo + " 无效");
- setMenuStatus("扫码枪号无效:" + gunNo + "(" + gunAddr + " 应为 1-" + MesClient.scanClients.length + ")", -1);
- return;
- }
- TCPClient client = MesClient.scanClients[gunNo - 1];
- gunField.setText(gunNo + "号枪 " + client.serverIP + ":" + client.serverPort);
- setMenuStatus(gunNo + "号枪扫码中...", 0);
- String sn = MesClient.scanQrcode(gunNo);
- if (sn.isEmpty()) {
- setMenuStatus(gunNo + "号枪扫码失败(未读到码),重试中", -1);
- return;
- }
- saveScan(sn, gunNo + "号枪(" + plcName + ")", gunNo);
- }
- // 存入正面扫码码并回写扫码完成反馈,同时留存推码
- private void saveScan(String sn, String source, int gunNo) {
- slot.put(sn, source);
- productSnFeeding.setText(sn);
- writeScanFinish(true);
- holdPushSn(gunNo, sn);
- scanDone = true;
- setMenuStatus("扫码成功" + (source.startsWith("手动") ? "(手动码)" : ""), 0);
- }
- public void resetScan() {
- slot.clear("人工清空(" + plcName + ")");
- productSnFeeding.setText("");
- scanDone = false;
- writeScanFinish(false);
- setMenuStatus("扫码结果已清空,等待请求扫码", 0);
- }
- // 与设备交互 -----------------------------------------------------------------------------------------
- // 读扫码枪号,读失败返回 null;每轮都读,只在值变化时记日志
- private Integer readGunNo() {
- Integer no = null;
- String err = "";
- try {
- no = (int) s7PLC.readInt16(gunAddr);
- } catch (Exception e) {
- err = e.getMessage();
- }
- String state = no == null ? "ERR" : String.valueOf(no);
- if (!state.equals(lastGunState)) {
- lastGunState = state;
- if (no == null) {
- log.warn("<{}>读取{}-->失败,不扫码:{}", tag(), gunAddr, err);
- } else {
- log.info("<{}>读取{}-->枪号变为:{}", tag(), gunAddr, no);
- }
- }
- return no;
- }
- 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("<{}>读取{}-->失败:{}", tag(), addr, e.getMessage());
- return false;
- }
- }
- public boolean writeBoolean(String addr, boolean value) {
- try {
- s7PLC.writeBoolean(addr, value);
- log.info("<{}>写入{}-->成功,写入内容:{}", tag(), addr, value);
- return true;
- } catch (Exception e) {
- log.error("<{}>写入{}-->失败,写入内容:{},{}", tag(), addr, value, e.getMessage());
- return false;
- }
- }
- }
|