|
|
@@ -0,0 +1,343 @@
|
|
|
+package com.mes.plc;
|
|
|
+
|
|
|
+import javax.swing.*;
|
|
|
+import javax.swing.border.EmptyBorder;
|
|
|
+import javax.swing.border.TitledBorder;
|
|
|
+import java.awt.*;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.TimerTask;
|
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 涂胶 PLC 监控:可改 IP;同一台 PLC 同时显示 a1 / b1 两把胶枪数据。
|
|
|
+ */
|
|
|
+public class TjPlcMonitorPanel extends JPanel {
|
|
|
+
|
|
|
+ private final JLabel statusLabel = new JLabel("未连接");
|
|
|
+ private final JTextField hostField = new JTextField(16);
|
|
|
+ private final JLabel dbLabel = new JLabel("-");
|
|
|
+
|
|
|
+ private final GunView a1View = new GunView("胶枪 A1");
|
|
|
+ private final GunView b1View = new GunView("胶枪 B1");
|
|
|
+
|
|
|
+ private TjPlcConfig config;
|
|
|
+ private TjS7Client s7Client;
|
|
|
+ private Timer timer;
|
|
|
+ private final AtomicBoolean refreshing = new AtomicBoolean(false);
|
|
|
+ private volatile boolean active;
|
|
|
+
|
|
|
+ public TjPlcMonitorPanel() {
|
|
|
+ setLayout(new BorderLayout(10, 10));
|
|
|
+ setBorder(new EmptyBorder(12, 16, 12, 16));
|
|
|
+
|
|
|
+ JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 8));
|
|
|
+ JLabel ipTitle = new JLabel("PLC IP:");
|
|
|
+ ipTitle.setFont(new Font("微软雅黑", Font.PLAIN, 18));
|
|
|
+ hostField.setFont(new Font("Consolas", Font.PLAIN, 18));
|
|
|
+ hostField.setPreferredSize(new Dimension(180, 32));
|
|
|
+
|
|
|
+ JButton saveIpBtn = new JButton("保存IP");
|
|
|
+ saveIpBtn.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ saveIpBtn.addActionListener(e -> saveIp());
|
|
|
+
|
|
|
+ JButton refreshBtn = new JButton("立即刷新");
|
|
|
+ refreshBtn.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ refreshBtn.addActionListener(e -> refreshOnce());
|
|
|
+
|
|
|
+ statusLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
|
|
+ dbLabel.setFont(new Font("Consolas", Font.PLAIN, 16));
|
|
|
+
|
|
|
+ top.add(ipTitle);
|
|
|
+ top.add(hostField);
|
|
|
+ top.add(saveIpBtn);
|
|
|
+ top.add(refreshBtn);
|
|
|
+ top.add(dbLabel);
|
|
|
+ top.add(statusLabel);
|
|
|
+ add(top, BorderLayout.NORTH);
|
|
|
+
|
|
|
+ JPanel center = new JPanel(new GridLayout(1, 2, 12, 0));
|
|
|
+ center.add(a1View);
|
|
|
+ center.add(b1View);
|
|
|
+ add(center, BorderLayout.CENTER);
|
|
|
+
|
|
|
+ loadUiFromConfig();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void loadUiFromConfig() {
|
|
|
+ config = TjPlcConfig.load();
|
|
|
+ hostField.setText(config.host);
|
|
|
+ dbLabel.setText("DB" + config.db + " 工作枪=" + config.machine);
|
|
|
+ a1View.bindAddr(config.a1, config.db);
|
|
|
+ b1View.bindAddr(config.b1, config.db);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveIp() {
|
|
|
+ String ip = hostField.getText() == null ? "" : hostField.getText().trim();
|
|
|
+ if (ip.isEmpty()) {
|
|
|
+ JOptionPane.showMessageDialog(this, "请输入 PLC IP", "提示", JOptionPane.WARNING_MESSAGE);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ boolean ok = TjPlcConfig.saveHost(ip);
|
|
|
+ if (config != null) {
|
|
|
+ config.host = ip;
|
|
|
+ }
|
|
|
+ if (s7Client != null) {
|
|
|
+ s7Client.updateHost(ip);
|
|
|
+ }
|
|
|
+ TjPlcPoller.reloadHost(ip);
|
|
|
+ if (ok) {
|
|
|
+ statusLabel.setForeground(new Color(0, 128, 0));
|
|
|
+ statusLabel.setText("IP已保存: " + ip);
|
|
|
+ refreshOnce();
|
|
|
+ } else {
|
|
|
+ statusLabel.setForeground(Color.ORANGE.darker());
|
|
|
+ statusLabel.setText("IP已用于当前连接,但写配置文件失败");
|
|
|
+ refreshOnce();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void startMonitor() {
|
|
|
+ active = true;
|
|
|
+ loadUiFromConfig();
|
|
|
+ if (s7Client == null) {
|
|
|
+ s7Client = new TjS7Client(config);
|
|
|
+ } else {
|
|
|
+ s7Client.updateHost(hostField.getText().trim());
|
|
|
+ }
|
|
|
+ if (timer != null) {
|
|
|
+ timer.cancel();
|
|
|
+ }
|
|
|
+ timer = new Timer("TjPlcMonitor", true);
|
|
|
+ timer.scheduleAtFixedRate(new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ if (active) {
|
|
|
+ refreshOnce();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 200, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void stopMonitor() {
|
|
|
+ active = false;
|
|
|
+ if (timer != null) {
|
|
|
+ timer.cancel();
|
|
|
+ timer = null;
|
|
|
+ }
|
|
|
+ if (s7Client != null) {
|
|
|
+ s7Client.close();
|
|
|
+ s7Client = null;
|
|
|
+ }
|
|
|
+ SwingUtilities.invokeLater(() -> statusLabel.setText("已停止刷新"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void refreshOnce() {
|
|
|
+ if (!refreshing.compareAndSet(false, true)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ if (config == null) {
|
|
|
+ config = TjPlcConfig.load();
|
|
|
+ }
|
|
|
+ String ip = hostField.getText() == null ? "" : hostField.getText().trim();
|
|
|
+ if (!ip.isEmpty()) {
|
|
|
+ config.host = ip;
|
|
|
+ }
|
|
|
+ if (s7Client == null) {
|
|
|
+ s7Client = new TjS7Client(config);
|
|
|
+ } else {
|
|
|
+ s7Client.updateHost(config.host);
|
|
|
+ }
|
|
|
+
|
|
|
+ Exception a1Err = null;
|
|
|
+ Exception b1Err = null;
|
|
|
+ GunSnapshot a1 = null;
|
|
|
+ GunSnapshot b1 = null;
|
|
|
+ try {
|
|
|
+ a1 = readGun(config.a1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ a1Err = e;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ b1 = readGun(config.b1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ b1Err = e;
|
|
|
+ }
|
|
|
+
|
|
|
+ final GunSnapshot a1Final = a1;
|
|
|
+ final GunSnapshot b1Final = b1;
|
|
|
+ final String a1Msg = a1Err == null ? null : a1Err.getMessage();
|
|
|
+ final String b1Msg = b1Err == null ? null : b1Err.getMessage();
|
|
|
+ final boolean a1Ok = a1Err == null;
|
|
|
+ final boolean b1Ok = b1Err == null;
|
|
|
+
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ if (a1Final != null) {
|
|
|
+ a1View.apply(a1Final);
|
|
|
+ }
|
|
|
+ if (b1Final != null) {
|
|
|
+ b1View.apply(b1Final);
|
|
|
+ }
|
|
|
+ if (a1Ok && b1Ok) {
|
|
|
+ statusLabel.setForeground(new Color(0, 128, 0));
|
|
|
+ statusLabel.setText("已连接 · 实时刷新中");
|
|
|
+ } else if (!a1Ok && !b1Ok) {
|
|
|
+ statusLabel.setForeground(Color.RED);
|
|
|
+ statusLabel.setText("A1/B1均失败(常见:DB不存在/优化块/越界): " + shorten(a1Msg));
|
|
|
+ } else if (!a1Ok) {
|
|
|
+ statusLabel.setForeground(Color.RED);
|
|
|
+ statusLabel.setText("A1失败: " + shorten(a1Msg));
|
|
|
+ } else {
|
|
|
+ statusLabel.setForeground(Color.RED);
|
|
|
+ statusLabel.setText("B1失败: " + shorten(b1Msg));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (a1Err != null && b1Err != null) {
|
|
|
+ if (s7Client != null) {
|
|
|
+ s7Client.close();
|
|
|
+ s7Client = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ if (s7Client != null) {
|
|
|
+ s7Client.close();
|
|
|
+ s7Client = null;
|
|
|
+ }
|
|
|
+ final String msg = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ statusLabel.setForeground(Color.RED);
|
|
|
+ statusLabel.setText("读取失败: " + shorten(msg));
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ refreshing.set(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String shorten(String msg) {
|
|
|
+ if (msg == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ String m = msg.replace('\n', ' ').trim();
|
|
|
+ if (m.length() > 120) {
|
|
|
+ return m.substring(0, 120) + "...";
|
|
|
+ }
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+
|
|
|
+ private GunSnapshot readGun(TjPlcConfig.GunAddr gun) throws Exception {
|
|
|
+ GunSnapshot s = new GunSnapshot();
|
|
|
+ s.frameCode = s7Client.readFrameCode(gun);
|
|
|
+ s.scanFeedback = s7Client.readBit(gun.scanFeedbackByte, gun.scanFeedbackBit);
|
|
|
+ s.allowEntry = s7Client.readBit(gun.allowEntryByte, gun.allowEntryBit);
|
|
|
+ s.pathDone = s7Client.readBit(gun.pathDoneByte, gun.pathDoneBit);
|
|
|
+ s.allDone = s7Client.readBit(gun.allDoneByte, gun.allDoneBit);
|
|
|
+ s.seq = s7Client.readUInt16(gun.seqOffset);
|
|
|
+ s.glueSpeed = s7Client.readFloat(gun.glueSpeedOffset);
|
|
|
+ s.glueLength = s7Client.readFloat(gun.glueLengthOffset);
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class GunSnapshot {
|
|
|
+ String frameCode;
|
|
|
+ boolean scanFeedback;
|
|
|
+ boolean allowEntry;
|
|
|
+ boolean pathDone;
|
|
|
+ boolean allDone;
|
|
|
+ int seq;
|
|
|
+ float glueSpeed;
|
|
|
+ float glueLength;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class GunView extends JPanel {
|
|
|
+ private final JLabel frameCode = valueLabel();
|
|
|
+ private final JLabel scanFeedback = valueLabel();
|
|
|
+ private final JLabel allowEntry = valueLabel();
|
|
|
+ private final JLabel pathDone = valueLabel();
|
|
|
+ private final JLabel allDone = valueLabel();
|
|
|
+ private final JLabel seq = valueLabel();
|
|
|
+ private final JLabel glueSpeed = valueLabel();
|
|
|
+ private final JLabel glueLength = valueLabel();
|
|
|
+
|
|
|
+ private final JLabel frameCodeAddr = addrLabel();
|
|
|
+ private final JLabel scanFeedbackAddr = addrLabel();
|
|
|
+ private final JLabel allowEntryAddr = addrLabel();
|
|
|
+ private final JLabel pathDoneAddr = addrLabel();
|
|
|
+ private final JLabel allDoneAddr = addrLabel();
|
|
|
+ private final JLabel seqAddr = addrLabel();
|
|
|
+ private final JLabel glueSpeedAddr = addrLabel();
|
|
|
+ private final JLabel glueLengthAddr = addrLabel();
|
|
|
+
|
|
|
+ GunView(String title) {
|
|
|
+ setLayout(new GridBagLayout());
|
|
|
+ setBorder(BorderFactory.createTitledBorder(
|
|
|
+ BorderFactory.createEtchedBorder(), title,
|
|
|
+ TitledBorder.LEFT, TitledBorder.TOP,
|
|
|
+ new Font("微软雅黑", Font.BOLD, 18)));
|
|
|
+ GridBagConstraints c = new GridBagConstraints();
|
|
|
+ c.insets = new Insets(6, 6, 6, 6);
|
|
|
+ c.fill = GridBagConstraints.HORIZONTAL;
|
|
|
+ c.anchor = GridBagConstraints.WEST;
|
|
|
+ int row = 0;
|
|
|
+ row = addRow(c, row, "框架码", frameCode, frameCodeAddr);
|
|
|
+ row = addRow(c, row, "扫码反馈", scanFeedback, scanFeedbackAddr);
|
|
|
+ row = addRow(c, row, "允许进站", allowEntry, allowEntryAddr);
|
|
|
+ row = addRow(c, row, "单道结束", pathDone, pathDoneAddr);
|
|
|
+ row = addRow(c, row, "全部结束", allDone, allDoneAddr);
|
|
|
+ row = addRow(c, row, "涂胶序号", seq, seqAddr);
|
|
|
+ row = addRow(c, row, "涂胶速度", glueSpeed, glueSpeedAddr);
|
|
|
+ addRow(c, row, "涂胶长度", glueLength, glueLengthAddr);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int addRow(GridBagConstraints c, int row, String title, JLabel value, JLabel addr) {
|
|
|
+ JLabel name = new JLabel(title);
|
|
|
+ name.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ c.gridx = 0;
|
|
|
+ c.gridy = row;
|
|
|
+ c.weightx = 0;
|
|
|
+ add(name, c);
|
|
|
+ c.gridx = 1;
|
|
|
+ c.weightx = 1;
|
|
|
+ add(value, c);
|
|
|
+ c.gridx = 2;
|
|
|
+ c.weightx = 0.8;
|
|
|
+ add(addr, c);
|
|
|
+ return row + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ void bindAddr(TjPlcConfig.GunAddr gun, int db) {
|
|
|
+ frameCodeAddr.setText(gun.frameCodeAddr(db));
|
|
|
+ scanFeedbackAddr.setText(gun.scanFeedbackAddr(db));
|
|
|
+ allowEntryAddr.setText(gun.allowEntryAddr(db));
|
|
|
+ pathDoneAddr.setText(gun.pathDoneAddr(db));
|
|
|
+ allDoneAddr.setText(gun.allDoneAddr(db));
|
|
|
+ seqAddr.setText(gun.seqAddr(db));
|
|
|
+ glueSpeedAddr.setText(gun.glueSpeedAddr(db));
|
|
|
+ glueLengthAddr.setText(gun.glueLengthAddr(db));
|
|
|
+ }
|
|
|
+
|
|
|
+ void apply(GunSnapshot s) {
|
|
|
+ frameCode.setText(s.frameCode == null || s.frameCode.isEmpty() ? "(空)" : s.frameCode);
|
|
|
+ scanFeedback.setText(s.scanFeedback ? "1" : "0");
|
|
|
+ allowEntry.setText(s.allowEntry ? "1" : "0");
|
|
|
+ pathDone.setText(s.pathDone ? "1" : "0");
|
|
|
+ allDone.setText(s.allDone ? "1" : "0");
|
|
|
+ seq.setText(String.valueOf(s.seq));
|
|
|
+ glueSpeed.setText(String.format("%.3f", s.glueSpeed));
|
|
|
+ glueLength.setText(String.format("%.3f", s.glueLength));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static JLabel valueLabel() {
|
|
|
+ JLabel l = new JLabel("-");
|
|
|
+ l.setFont(new Font("微软雅黑", Font.BOLD, 18));
|
|
|
+ return l;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static JLabel addrLabel() {
|
|
|
+ JLabel l = new JLabel("-");
|
|
|
+ l.setFont(new Font("Consolas", Font.PLAIN, 13));
|
|
|
+ l.setForeground(Color.DARK_GRAY);
|
|
|
+ return l;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|