|
@@ -12,6 +12,7 @@ import javax.swing.*;
|
|
|
import java.awt.*;
|
|
import java.awt.*;
|
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseAdapter;
|
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseEvent;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.Timer;
|
|
import java.util.Timer;
|
|
|
import java.util.TimerTask;
|
|
import java.util.TimerTask;
|
|
|
|
|
|
|
@@ -21,6 +22,7 @@ import java.util.TimerTask;
|
|
|
* 枪号 0=未发;正常同一时间 92、93 只有一个非 0,PLC 收到扫码完成反馈后清零
|
|
* 枪号 0=未发;正常同一时间 92、93 只有一个非 0,PLC 收到扫码完成反馈后清零
|
|
|
* 请求扫码时 PLC 给几号就触发几号枪,读到码(不校验长度)存入本 PLC 的正面扫码码
|
|
* 请求扫码时 PLC 给几号就触发几号枪,读到码(不校验长度)存入本 PLC 的正面扫码码
|
|
|
* 92 扫的码给 OP130A-E 取,93 扫的码给 OP130F(93 DB7004)取
|
|
* 92 扫的码给 OP130A-E 取,93 扫的码给 OP130F(93 DB7004)取
|
|
|
|
|
+ * 推码给 PLC(pushAddrs 按枪号对应 S7String 地址):扫到码后留存 15s 写入,到期清空,独立定时器,失败只记日志
|
|
|
*/
|
|
*/
|
|
|
public class WorkJPanelScan extends JPanel {
|
|
public class WorkJPanelScan extends JPanel {
|
|
|
|
|
|
|
@@ -65,16 +67,45 @@ public class WorkJPanelScan extends JPanel {
|
|
|
|
|
|
|
|
public Timer oprnoTimer;
|
|
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 plcIp PLC 地址
|
|
|
|
|
+ * @param slot 扫到的码存入的码槽(92 给 92 正面机,93 给 93 DB7004 正面机)
|
|
|
|
|
+ * @param pushAddrs 推码地址,第 i 个对应 i+1 号枪;不传则不推码
|
|
|
*/
|
|
*/
|
|
|
- public WorkJPanelScan(String plcIp, SnSlot slot) {
|
|
|
|
|
|
|
+ public WorkJPanelScan(String plcIp, SnSlot slot, String... pushAddrs) {
|
|
|
this.setLayout(null);
|
|
this.setLayout(null);
|
|
|
this.setBackground(Color.WHITE);
|
|
this.setBackground(Color.WHITE);
|
|
|
this.plcIp = plcIp;
|
|
this.plcIp = plcIp;
|
|
|
this.plcName = plcIp.substring(plcIp.lastIndexOf('.') + 1);
|
|
this.plcName = plcIp.substring(plcIp.lastIndexOf('.') + 1);
|
|
|
this.slot = slot;
|
|
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 = new S7PLC(EPlcType.S1500, plcIp);
|
|
|
s7PLC.setConnectTimeout(1000);
|
|
s7PLC.setConnectTimeout(1000);
|
|
@@ -84,6 +115,9 @@ public class WorkJPanelScan extends JPanel {
|
|
|
productSnFeeding.setText(slot.getSn());
|
|
productSnFeeding.setText(slot.getSn());
|
|
|
setMenuStatus("等待请求扫码", 0);
|
|
setMenuStatus("等待请求扫码", 0);
|
|
|
startOprnoTimer();
|
|
startOprnoTimer();
|
|
|
|
|
+ if (pushPorts.length > 0) {
|
|
|
|
|
+ startPushTimer();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private String tag() {
|
|
private String tag() {
|
|
@@ -188,15 +222,40 @@ public class WorkJPanelScan extends JPanel {
|
|
|
|
|
|
|
|
setManualMode(MesClient.manualScanEnabled);
|
|
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);
|
|
JLabel ipLabel = new JLabel("PLC " + plcIp + " | " + plcDb + " | 枪号 " + gunAddr);
|
|
|
ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
|
ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
|
|
ipLabel.setForeground(Color.DARK_GRAY);
|
|
ipLabel.setForeground(Color.DARK_GRAY);
|
|
|
- ipLabel.setBounds(5, 230, workWidth * 2 / 3 + 55, 26);
|
|
|
|
|
|
|
+ ipLabel.setBounds(5, footerY, workWidth * 2 / 3 + 55, 26);
|
|
|
this.add(ipLabel);
|
|
this.add(ipLabel);
|
|
|
|
|
|
|
|
signalMonitor = new PlcSignalMonitor02(s7PLC, plcDb, gunAddr, tag(), MesClient.mesClientFrame);
|
|
signalMonitor = new PlcSignalMonitor02(s7PLC, plcDb, gunAddr, tag(), MesClient.mesClientFrame);
|
|
|
signalBtn = signalMonitor.createSignalButton();
|
|
signalBtn = signalMonitor.createSignalButton();
|
|
|
- signalBtn.setBounds(workWidth * 2 / 3 + 63, 230, 90, 26);
|
|
|
|
|
|
|
+ signalBtn.setBounds(workWidth * 2 / 3 + 63, footerY, 90, 26);
|
|
|
this.add(signalBtn);
|
|
this.add(signalBtn);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -299,6 +358,61 @@ public class WorkJPanelScan extends JPanel {
|
|
|
}, 100, 1500);
|
|
}, 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() {
|
|
private void cycle() {
|
|
|
boolean retScan = readBoolean(plcDb + ".0.0");
|
|
boolean retScan = readBoolean(plcDb + ".0.0");
|
|
|
boolean retFinish = readBoolean(plcDb + ".0.1");
|
|
boolean retFinish = readBoolean(plcDb + ".0.1");
|
|
@@ -354,7 +468,8 @@ public class WorkJPanelScan extends JPanel {
|
|
|
pendingManualSn = "";
|
|
pendingManualSn = "";
|
|
|
log.info("<{}>手动码执行:{}", tag(), manualSn);
|
|
log.info("<{}>手动码执行:{}", tag(), manualSn);
|
|
|
gunField.setText("手动输入");
|
|
gunField.setText("手动输入");
|
|
|
- saveScan(manualSn, "手动输入(" + plcName + ")");
|
|
|
|
|
|
|
+ // 手动码按 PLC 当前枪号推码,枪号 0 或读不到则不推
|
|
|
|
|
+ saveScan(manualSn, "手动输入(" + plcName + ")", plcGunNo == null ? 0 : plcGunNo);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
// 读不到枪号不扫码
|
|
// 读不到枪号不扫码
|
|
@@ -385,14 +500,15 @@ public class WorkJPanelScan extends JPanel {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- saveScan(sn, gunNo + "号枪(" + plcName + ")");
|
|
|
|
|
|
|
+ saveScan(sn, gunNo + "号枪(" + plcName + ")", gunNo);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 存入正面扫码码并回写扫码完成反馈
|
|
|
|
|
- private void saveScan(String sn, String source) {
|
|
|
|
|
|
|
+ // 存入正面扫码码并回写扫码完成反馈,同时留存推码
|
|
|
|
|
+ private void saveScan(String sn, String source, int gunNo) {
|
|
|
slot.put(sn, source);
|
|
slot.put(sn, source);
|
|
|
productSnFeeding.setText(sn);
|
|
productSnFeeding.setText(sn);
|
|
|
writeScanFinish(true);
|
|
writeScanFinish(true);
|
|
|
|
|
+ holdPushSn(gunNo, sn);
|
|
|
scanDone = true;
|
|
scanDone = true;
|
|
|
setMenuStatus("扫码成功" + (source.startsWith("手动") ? "(手动码)" : ""), 0);
|
|
setMenuStatus("扫码成功" + (source.startsWith("手动") ? "(手动码)" : ""), 0);
|
|
|
}
|
|
}
|