package com.mes.ui; import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp; import com.mes.util.JdbcUtils; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 设备管理:IP、预设数量、按颗数区间的铆接参数配置 */ public class DeviceManagePanel extends JPanel { private static final String[] PARAM_COLUMNS = {"起始颗", "结束颗", "F-min", "F-max", "S-min", "S-max"}; private static final int CONTENT_WIDTH = 940; private JTextField txtASet; private JTextField txtBSet; private JTextField txtIpA; private JTextField txtIpB; private DefaultTableModel tableModelA; private DefaultTableModel tableModelB; private JTable tableA; private JTable tableB; public DeviceManagePanel() { setLayout(null); setPreferredSize(new Dimension(980, 720)); buildUi(); loadConfigToUi(); } private void buildUi() { Font labelFont = new Font("微软雅黑", Font.PLAIN, 18); Font fieldFont = new Font("微软雅黑", Font.PLAIN, 18); Font hintFont = new Font("微软雅黑", Font.PLAIN, 14); addLabel("A枪预设数量:", 20, 16, 130, 34, labelFont); txtASet = addField(String.valueOf(MesClient.aSetNum), 150, 16, 120, 34, fieldFont); addLabel("B枪预设数量:", 320, 16, 130, 34, labelFont); txtBSet = addField(String.valueOf(MesClient.bSetNum), 450, 16, 120, 34, fieldFont); addLabel("A枪IP:", 20, 58, 80, 34, labelFont); txtIpA = addField(MesClient.curIpA, 100, 58, 170, 34, fieldFont); addLabel("B枪IP:", 320, 58, 80, 34, labelFont); txtIpB = addField(MesClient.curIpB, 400, 58, 170, 34, fieldFont); addLabel("说明:S-min/S-max 为PLC原始值,界面行程显示值 = S / 1000", 20, 98, CONTENT_WIDTH, 24, hintFont); addLabel("A枪参数区间", 20, 128, 200, 28, labelFont); tableModelA = createParamTableModel(); tableA = createParamTable(tableModelA, 20, 156, CONTENT_WIDTH, 150); JButton btnAddA = new JButton("A枪添加行"); btnAddA.setFont(labelFont); btnAddA.setBounds(20, 316, 120, 32); btnAddA.addActionListener(e -> tableModelA.addRow(new Object[]{"", "", "0", "0", "0", "0"})); add(btnAddA); JButton btnDelA = new JButton("A枪删除行"); btnDelA.setFont(labelFont); btnDelA.setBounds(150, 316, 120, 32); btnDelA.addActionListener(e -> removeSelectedRow(tableA, tableModelA)); add(btnDelA); addLabel("B枪参数区间", 20, 360, 200, 28, labelFont); tableModelB = createParamTableModel(); tableB = createParamTable(tableModelB, 20, 388, CONTENT_WIDTH, 150); JButton btnAddB = new JButton("B枪添加行"); btnAddB.setFont(labelFont); btnAddB.setBounds(20, 548, 120, 32); btnAddB.addActionListener(e -> tableModelB.addRow(new Object[]{"", "", "0", "0", "0", "0"})); add(btnAddB); JButton btnDelB = new JButton("B枪删除行"); btnDelB.setFont(labelFont); btnDelB.setBounds(150, 548, 120, 32); btnDelB.addActionListener(e -> removeSelectedRow(tableB, tableModelB)); add(btnDelB); JButton btnSave = new JButton("保存全部设置"); btnSave.setFont(new Font("微软雅黑", Font.PLAIN, 20)); btnSave.setBounds(380, 596, 180, 45); btnSave.addActionListener(e -> saveAllConfig()); add(btnSave); addLabel("每行表示「从起始颗到结束颗」使用同一套参数;开工时下发第1颗参数,每完成一颗后自动切换下一颗区间。", 20, 652, CONTENT_WIDTH, 40, hintFont); } private void addLabel(String text, int x, int y, int w, int h, Font font) { JLabel label = new JLabel("" + text + ""); label.setFont(font); label.setBounds(x, y, w, h); add(label); } private JTextField addField(String text, int x, int y, int w, int h, Font font) { JTextField field = new JTextField(text); field.setFont(font); field.setBounds(x, y, w, h); add(field); return field; } private DefaultTableModel createParamTableModel() { return new DefaultTableModel(PARAM_COLUMNS, 0) { @Override public boolean isCellEditable(int row, int column) { return true; } }; } private JTable createParamTable(DefaultTableModel model, int x, int y, int w, int h) { JTable table = new JTable(model); table.setRowHeight(28); table.setFont(new Font("微软雅黑", Font.PLAIN, 14)); table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); JScrollPane scrollPane = new JScrollPane(table); scrollPane.setBounds(x, y, w, h); add(scrollPane); return table; } private void removeSelectedRow(JTable table, DefaultTableModel model) { int row = table.getSelectedRow(); if (row >= 0) { model.removeRow(row); } } private void loadConfigToUi() { txtASet.setText(String.valueOf(MesClient.aSetNum)); txtBSet.setText(String.valueOf(MesClient.bSetNum)); txtIpA.setText(MesClient.curIpA); txtIpB.setText(MesClient.curIpB); fillTableModel(tableModelA, JdbcUtils.getRivetParams("A")); fillTableModel(tableModelB, JdbcUtils.getRivetParams("B")); } // 重新从数据库加载到界面(进入设备管理页时调用) public void reloadFromDb() { Map config = JdbcUtils.getConfig(); if (config.size() > 0) { MesClient.aSetNum = (Short) config.get("a_set_num"); MesClient.bSetNum = (Short) config.get("b_set_num"); MesClient.curIpA = (String) config.get("plc_ip_a"); MesClient.curIpB = (String) config.get("plc_ip_b"); } MesClient.loadRivetParamConfig(); loadConfigToUi(); } private void fillTableModel(DefaultTableModel model, List ranges) { model.setRowCount(0); for (RivetParamRange range : ranges) { model.addRow(new Object[]{ String.valueOf(range.getStartRivet()), String.valueOf(range.getEndRivet()), String.valueOf(range.getFMin()), String.valueOf(range.getFMax()), String.valueOf(range.getSMin()), String.valueOf(range.getSMax()) }); } } private List parseTableModel(DefaultTableModel model, String gunType) throws IllegalArgumentException { List ranges = new ArrayList<>(); for (int i = 0; i < model.getRowCount(); i++) { String startText = String.valueOf(model.getValueAt(i, 0)).trim(); String endText = String.valueOf(model.getValueAt(i, 1)).trim(); if (startText.isEmpty() && endText.isEmpty()) { continue; } if (startText.isEmpty() || endText.isEmpty()) { throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行起始颗/结束颗不能为空"); } int start = Integer.parseInt(startText); int end = Integer.parseInt(endText); if (start < 1 || end < start) { throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行颗数范围无效"); } int fMin = parseIntCell(model, i, 2); int fMax = parseIntCell(model, i, 3); int sMin = parseIntCell(model, i, 4); int sMax = parseIntCell(model, i, 5); if (fMin > fMax) { throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行 F-min 不能大于 F-max"); } if (sMin > sMax) { throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行 S-min 不能大于 S-max"); } // PLC Int16 写入上限,避免溢出后下发错误值 if (fMin < Short.MIN_VALUE || fMax > Short.MAX_VALUE || sMin < Short.MIN_VALUE || sMax > Short.MAX_VALUE) { throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行参数超出范围(-32768~32767)"); } RivetParamRange range = new RivetParamRange(); range.setGunType(gunType); range.setStartRivet(start); range.setEndRivet(end); range.setFMin(fMin); range.setFMax(fMax); range.setSMin(sMin); range.setSMax(sMax); ranges.add(range); } if (ranges.isEmpty()) { throw new IllegalArgumentException(gunType + "枪至少配置一行参数区间"); } // 检查区间是否重叠,重叠会导致下发时命中错误行 for (int i = 0; i < ranges.size(); i++) { RivetParamRange a = ranges.get(i); for (int j = i + 1; j < ranges.size(); j++) { RivetParamRange b = ranges.get(j); if (a.getStartRivet() <= b.getEndRivet() && b.getStartRivet() <= a.getEndRivet()) { throw new IllegalArgumentException(gunType + "枪参数区间存在重叠:" + a.getStartRivet() + "-" + a.getEndRivet() + " 与 " + b.getStartRivet() + "-" + b.getEndRivet()); } } } return ranges; } private int parseIntCell(DefaultTableModel model, int row, int col) { String text = String.valueOf(model.getValueAt(row, col)).trim(); if (text.isEmpty()) { return 0; } return Integer.parseInt(text); } // 提交表格编辑并将参数下发到PLC private boolean pushRivetParamsToDevice(ModbusTcp plc, List ranges, String gunType) { if (plc == null) { System.out.println(gunType + "枪PLC未连接,跳过参数下发"); return false; } try { ModbusUtil.syncRivetParamsToPlc(plc, ranges); return true; } catch (Exception ex) { ex.printStackTrace(); return false; } } private void saveAllConfig() { // 保存前先结束表格编辑,确保最新输入写入模型 if (tableA != null && tableA.isEditing()) { tableA.getCellEditor().stopCellEditing(); } if (tableB != null && tableB.isEditing()) { tableB.getCellEditor().stopCellEditing(); } try { short newASet = Short.parseShort(txtASet.getText().trim()); short newBSet = Short.parseShort(txtBSet.getText().trim()); String newIpA = txtIpA.getText().trim(); String newIpB = txtIpB.getText().trim(); if (newIpA.isEmpty() || newIpB.isEmpty()) { JOptionPane.showMessageDialog(MesClient.mesClientFrame, "IP地址不能为空", "错误", JOptionPane.ERROR_MESSAGE); return; } List rangesA = parseTableModel(tableModelA, "A"); List rangesB = parseTableModel(tableModelB, "B"); boolean configOk = JdbcUtils.updateConfig(newASet, newBSet, newIpA, newIpB); boolean saveAOk = JdbcUtils.saveRivetParams("A", rangesA); boolean saveBOk = JdbcUtils.saveRivetParams("B", rangesB); if (!configOk || !saveAOk || !saveBOk) { JOptionPane.showMessageDialog(MesClient.mesClientFrame, "配置保存失败,请查看控制台日志后重试(常见原因:数据库被占用)", "错误", JOptionPane.ERROR_MESSAGE); // 回读数据库,避免界面显示未真正落库的值 reloadFromDb(); return; } MesClient.aSetNum = newASet; MesClient.bSetNum = newBSet; MesClient.rivetParamRangesA = rangesA; MesClient.rivetParamRangesB = rangesB; if (MesClient.param1 != null) { MesClient.param1.setText(String.valueOf(MesClient.aSetNum)); } if (MesClient.param3 != null) { MesClient.param3.setText(String.valueOf(MesClient.bSetNum)); } boolean ipAChanged = !newIpA.equals(MesClient.curIpA); boolean ipBChanged = !newIpB.equals(MesClient.curIpB); if (ipAChanged) { if (MesClient.plcA != null) { MesClient.plcA.close(); } MesClient.curIpA = newIpA; MesClient.plcA = new ModbusTcp(1, MesClient.curIpA); } if (ipBChanged) { if (MesClient.plcB != null) { MesClient.plcB.close(); } MesClient.curIpB = newIpB; MesClient.plcB = new ModbusTcp(1, MesClient.curIpB); } // 保存后立即将拉力/行程参数下发到设备 boolean syncA = pushRivetParamsToDevice(MesClient.plcA, rangesA, "A"); boolean syncB = pushRivetParamsToDevice(MesClient.plcB, rangesB, "B"); if (syncA && syncB) { JOptionPane.showMessageDialog(MesClient.mesClientFrame, "配置已保存,并已下发到设备", "提示", JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(MesClient.mesClientFrame, "配置已保存到本地,但部分设备参数下发失败,请检查设备连接;下次开工时会按新配置重新下发", "提示", JOptionPane.WARNING_MESSAGE); } } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(MesClient.mesClientFrame, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE); } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(MesClient.mesClientFrame, ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } }