|
|
@@ -0,0 +1,425 @@
|
|
|
+package com.jeesite.test;
|
|
|
+
|
|
|
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
|
|
|
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
|
|
|
+
|
|
|
+import javax.swing.JButton;
|
|
|
+import javax.swing.JFrame;
|
|
|
+import javax.swing.JLabel;
|
|
|
+import javax.swing.JOptionPane;
|
|
|
+import javax.swing.JPanel;
|
|
|
+import javax.swing.JScrollPane;
|
|
|
+import javax.swing.JTable;
|
|
|
+import javax.swing.JTextField;
|
|
|
+import javax.swing.SwingUtilities;
|
|
|
+import javax.swing.UIManager;
|
|
|
+import javax.swing.WindowConstants;
|
|
|
+import javax.swing.border.EmptyBorder;
|
|
|
+import javax.swing.border.TitledBorder;
|
|
|
+import javax.swing.table.AbstractTableModel;
|
|
|
+import javax.swing.table.DefaultTableCellRenderer;
|
|
|
+import java.awt.BorderLayout;
|
|
|
+import java.awt.Color;
|
|
|
+import java.awt.Component;
|
|
|
+import java.awt.FlowLayout;
|
|
|
+import java.awt.event.WindowAdapter;
|
|
|
+import java.awt.event.WindowEvent;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 框架气密(OP360 / FrameworkQmScheduling)PLC 点位实时监视工具。
|
|
|
+ * <p>
|
|
|
+ * 用法:在 IDE 中右键运行本类 main(不要启动 Spring / Application)。
|
|
|
+ * 界面可修改 PLC IP、启停刷新,实时显示 DB9100 全部控制/过程点位。
|
|
|
+ */
|
|
|
+public class FrameworkQmPointMonitorTest {
|
|
|
+
|
|
|
+ private static final String DEFAULT_PLC_IP = "192.168.1.1";
|
|
|
+ private static final int DEFAULT_INTERVAL_MS = 500;
|
|
|
+ private static final int SN_LENGTH = 60;
|
|
|
+
|
|
|
+ private static final PointDef[] POINTS = {
|
|
|
+ // 进站
|
|
|
+ new PointDef("进站", "DB9100.112.0", "A1允许进站", PointType.BOOL),
|
|
|
+ new PointDef("进站", "DB9100.112.1", "A1扫码反馈", PointType.BOOL),
|
|
|
+ new PointDef("进站", "DB9100.114.0", "进站框架码 String[60]", PointType.STRING60),
|
|
|
+ new PointDef("进站", "DB9100.176.0", "A1扫码结果", PointType.BOOL),
|
|
|
+ // ST01
|
|
|
+ new PointDef("ST01", "DB9100.240.0", "气密ST01工作完成", PointType.BOOL),
|
|
|
+ new PointDef("ST01", "DB9100.178.0", "气密ST01框架码 String[60]", PointType.STRING60),
|
|
|
+ new PointDef("ST01", "DB9100.0.0", "液冷板充气时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.4.0", "液冷板保压时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.8.0", "液冷板测试时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.12.0", "液冷板充气压力", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.16.0", "液冷板稳定压力", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.20.0", "液冷板泄漏量", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.24.0", "框架充气时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.28.0", "框架保压时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.32.0", "框架测试时间", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.36.0", "框架充气压力", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.40.0", "框架稳定压力", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.44.0", "框架泄漏量", PointType.REAL),
|
|
|
+ new PointDef("ST01", "DB9100.48.0", "液冷板判定结果", PointType.BOOL),
|
|
|
+ new PointDef("ST01", "DB9100.48.1", "框架判定结果", PointType.BOOL),
|
|
|
+ new PointDef("ST01", "DB9100.48.2", "MES全部测试完成(只读)", PointType.BOOL),
|
|
|
+ // ST02
|
|
|
+ new PointDef("ST02", "DB9100.304.0", "气密ST02工作完成", PointType.BOOL),
|
|
|
+ new PointDef("ST02", "DB9100.242.0", "气密ST02框架码 String[60]", PointType.STRING60),
|
|
|
+ new PointDef("ST02", "DB9100.306.0", "液冷板充气时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.310.0", "液冷板保压时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.314.0", "液冷板测试时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.318.0", "液冷板充气压力ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.322.0", "液冷板稳定压力ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.326.0", "液冷板泄漏量ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.330.0", "框架充气时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.334.0", "框架保压时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.338.0", "框架测试时间ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.342.0", "框架充气压力ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.346.0", "框架稳定压力ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.350.0", "框架泄漏量ST02", PointType.REAL),
|
|
|
+ new PointDef("ST02", "DB9100.354.0", "液冷板判定结果ST02", PointType.BOOL),
|
|
|
+ new PointDef("ST02", "DB9100.354.1", "框架判定结果ST02", PointType.BOOL),
|
|
|
+ };
|
|
|
+
|
|
|
+ private enum PointType { BOOL, REAL, STRING60 }
|
|
|
+
|
|
|
+ private static final class PointDef {
|
|
|
+ final String group;
|
|
|
+ final String address;
|
|
|
+ final String title;
|
|
|
+ final PointType type;
|
|
|
+
|
|
|
+ PointDef(String group, String address, String title, PointType type) {
|
|
|
+ this.group = group;
|
|
|
+ this.address = address;
|
|
|
+ this.title = title;
|
|
|
+ this.type = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final class PointRow {
|
|
|
+ final PointDef def;
|
|
|
+ volatile String value = "-";
|
|
|
+ volatile boolean changed;
|
|
|
+
|
|
|
+ PointRow(PointDef def) {
|
|
|
+ this.def = def;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private final List<PointRow> rows = new ArrayList<>();
|
|
|
+ private final AtomicBoolean running = new AtomicBoolean(false);
|
|
|
+ private volatile String currentIp = DEFAULT_PLC_IP;
|
|
|
+ private volatile int intervalMs = DEFAULT_INTERVAL_MS;
|
|
|
+ private volatile long lastOkMs;
|
|
|
+ private Thread pollThread;
|
|
|
+
|
|
|
+ private JFrame frame;
|
|
|
+ private JTextField ipField;
|
|
|
+ private JTextField intervalField;
|
|
|
+ private JLabel statusLabel;
|
|
|
+ private JButton startBtn;
|
|
|
+ private JButton stopBtn;
|
|
|
+ private PointTableModel tableModel;
|
|
|
+ private final SimpleDateFormat timeFmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ // 仅启动 Swing,不启动 Spring / 不连数据库
|
|
|
+ System.setProperty("java.awt.headless", "false");
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ try {
|
|
|
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ new FrameworkQmPointMonitorTest().createAndShow();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void createAndShow() {
|
|
|
+ for (PointDef def : POINTS) {
|
|
|
+ rows.add(new PointRow(def));
|
|
|
+ }
|
|
|
+
|
|
|
+ frame = new JFrame("框架气密 OP360 点位监视 (FrameworkQmScheduling)");
|
|
|
+ frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
|
|
+ frame.setSize(920, 640);
|
|
|
+ frame.setLocationRelativeTo(null);
|
|
|
+
|
|
|
+ JPanel root = new JPanel(new BorderLayout(8, 8));
|
|
|
+ root.setBorder(new EmptyBorder(10, 10, 10, 10));
|
|
|
+ root.add(buildToolbar(), BorderLayout.NORTH);
|
|
|
+ root.add(buildTable(), BorderLayout.CENTER);
|
|
|
+ root.add(buildFooter(), BorderLayout.SOUTH);
|
|
|
+
|
|
|
+ frame.setContentPane(root);
|
|
|
+ frame.addWindowListener(new WindowAdapter() {
|
|
|
+ @Override
|
|
|
+ public void windowClosing(WindowEvent e) {
|
|
|
+ stopPolling();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ frame.setVisible(true);
|
|
|
+ System.out.println("[FrameworkQmPointMonitor] UI started, default IP=" + DEFAULT_PLC_IP);
|
|
|
+ }
|
|
|
+
|
|
|
+ private JPanel buildToolbar() {
|
|
|
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 4));
|
|
|
+ panel.setBorder(new TitledBorder("连接"));
|
|
|
+
|
|
|
+ panel.add(new JLabel("PLC IP:"));
|
|
|
+ ipField = new JTextField(currentIp, 14);
|
|
|
+ panel.add(ipField);
|
|
|
+
|
|
|
+ JButton applyIpBtn = new JButton("应用 IP");
|
|
|
+ applyIpBtn.addActionListener(e -> applyIp());
|
|
|
+ panel.add(applyIpBtn);
|
|
|
+
|
|
|
+ panel.add(new JLabel("刷新(ms):"));
|
|
|
+ intervalField = new JTextField(String.valueOf(DEFAULT_INTERVAL_MS), 5);
|
|
|
+ panel.add(intervalField);
|
|
|
+
|
|
|
+ startBtn = new JButton("开始监视");
|
|
|
+ startBtn.addActionListener(e -> startPolling());
|
|
|
+ panel.add(startBtn);
|
|
|
+
|
|
|
+ stopBtn = new JButton("停止");
|
|
|
+ stopBtn.setEnabled(false);
|
|
|
+ stopBtn.addActionListener(e -> stopPolling());
|
|
|
+ panel.add(stopBtn);
|
|
|
+
|
|
|
+ JButton onceBtn = new JButton("读一次");
|
|
|
+ onceBtn.addActionListener(e -> readOnceAsync());
|
|
|
+ panel.add(onceBtn);
|
|
|
+
|
|
|
+ return panel;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JScrollPane buildTable() {
|
|
|
+ tableModel = new PointTableModel();
|
|
|
+ JTable table = new JTable(tableModel);
|
|
|
+ table.setRowHeight(24);
|
|
|
+ table.setFillsViewportHeight(true);
|
|
|
+ table.getColumnModel().getColumn(0).setPreferredWidth(70);
|
|
|
+ table.getColumnModel().getColumn(1).setPreferredWidth(130);
|
|
|
+ table.getColumnModel().getColumn(2).setPreferredWidth(200);
|
|
|
+ table.getColumnModel().getColumn(3).setPreferredWidth(80);
|
|
|
+ table.getColumnModel().getColumn(4).setPreferredWidth(280);
|
|
|
+
|
|
|
+ DefaultTableCellRenderer valueRenderer = new DefaultTableCellRenderer() {
|
|
|
+ @Override
|
|
|
+ public Component getTableCellRendererComponent(JTable tbl, Object value,
|
|
|
+ boolean isSelected, boolean hasFocus, int row, int column) {
|
|
|
+ Component c = super.getTableCellRendererComponent(tbl, value, isSelected, hasFocus, row, column);
|
|
|
+ PointRow pr = rows.get(row);
|
|
|
+ if (!isSelected) {
|
|
|
+ if ("true".equalsIgnoreCase(String.valueOf(value))) {
|
|
|
+ c.setBackground(new Color(198, 239, 206));
|
|
|
+ } else if ("false".equalsIgnoreCase(String.valueOf(value))) {
|
|
|
+ c.setBackground(new Color(255, 235, 156));
|
|
|
+ } else if (pr.changed) {
|
|
|
+ c.setBackground(new Color(210, 230, 255));
|
|
|
+ } else {
|
|
|
+ c.setBackground(Color.WHITE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ table.getColumnModel().getColumn(4).setCellRenderer(valueRenderer);
|
|
|
+ return new JScrollPane(table);
|
|
|
+ }
|
|
|
+
|
|
|
+ private JPanel buildFooter() {
|
|
|
+ JPanel panel = new JPanel(new BorderLayout());
|
|
|
+ statusLabel = new JLabel("状态: 未连接 | IP=" + currentIp);
|
|
|
+ statusLabel.setBorder(new EmptyBorder(4, 2, 0, 2));
|
|
|
+ panel.add(statusLabel, BorderLayout.CENTER);
|
|
|
+ JLabel tip = new JLabel("对应 FrameworkQmScheduling:进站+ST01/ST02;改 IP 后点「应用 IP」或「开始监视」。不启动 Spring/数据库。");
|
|
|
+ tip.setForeground(Color.GRAY);
|
|
|
+ panel.add(tip, BorderLayout.SOUTH);
|
|
|
+ return panel;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void applyIp() {
|
|
|
+ String ip = ipField.getText() == null ? "" : ipField.getText().trim();
|
|
|
+ if (ip.isEmpty()) {
|
|
|
+ JOptionPane.showMessageDialog(frame, "请输入 PLC IP", "提示", JOptionPane.WARNING_MESSAGE);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentIp = ip;
|
|
|
+ updateStatus("已切换 IP=" + currentIp + (running.get() ? "(下一轮生效)" : ""));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startPolling() {
|
|
|
+ applyIp();
|
|
|
+ try {
|
|
|
+ intervalMs = Math.max(100, Integer.parseInt(intervalField.getText().trim()));
|
|
|
+ } catch (Exception e) {
|
|
|
+ intervalMs = DEFAULT_INTERVAL_MS;
|
|
|
+ intervalField.setText(String.valueOf(intervalMs));
|
|
|
+ }
|
|
|
+ if (!running.compareAndSet(false, true)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ startBtn.setEnabled(false);
|
|
|
+ stopBtn.setEnabled(true);
|
|
|
+ pollThread = new Thread(this::pollLoop, "framework-qm-point-monitor");
|
|
|
+ pollThread.setDaemon(true);
|
|
|
+ pollThread.start();
|
|
|
+ updateStatus("监视中 IP=" + currentIp + " 间隔=" + intervalMs + "ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void stopPolling() {
|
|
|
+ running.set(false);
|
|
|
+ if (pollThread != null) {
|
|
|
+ pollThread.interrupt();
|
|
|
+ pollThread = null;
|
|
|
+ }
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ if (startBtn != null) {
|
|
|
+ startBtn.setEnabled(true);
|
|
|
+ }
|
|
|
+ if (stopBtn != null) {
|
|
|
+ stopBtn.setEnabled(false);
|
|
|
+ }
|
|
|
+ updateStatus("已停止 | 上次成功=" + (lastOkMs == 0 ? "-" : timeFmt.format(new Date(lastOkMs))));
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readOnceAsync() {
|
|
|
+ applyIp();
|
|
|
+ new Thread(() -> {
|
|
|
+ try {
|
|
|
+ readAllPoints();
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ tableModel.fireTableDataChanged();
|
|
|
+ updateStatus("读一次成功 " + timeFmt.format(new Date(lastOkMs)) + " | IP=" + currentIp);
|
|
|
+ });
|
|
|
+ } catch (Exception ex) {
|
|
|
+ SwingUtilities.invokeLater(() ->
|
|
|
+ updateStatus("读一次失败: " + ex.getMessage() + " | IP=" + currentIp));
|
|
|
+ }
|
|
|
+ }, "framework-qm-read-once").start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void pollLoop() {
|
|
|
+ while (running.get()) {
|
|
|
+ try {
|
|
|
+ readAllPoints();
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ tableModel.fireTableDataChanged();
|
|
|
+ updateStatus("已连接 " + timeFmt.format(new Date(lastOkMs))
|
|
|
+ + " | IP=" + currentIp + " | 间隔=" + intervalMs + "ms");
|
|
|
+ });
|
|
|
+ } catch (Exception ex) {
|
|
|
+ SwingUtilities.invokeLater(() ->
|
|
|
+ updateStatus("读取失败: " + ex.getMessage() + " | IP=" + currentIp));
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Thread.sleep(intervalMs);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readAllPoints() throws Exception {
|
|
|
+ S7PLC s7 = null;
|
|
|
+ try {
|
|
|
+ s7 = new S7PLC(EPlcType.S1200, currentIp);
|
|
|
+ for (PointRow row : rows) {
|
|
|
+ String old = row.value;
|
|
|
+ String neu = readPoint(s7, row.def);
|
|
|
+ row.value = neu;
|
|
|
+ row.changed = old != null && !old.equals(neu) && !"-".equals(old);
|
|
|
+ }
|
|
|
+ lastOkMs = System.currentTimeMillis();
|
|
|
+ } finally {
|
|
|
+ if (s7 != null) {
|
|
|
+ try {
|
|
|
+ s7.close();
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String readPoint(S7PLC s7, PointDef def) {
|
|
|
+ switch (def.type) {
|
|
|
+ case BOOL:
|
|
|
+ return String.valueOf(s7.readBoolean(def.address));
|
|
|
+ case REAL:
|
|
|
+ return String.format("%.3f", s7.readFloat32(def.address));
|
|
|
+ case STRING60:
|
|
|
+ return sanitizeString(s7.readString(def.address, SN_LENGTH));
|
|
|
+ default:
|
|
|
+ return "?";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String sanitizeString(String raw) {
|
|
|
+ if (raw == null || raw.trim().isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ StringBuilder sb = new StringBuilder(raw.length());
|
|
|
+ for (int i = 0; i < raw.length(); i++) {
|
|
|
+ char c = raw.charAt(i);
|
|
|
+ if (c == 0 || c == ' ') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (c >= 32 && c < 127) {
|
|
|
+ sb.append(c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return sb.toString().trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateStatus(String text) {
|
|
|
+ if (statusLabel != null) {
|
|
|
+ statusLabel.setText("状态: " + text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private final class PointTableModel extends AbstractTableModel {
|
|
|
+ private final String[] cols = {"分组", "地址", "说明", "类型", "当前值"};
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getRowCount() {
|
|
|
+ return rows.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getColumnCount() {
|
|
|
+ return cols.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getColumnName(int column) {
|
|
|
+ return cols[column];
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object getValueAt(int rowIndex, int columnIndex) {
|
|
|
+ PointRow row = rows.get(rowIndex);
|
|
|
+ switch (columnIndex) {
|
|
|
+ case 0:
|
|
|
+ return row.def.group;
|
|
|
+ case 1:
|
|
|
+ return row.def.address;
|
|
|
+ case 2:
|
|
|
+ return row.def.title;
|
|
|
+ case 3:
|
|
|
+ return row.def.type.name();
|
|
|
+ case 4:
|
|
|
+ return row.value;
|
|
|
+ default:
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|