|
|
@@ -0,0 +1,379 @@
|
|
|
+package com.mes.ui;
|
|
|
+
|
|
|
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
|
|
|
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
|
|
|
+
|
|
|
+import javax.swing.*;
|
|
|
+import javax.swing.border.EmptyBorder;
|
|
|
+import javax.swing.border.TitledBorder;
|
|
|
+import javax.swing.table.DefaultTableCellRenderer;
|
|
|
+import javax.swing.table.DefaultTableModel;
|
|
|
+import java.awt.*;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Properties;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.TimerTask;
|
|
|
+
|
|
|
+/**
|
|
|
+ * PLC 点位实时监控测试窗口(DB200 全部点位,每 4 秒刷新)
|
|
|
+ */
|
|
|
+public class PlcReadTestFrame extends JFrame {
|
|
|
+
|
|
|
+ private static final String DEFAULT_PLC_IP = "192.168.1.1";
|
|
|
+ private static final int DB_NO = 200;
|
|
|
+ private static final long READ_INTERVAL_MS = 1000;
|
|
|
+ private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ private final S7PLC s7PLC;
|
|
|
+ private final String db;
|
|
|
+ private final List<PlcPointDef> pointDefs = buildPointDefs();
|
|
|
+
|
|
|
+ private JLabel lblPlcInfo;
|
|
|
+ private JLabel lblLastUpdate;
|
|
|
+ private JLabel lblReadStatus;
|
|
|
+ private JButton btnMesAllowTest;
|
|
|
+ private DefaultTableModel tableModel;
|
|
|
+ private Timer readTimer;
|
|
|
+
|
|
|
+ public PlcReadTestFrame(String plcIp) {
|
|
|
+ this.s7PLC = new S7PLC(EPlcType.S1200, plcIp);
|
|
|
+ this.db = "DB" + DB_NO;
|
|
|
+
|
|
|
+ setTitle("PLC 点位监控测试 - OP040~OP060");
|
|
|
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
|
+ setBounds(100, 80, 980, 720);
|
|
|
+ setMinimumSize(new Dimension(860, 600));
|
|
|
+
|
|
|
+ initUI(plcIp);
|
|
|
+ startReadTimer();
|
|
|
+ addWindowListener(new java.awt.event.WindowAdapter() {
|
|
|
+ @Override
|
|
|
+ public void windowClosing(java.awt.event.WindowEvent e) {
|
|
|
+ stopReadTimer();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initUI(String plcIp) {
|
|
|
+ JPanel content = new JPanel(new BorderLayout(8, 8));
|
|
|
+ content.setBorder(new EmptyBorder(10, 10, 10, 10));
|
|
|
+ setContentPane(content);
|
|
|
+
|
|
|
+ content.add(buildTopPanel(plcIp), BorderLayout.NORTH);
|
|
|
+ content.add(buildTablePanel(), BorderLayout.CENTER);
|
|
|
+ content.add(buildBottomPanel(), BorderLayout.SOUTH);
|
|
|
+ }
|
|
|
+
|
|
|
+ private JPanel buildTopPanel(String plcIp) {
|
|
|
+ JPanel panel = new JPanel(new BorderLayout(8, 4));
|
|
|
+ panel.setBorder(new TitledBorder("连接信息"));
|
|
|
+
|
|
|
+ lblPlcInfo = new JLabel("PLC IP: " + plcIp + " 数据块: " + db + " 刷新间隔: "
|
|
|
+ + (READ_INTERVAL_MS / 1000) + " 秒 点位数量: " + pointDefs.size());
|
|
|
+ lblPlcInfo.setFont(new Font("微软雅黑", Font.PLAIN, 15));
|
|
|
+ panel.add(lblPlcInfo, BorderLayout.WEST);
|
|
|
+
|
|
|
+ lblLastUpdate = new JLabel("最后更新: --");
|
|
|
+ lblLastUpdate.setFont(new Font("微软雅黑", Font.PLAIN, 15));
|
|
|
+ lblLastUpdate.setHorizontalAlignment(SwingConstants.RIGHT);
|
|
|
+ panel.add(lblLastUpdate, BorderLayout.EAST);
|
|
|
+ return panel;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JScrollPane buildTablePanel() {
|
|
|
+ String[] columns = {"分组", "名称", "PLC地址", "类型", "当前值"};
|
|
|
+ tableModel = new DefaultTableModel(columns, 0) {
|
|
|
+ @Override
|
|
|
+ public boolean isCellEditable(int row, int column) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ JTable table = new JTable(tableModel);
|
|
|
+ table.setRowHeight(32);
|
|
|
+ table.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
|
|
+ table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 14));
|
|
|
+ table.getTableHeader().setReorderingAllowed(false);
|
|
|
+ table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
|
+
|
|
|
+ table.getColumnModel().getColumn(0).setPreferredWidth(100);
|
|
|
+ table.getColumnModel().getColumn(1).setPreferredWidth(180);
|
|
|
+ table.getColumnModel().getColumn(2).setPreferredWidth(120);
|
|
|
+ table.getColumnModel().getColumn(3).setPreferredWidth(70);
|
|
|
+ table.getColumnModel().getColumn(4).setPreferredWidth(140);
|
|
|
+
|
|
|
+ DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
|
|
|
+ centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
|
|
|
+ table.getColumnModel().getColumn(2).setCellRenderer(centerRenderer);
|
|
|
+ table.getColumnModel().getColumn(3).setCellRenderer(centerRenderer);
|
|
|
+ table.getColumnModel().getColumn(4).setCellRenderer(new PlcValueCellRenderer());
|
|
|
+
|
|
|
+ DefaultTableCellRenderer leftRenderer = new DefaultTableCellRenderer();
|
|
|
+ leftRenderer.setHorizontalAlignment(SwingConstants.LEFT);
|
|
|
+ table.getColumnModel().getColumn(1).setCellRenderer(leftRenderer);
|
|
|
+
|
|
|
+ // 初始化空行
|
|
|
+ for (PlcPointDef def : pointDefs) {
|
|
|
+ tableModel.addRow(new Object[]{def.group, def.name, db + "." + def.offset, def.type, "--"});
|
|
|
+ }
|
|
|
+
|
|
|
+ JScrollPane scrollPane = new JScrollPane(table);
|
|
|
+ scrollPane.setBorder(new TitledBorder("DB200 全部点位(实时)"));
|
|
|
+ return scrollPane;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JPanel buildBottomPanel() {
|
|
|
+ JPanel panel = new JPanel(new BorderLayout());
|
|
|
+ lblReadStatus = new JLabel("状态: 等待首次读取...");
|
|
|
+ lblReadStatus.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
|
|
+ lblReadStatus.setForeground(new Color(0, 100, 0));
|
|
|
+ panel.add(lblReadStatus, BorderLayout.WEST);
|
|
|
+
|
|
|
+ JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
|
|
+
|
|
|
+ btnMesAllowTest = new JButton("测试: MES允许进站 → TRUE");
|
|
|
+ btnMesAllowTest.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
|
|
+ btnMesAllowTest.setForeground(new Color(0, 100, 0));
|
|
|
+ btnMesAllowTest.addActionListener(e -> toggleMesAllowEntry());
|
|
|
+ btnPanel.add(btnMesAllowTest);
|
|
|
+
|
|
|
+ JButton refreshBtn = new JButton("立即刷新");
|
|
|
+ refreshBtn.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
|
|
+ refreshBtn.addActionListener(e -> readPointsAsync(true));
|
|
|
+ btnPanel.add(refreshBtn);
|
|
|
+
|
|
|
+ panel.add(btnPanel, BorderLayout.EAST);
|
|
|
+ return panel;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试写入 MES允许进站(DB200.0.1),点击后在 TRUE/FALSE 间切换
|
|
|
+ */
|
|
|
+ private void toggleMesAllowEntry() {
|
|
|
+ btnMesAllowTest.setEnabled(false);
|
|
|
+ new Thread(() -> {
|
|
|
+ try {
|
|
|
+ Boolean current = s7PLC.readBoolean(db + ".0.1");
|
|
|
+ boolean newVal = !Boolean.TRUE.equals(current);
|
|
|
+ s7PLC.writeBoolean(db + ".0.1", newVal);
|
|
|
+ final String msg = "MES允许进站 已写入: " + (newVal ? "TRUE" : "FALSE");
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ updateMesAllowButtonText(newVal);
|
|
|
+ lblReadStatus.setText("状态: " + msg);
|
|
|
+ lblReadStatus.setForeground(new Color(0, 100, 0));
|
|
|
+ btnMesAllowTest.setEnabled(true);
|
|
|
+ readPointsAsync(true);
|
|
|
+ });
|
|
|
+ } catch (Exception e) {
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
+ lblReadStatus.setText("状态: MES允许进站写入失败 - " + e.getMessage());
|
|
|
+ lblReadStatus.setForeground(Color.RED);
|
|
|
+ btnMesAllowTest.setEnabled(true);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, "PlcRead-MesAllowTest").start();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 根据当前值更新测试按钮文案 */
|
|
|
+ private void updateMesAllowButtonText(boolean currentVal) {
|
|
|
+ if (currentVal) {
|
|
|
+ btnMesAllowTest.setText("测试: MES允许进站 → FALSE");
|
|
|
+ btnMesAllowTest.setForeground(new Color(180, 80, 0));
|
|
|
+ } else {
|
|
|
+ btnMesAllowTest.setText("测试: MES允许进站 → TRUE");
|
|
|
+ btnMesAllowTest.setForeground(new Color(0, 100, 0));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startReadTimer() {
|
|
|
+ readTimer = new Timer("PlcReadTestFrame-Timer", true);
|
|
|
+ readTimer.schedule(new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ readPointsAsync(false);
|
|
|
+ }
|
|
|
+ }, 0, READ_INTERVAL_MS);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void stopReadTimer() {
|
|
|
+ if (readTimer != null) {
|
|
|
+ readTimer.cancel();
|
|
|
+ readTimer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readPointsAsync(boolean manual) {
|
|
|
+ new Thread(() -> {
|
|
|
+ List<String[]> rows = new ArrayList<>();
|
|
|
+ int errorCount = 0;
|
|
|
+ for (PlcPointDef def : pointDefs) {
|
|
|
+ String value = readPointValue(def);
|
|
|
+ if (value.startsWith("读取异常")) {
|
|
|
+ errorCount++;
|
|
|
+ }
|
|
|
+ rows.add(new String[]{def.group, def.name, db + "." + def.offset, def.type, value});
|
|
|
+ }
|
|
|
+ final int errors = errorCount;
|
|
|
+ final String time = LocalDateTime.now().format(TIME_FMT);
|
|
|
+ SwingUtilities.invokeLater(() -> updateTable(rows, time, errors, manual));
|
|
|
+ }, manual ? "PlcRead-Manual" : "PlcRead-Timer").start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String readPointValue(PlcPointDef def) {
|
|
|
+ try {
|
|
|
+ String addr = db + "." + def.offset;
|
|
|
+ switch (def.type) {
|
|
|
+ case "Bool":
|
|
|
+ Boolean b = s7PLC.readBoolean(addr);
|
|
|
+ return b == null ? "null" : (b ? "TRUE" : "FALSE");
|
|
|
+ case "Word":
|
|
|
+ Short w = s7PLC.readInt16(addr);
|
|
|
+ return w == null ? "null" : String.valueOf(w);
|
|
|
+ case "Real":
|
|
|
+ Float r = s7PLC.readFloat32(addr);
|
|
|
+ return r == null ? "null" : String.format("%.2f", r);
|
|
|
+ default:
|
|
|
+ return "未知类型";
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ return "读取异常: " + e.getMessage();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateTable(List<String[]> rows, String time, int errorCount, boolean manual) {
|
|
|
+ for (int i = 0; i < rows.size() && i < tableModel.getRowCount(); i++) {
|
|
|
+ String[] row = rows.get(i);
|
|
|
+ for (int col = 0; col < row.length; col++) {
|
|
|
+ tableModel.setValueAt(row[col], i, col);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ lblLastUpdate.setText("最后更新: " + time + (manual ? "(手动)" : ""));
|
|
|
+ if (errorCount > 0) {
|
|
|
+ lblReadStatus.setText("状态: 读取完成," + errorCount + " 个点位异常");
|
|
|
+ lblReadStatus.setForeground(Color.RED);
|
|
|
+ } else if (!manual) {
|
|
|
+ lblReadStatus.setText("状态: 读取正常,共 " + pointDefs.size() + " 个点位");
|
|
|
+ lblReadStatus.setForeground(new Color(0, 128, 0));
|
|
|
+ }
|
|
|
+ // 同步 MES允许进站 测试按钮文案
|
|
|
+ syncMesAllowButtonFromTable();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 从表格中 MES允许进站 行同步按钮显示 */
|
|
|
+ private void syncMesAllowButtonFromTable() {
|
|
|
+ if (btnMesAllowTest == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (int i = 0; i < tableModel.getRowCount(); i++) {
|
|
|
+ if ("MES允许进站".equals(tableModel.getValueAt(i, 1))) {
|
|
|
+ String val = String.valueOf(tableModel.getValueAt(i, 4));
|
|
|
+ updateMesAllowButtonText("TRUE".equals(val));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** DB200 全部点位定义 */
|
|
|
+ private static List<PlcPointDef> buildPointDefs() {
|
|
|
+ List<PlcPointDef> list = new ArrayList<>();
|
|
|
+ // 状态信号 Bool
|
|
|
+ list.add(new PlcPointDef("状态信号", "心跳信号", "0.0", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "MES允许进站", "0.1", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "工位A识别", "0.2", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "工位B识别", "0.3", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "A面启动中", "0.4", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "B面启动中", "0.5", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "R1焊接中", "0.6", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "R2焊接中", "0.7", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "屏蔽信号", "1.0", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "报警信号", "1.1", "Bool"));
|
|
|
+ list.add(new PlcPointDef("状态信号", "R1焊接完成(单条)", "1.2", "Bool"));
|
|
|
+ // R1 工艺参数
|
|
|
+ list.add(new PlcPointDef("R1工艺参数", "R1焊道序号", "2", "Word"));
|
|
|
+ list.add(new PlcPointDef("R1工艺参数", "R1焊接电流平均值", "4", "Real"));
|
|
|
+ list.add(new PlcPointDef("R1工艺参数", "R1焊接电压平均值", "8", "Real"));
|
|
|
+ list.add(new PlcPointDef("R1工艺参数", "R1送丝速度平均值", "12", "Real"));
|
|
|
+ list.add(new PlcPointDef("R1工艺参数", "R1焊接速度", "16", "Real"));
|
|
|
+ // R2 工艺参数
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2焊接完成(单条)", "20.0", "Bool"));
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2焊道序号", "22", "Word"));
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2焊接电流平均值", "24", "Real"));
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2焊接电压平均值", "28", "Real"));
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2送丝速度平均值", "32", "Real"));
|
|
|
+ list.add(new PlcPointDef("R2工艺参数", "R2焊接速度", "36", "Real"));
|
|
|
+ // 面完成信号
|
|
|
+ list.add(new PlcPointDef("面完成信号", "A面焊接完成", "40.0", "Bool"));
|
|
|
+ list.add(new PlcPointDef("面完成信号", "B面焊接完成", "40.1", "Bool"));
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String loadPlcIp() {
|
|
|
+ try {
|
|
|
+ InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
|
|
|
+ if (is != null) {
|
|
|
+ Properties pro = new Properties();
|
|
|
+ BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
|
|
+ pro.load(br);
|
|
|
+ br.close();
|
|
|
+ String ip = pro.getProperty("mes.plc_ip");
|
|
|
+ if (ip != null && !ip.trim().isEmpty()) {
|
|
|
+ return ip.trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ }
|
|
|
+ return DEFAULT_PLC_IP;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 点位定义 */
|
|
|
+ private static class PlcPointDef {
|
|
|
+ final String group;
|
|
|
+ final String name;
|
|
|
+ final String offset;
|
|
|
+ final String type;
|
|
|
+
|
|
|
+ PlcPointDef(String group, String name, String offset, String type) {
|
|
|
+ this.group = group;
|
|
|
+ this.name = name;
|
|
|
+ this.offset = offset;
|
|
|
+ this.type = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当前值列渲染:TRUE/FALSE/异常 分色显示 */
|
|
|
+ private static class PlcValueCellRenderer extends DefaultTableCellRenderer {
|
|
|
+ @Override
|
|
|
+ public Component getTableCellRendererComponent(JTable table, Object value,
|
|
|
+ boolean isSelected, boolean hasFocus,
|
|
|
+ int row, int column) {
|
|
|
+ Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
|
|
+ setHorizontalAlignment(SwingConstants.CENTER);
|
|
|
+ if (!isSelected) {
|
|
|
+ String text = value == null ? "" : value.toString();
|
|
|
+ if ("TRUE".equals(text)) {
|
|
|
+ setBackground(new Color(210, 245, 210));
|
|
|
+ setForeground(new Color(0, 120, 0));
|
|
|
+ setFont(getFont().deriveFont(Font.BOLD));
|
|
|
+ } else if ("FALSE".equals(text)) {
|
|
|
+ setBackground(Color.WHITE);
|
|
|
+ setForeground(new Color(100, 100, 100));
|
|
|
+ setFont(getFont().deriveFont(Font.PLAIN));
|
|
|
+ } else if (text.startsWith("读取异常")) {
|
|
|
+ setBackground(new Color(255, 230, 230));
|
|
|
+ setForeground(Color.RED);
|
|
|
+ } else {
|
|
|
+ setBackground(new Color(240, 248, 255));
|
|
|
+ setForeground(new Color(0, 70, 140));
|
|
|
+ setFont(getFont().deriveFont(Font.BOLD));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|