|
|
@@ -0,0 +1,354 @@
|
|
|
+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;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 框架气密 DB200 调试:读取 mes 数据块全部点位。
|
|
|
+ */
|
|
|
+public class QmPlcMonitorPanel extends JPanel {
|
|
|
+
|
|
|
+ private final JLabel statusLabel = new JLabel("未连接");
|
|
|
+ private final JTextField hostField = new JTextField(16);
|
|
|
+ private final JLabel dbLabel = new JLabel("-");
|
|
|
+
|
|
|
+ private final PointRow inflationTime = new PointRow("框架充气时间", "Real");
|
|
|
+ private final PointRow holdTime = new PointRow("框架保压时间", "Real");
|
|
|
+ private final PointRow testTime = new PointRow("框架测试时间", "Real");
|
|
|
+ private final PointRow inflationPressure = new PointRow("框架充气压力", "Real");
|
|
|
+ private final PointRow stablePressure = new PointRow("框架稳定压力", "Real");
|
|
|
+ private final PointRow leak = new PointRow("框架泄漏量", "Real");
|
|
|
+ private final PointRow judgeResult = new PointRow("框架判定结果", "Bool");
|
|
|
+ private final PointRow frameCode = new PointRow("框架码", "String[30]");
|
|
|
+ private final PointRow complete = new PointRow("检测完成", "Bool");
|
|
|
+ private final PointRow allowEntry = new PointRow("允许进站", "Bool");
|
|
|
+ private final PointRow scanFeedback = new PointRow("扫码反馈", "Bool");
|
|
|
+
|
|
|
+ private QmPlcConfig config;
|
|
|
+ private QmS7Client s7Client;
|
|
|
+ private Timer timer;
|
|
|
+ private final AtomicBoolean refreshing = new AtomicBoolean(false);
|
|
|
+ private volatile boolean active;
|
|
|
+
|
|
|
+ public QmPlcMonitorPanel() {
|
|
|
+ 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();
|
|
|
+ center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS));
|
|
|
+ center.setBorder(new TitledBorder("mes [DB200] 点位"));
|
|
|
+ center.add(headerRow());
|
|
|
+ center.add(inflationTime);
|
|
|
+ center.add(holdTime);
|
|
|
+ center.add(testTime);
|
|
|
+ center.add(inflationPressure);
|
|
|
+ center.add(stablePressure);
|
|
|
+ center.add(leak);
|
|
|
+ center.add(judgeResult);
|
|
|
+ center.add(frameCode);
|
|
|
+ center.add(complete);
|
|
|
+ center.add(allowEntry);
|
|
|
+ center.add(scanFeedback);
|
|
|
+
|
|
|
+ JScrollPane scroll = new JScrollPane(center);
|
|
|
+ scroll.setBorder(null);
|
|
|
+ add(scroll, BorderLayout.CENTER);
|
|
|
+
|
|
|
+ loadUiFromConfig();
|
|
|
+ }
|
|
|
+
|
|
|
+ private JPanel headerRow() {
|
|
|
+ JPanel p = new JPanel(new GridLayout(1, 4, 8, 0));
|
|
|
+ p.setMaximumSize(new Dimension(Integer.MAX_VALUE, 28));
|
|
|
+ p.add(labelBold("名称"));
|
|
|
+ p.add(labelBold("数据类型"));
|
|
|
+ p.add(labelBold("偏移量"));
|
|
|
+ p.add(labelBold("当前值"));
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static JLabel labelBold(String text) {
|
|
|
+ JLabel l = new JLabel(text);
|
|
|
+ l.setFont(new Font("微软雅黑", Font.BOLD, 15));
|
|
|
+ return l;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void loadUiFromConfig() {
|
|
|
+ config = QmPlcConfig.load();
|
|
|
+ hostField.setText(config.host);
|
|
|
+ dbLabel.setText("DB" + config.db);
|
|
|
+ inflationTime.setAddr(String.format("DB%d.DBD%d", config.db, config.inflationTimeOffset));
|
|
|
+ holdTime.setAddr(String.format("DB%d.DBD%d", config.db, config.holdTimeOffset));
|
|
|
+ testTime.setAddr(String.format("DB%d.DBD%d", config.db, config.testTimeOffset));
|
|
|
+ inflationPressure.setAddr(String.format("DB%d.DBD%d", config.db, config.inflationPressureOffset));
|
|
|
+ stablePressure.setAddr(String.format("DB%d.DBD%d", config.db, config.stablePressureOffset));
|
|
|
+ leak.setAddr(String.format("DB%d.DBD%d", config.db, config.leakOffset));
|
|
|
+ judgeResult.setAddr(String.format("DB%d.DBX%d.%d", config.db, config.judgeResultByte, config.judgeResultBit));
|
|
|
+ frameCode.setAddr(String.format("DB%d.DBB%d String[%d]", config.db, config.frameCodeOffset, config.frameCodeLen));
|
|
|
+ complete.setAddr(String.format("DB%d.DBX%d.%d", config.db, config.completeByte, config.completeBit));
|
|
|
+ allowEntry.setAddr(String.format("DB%d.DBX%d.%d", config.db, config.allowEntryByte, config.allowEntryBit));
|
|
|
+ scanFeedback.setAddr(String.format("DB%d.DBX%d.%d", config.db, config.scanFeedbackByte, config.scanFeedbackBit));
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 = QmPlcConfig.saveHost(ip);
|
|
|
+ if (config != null) {
|
|
|
+ config.host = ip;
|
|
|
+ }
|
|
|
+ if (s7Client != null) {
|
|
|
+ s7Client.updateHost(ip);
|
|
|
+ }
|
|
|
+ QmPlcPoller.reloadHost(ip);
|
|
|
+ if (ok) {
|
|
|
+ statusLabel.setForeground(new Color(0, 128, 0));
|
|
|
+ statusLabel.setText("IP已保存: " + ip);
|
|
|
+ } else {
|
|
|
+ statusLabel.setForeground(Color.ORANGE.darker());
|
|
|
+ statusLabel.setText("IP已用于当前连接,但写配置文件失败");
|
|
|
+ }
|
|
|
+ refreshOnce();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void startMonitor() {
|
|
|
+ active = true;
|
|
|
+ loadUiFromConfig();
|
|
|
+ if (s7Client == null) {
|
|
|
+ s7Client = new QmS7Client(config);
|
|
|
+ } else {
|
|
|
+ s7Client.updateHost(hostField.getText().trim());
|
|
|
+ }
|
|
|
+ if (timer != null) {
|
|
|
+ timer.cancel();
|
|
|
+ }
|
|
|
+ timer = new Timer("QmPlcMonitor", 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 = QmPlcConfig.load();
|
|
|
+ }
|
|
|
+ String ip = hostField.getText() == null ? "" : hostField.getText().trim();
|
|
|
+ if (!ip.isEmpty()) {
|
|
|
+ config.host = ip;
|
|
|
+ }
|
|
|
+ if (s7Client == null) {
|
|
|
+ s7Client = new QmS7Client(config);
|
|
|
+ } else {
|
|
|
+ s7Client.updateHost(config.host);
|
|
|
+ }
|
|
|
+
|
|
|
+ final Snapshot s = new Snapshot();
|
|
|
+ s.inflationTime = readFloat(config.inflationTimeOffset, s);
|
|
|
+ s.holdTime = readFloat(config.holdTimeOffset, s);
|
|
|
+ s.testTime = readFloat(config.testTimeOffset, s);
|
|
|
+ s.inflationPressure = readFloat(config.inflationPressureOffset, s);
|
|
|
+ s.stablePressure = readFloat(config.stablePressureOffset, s);
|
|
|
+ s.leak = readFloat(config.leakOffset, s);
|
|
|
+ s.judgeResult = readBit(config.judgeResultByte, config.judgeResultBit, s);
|
|
|
+ s.frameCode = readFrame(s);
|
|
|
+ s.complete = readBit(config.completeByte, config.completeBit, s);
|
|
|
+ s.allowEntry = readBit(config.allowEntryByte, config.allowEntryBit, s);
|
|
|
+ s.scanFeedback = readBit(config.scanFeedbackByte, config.scanFeedbackBit, s);
|
|
|
+
|
|
|
+ final boolean allOk = s.errorCount == 0;
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ inflationTime.setValue(s.inflationTime);
|
|
|
+ holdTime.setValue(s.holdTime);
|
|
|
+ testTime.setValue(s.testTime);
|
|
|
+ inflationPressure.setValue(s.inflationPressure);
|
|
|
+ stablePressure.setValue(s.stablePressure);
|
|
|
+ leak.setValue(s.leak);
|
|
|
+ judgeResult.setValue(s.judgeResult);
|
|
|
+ frameCode.setValue(s.frameCode);
|
|
|
+ complete.setValue(s.complete);
|
|
|
+ allowEntry.setValue(s.allowEntry);
|
|
|
+ scanFeedback.setValue(s.scanFeedback);
|
|
|
+ if (allOk) {
|
|
|
+ statusLabel.setForeground(new Color(0, 128, 0));
|
|
|
+ statusLabel.setText("已连接 · 实时刷新中");
|
|
|
+ } else {
|
|
|
+ statusLabel.setForeground(Color.RED);
|
|
|
+ statusLabel.setText("部分读取失败: " + shorten(s.firstError));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (s.errorCount >= 11 && 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 String readFloat(int offset, Snapshot s) {
|
|
|
+ try {
|
|
|
+ return String.format("%.3f", s7Client.readFloat(offset));
|
|
|
+ } catch (Exception e) {
|
|
|
+ s.errorCount++;
|
|
|
+ if (s.firstError == null) {
|
|
|
+ s.firstError = e.getMessage();
|
|
|
+ }
|
|
|
+ return "ERR";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String readBit(int byteOffset, int bit, Snapshot s) {
|
|
|
+ try {
|
|
|
+ return s7Client.readBit(byteOffset, bit) ? "true" : "false";
|
|
|
+ } catch (Exception e) {
|
|
|
+ s.errorCount++;
|
|
|
+ if (s.firstError == null) {
|
|
|
+ s.firstError = e.getMessage();
|
|
|
+ }
|
|
|
+ return "ERR";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String readFrame(Snapshot s) {
|
|
|
+ try {
|
|
|
+ String v = s7Client.readFrameCode();
|
|
|
+ return (v == null || v.isEmpty()) ? "(空)" : v;
|
|
|
+ } catch (Exception e) {
|
|
|
+ s.errorCount++;
|
|
|
+ if (s.firstError == null) {
|
|
|
+ s.firstError = e.getMessage();
|
|
|
+ }
|
|
|
+ return "ERR";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 static class Snapshot {
|
|
|
+ String inflationTime;
|
|
|
+ String holdTime;
|
|
|
+ String testTime;
|
|
|
+ String inflationPressure;
|
|
|
+ String stablePressure;
|
|
|
+ String leak;
|
|
|
+ String judgeResult;
|
|
|
+ String frameCode;
|
|
|
+ String complete;
|
|
|
+ String allowEntry;
|
|
|
+ String scanFeedback;
|
|
|
+ int errorCount;
|
|
|
+ String firstError;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class PointRow extends JPanel {
|
|
|
+ private final JLabel addrLabel = new JLabel("-");
|
|
|
+ private final JLabel valueLabel = new JLabel("-");
|
|
|
+
|
|
|
+ PointRow(String name, String type) {
|
|
|
+ setLayout(new GridLayout(1, 4, 8, 0));
|
|
|
+ setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
|
|
|
+ JLabel nameLabel = new JLabel(name);
|
|
|
+ nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
|
|
+ JLabel typeLabel = new JLabel(type);
|
|
|
+ typeLabel.setFont(new Font("Consolas", Font.PLAIN, 14));
|
|
|
+ addrLabel.setFont(new Font("Consolas", Font.PLAIN, 14));
|
|
|
+ valueLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
|
|
+ add(nameLabel);
|
|
|
+ add(typeLabel);
|
|
|
+ add(addrLabel);
|
|
|
+ add(valueLabel);
|
|
|
+ }
|
|
|
+
|
|
|
+ void setAddr(String addr) {
|
|
|
+ addrLabel.setText(addr);
|
|
|
+ }
|
|
|
+
|
|
|
+ void setValue(String value) {
|
|
|
+ valueLabel.setText(value == null ? "-" : value);
|
|
|
+ if ("ERR".equals(value)) {
|
|
|
+ valueLabel.setForeground(Color.RED);
|
|
|
+ } else if ("true".equals(value)) {
|
|
|
+ valueLabel.setForeground(new Color(0, 128, 0));
|
|
|
+ } else if ("false".equals(value)) {
|
|
|
+ valueLabel.setForeground(Color.GRAY);
|
|
|
+ } else {
|
|
|
+ valueLabel.setForeground(Color.BLACK);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|