DeviceManagePanel.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package com.mes.ui;
  2. import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp;
  3. import com.mes.util.JdbcUtils;
  4. import javax.swing.*;
  5. import javax.swing.table.DefaultTableModel;
  6. import java.awt.*;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. import java.util.Map;
  10. /**
  11. * 设备管理:IP、预设数量、按颗数区间的铆接参数配置
  12. */
  13. public class DeviceManagePanel extends JPanel {
  14. private static final String[] PARAM_COLUMNS = {"起始颗", "结束颗", "F-min", "F-max", "S-min", "S-max"};
  15. private static final int CONTENT_WIDTH = 940;
  16. private JTextField txtASet;
  17. private JTextField txtBSet;
  18. private JTextField txtIpA;
  19. private JTextField txtIpB;
  20. private DefaultTableModel tableModelA;
  21. private DefaultTableModel tableModelB;
  22. private JTable tableA;
  23. private JTable tableB;
  24. public DeviceManagePanel() {
  25. setLayout(null);
  26. setPreferredSize(new Dimension(980, 720));
  27. buildUi();
  28. loadConfigToUi();
  29. }
  30. private void buildUi() {
  31. Font labelFont = new Font("微软雅黑", Font.PLAIN, 18);
  32. Font fieldFont = new Font("微软雅黑", Font.PLAIN, 18);
  33. Font hintFont = new Font("微软雅黑", Font.PLAIN, 14);
  34. addLabel("A枪预设数量:", 20, 16, 130, 34, labelFont);
  35. txtASet = addField(String.valueOf(MesClient.aSetNum), 150, 16, 120, 34, fieldFont);
  36. addLabel("B枪预设数量:", 320, 16, 130, 34, labelFont);
  37. txtBSet = addField(String.valueOf(MesClient.bSetNum), 450, 16, 120, 34, fieldFont);
  38. addLabel("A枪IP:", 20, 58, 80, 34, labelFont);
  39. txtIpA = addField(MesClient.curIpA, 100, 58, 170, 34, fieldFont);
  40. addLabel("B枪IP:", 320, 58, 80, 34, labelFont);
  41. txtIpB = addField(MesClient.curIpB, 400, 58, 170, 34, fieldFont);
  42. addLabel("说明:S-min/S-max 为PLC原始值,界面行程显示值 = S / 1000", 20, 98, CONTENT_WIDTH, 24, hintFont);
  43. addLabel("A枪参数区间", 20, 128, 200, 28, labelFont);
  44. tableModelA = createParamTableModel();
  45. tableA = createParamTable(tableModelA, 20, 156, CONTENT_WIDTH, 150);
  46. JButton btnAddA = new JButton("A枪添加行");
  47. btnAddA.setFont(labelFont);
  48. btnAddA.setBounds(20, 316, 120, 32);
  49. btnAddA.addActionListener(e -> tableModelA.addRow(new Object[]{"", "", "0", "0", "0", "0"}));
  50. add(btnAddA);
  51. JButton btnDelA = new JButton("A枪删除行");
  52. btnDelA.setFont(labelFont);
  53. btnDelA.setBounds(150, 316, 120, 32);
  54. btnDelA.addActionListener(e -> removeSelectedRow(tableA, tableModelA));
  55. add(btnDelA);
  56. addLabel("B枪参数区间", 20, 360, 200, 28, labelFont);
  57. tableModelB = createParamTableModel();
  58. tableB = createParamTable(tableModelB, 20, 388, CONTENT_WIDTH, 150);
  59. JButton btnAddB = new JButton("B枪添加行");
  60. btnAddB.setFont(labelFont);
  61. btnAddB.setBounds(20, 548, 120, 32);
  62. btnAddB.addActionListener(e -> tableModelB.addRow(new Object[]{"", "", "0", "0", "0", "0"}));
  63. add(btnAddB);
  64. JButton btnDelB = new JButton("B枪删除行");
  65. btnDelB.setFont(labelFont);
  66. btnDelB.setBounds(150, 548, 120, 32);
  67. btnDelB.addActionListener(e -> removeSelectedRow(tableB, tableModelB));
  68. add(btnDelB);
  69. JButton btnSave = new JButton("保存全部设置");
  70. btnSave.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  71. btnSave.setBounds(380, 596, 180, 45);
  72. btnSave.addActionListener(e -> saveAllConfig());
  73. add(btnSave);
  74. addLabel("每行表示「从起始颗到结束颗」使用同一套参数;开工时下发第1颗参数,每完成一颗后自动切换下一颗区间。",
  75. 20, 652, CONTENT_WIDTH, 40, hintFont);
  76. }
  77. private void addLabel(String text, int x, int y, int w, int h, Font font) {
  78. JLabel label = new JLabel("<html>" + text + "</html>");
  79. label.setFont(font);
  80. label.setBounds(x, y, w, h);
  81. add(label);
  82. }
  83. private JTextField addField(String text, int x, int y, int w, int h, Font font) {
  84. JTextField field = new JTextField(text);
  85. field.setFont(font);
  86. field.setBounds(x, y, w, h);
  87. add(field);
  88. return field;
  89. }
  90. private DefaultTableModel createParamTableModel() {
  91. return new DefaultTableModel(PARAM_COLUMNS, 0) {
  92. @Override
  93. public boolean isCellEditable(int row, int column) {
  94. return true;
  95. }
  96. };
  97. }
  98. private JTable createParamTable(DefaultTableModel model, int x, int y, int w, int h) {
  99. JTable table = new JTable(model);
  100. table.setRowHeight(28);
  101. table.setFont(new Font("微软雅黑", Font.PLAIN, 14));
  102. table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
  103. JScrollPane scrollPane = new JScrollPane(table);
  104. scrollPane.setBounds(x, y, w, h);
  105. add(scrollPane);
  106. return table;
  107. }
  108. private void removeSelectedRow(JTable table, DefaultTableModel model) {
  109. int row = table.getSelectedRow();
  110. if (row >= 0) {
  111. model.removeRow(row);
  112. }
  113. }
  114. private void loadConfigToUi() {
  115. txtASet.setText(String.valueOf(MesClient.aSetNum));
  116. txtBSet.setText(String.valueOf(MesClient.bSetNum));
  117. txtIpA.setText(MesClient.curIpA);
  118. txtIpB.setText(MesClient.curIpB);
  119. fillTableModel(tableModelA, JdbcUtils.getRivetParams("A"));
  120. fillTableModel(tableModelB, JdbcUtils.getRivetParams("B"));
  121. }
  122. // 重新从数据库加载到界面(进入设备管理页时调用)
  123. public void reloadFromDb() {
  124. Map<String, Object> config = JdbcUtils.getConfig();
  125. if (config.size() > 0) {
  126. MesClient.aSetNum = (Short) config.get("a_set_num");
  127. MesClient.bSetNum = (Short) config.get("b_set_num");
  128. MesClient.curIpA = (String) config.get("plc_ip_a");
  129. MesClient.curIpB = (String) config.get("plc_ip_b");
  130. }
  131. MesClient.loadRivetParamConfig();
  132. loadConfigToUi();
  133. }
  134. private void fillTableModel(DefaultTableModel model, List<RivetParamRange> ranges) {
  135. model.setRowCount(0);
  136. for (RivetParamRange range : ranges) {
  137. model.addRow(new Object[]{
  138. String.valueOf(range.getStartRivet()),
  139. String.valueOf(range.getEndRivet()),
  140. String.valueOf(range.getFMin()),
  141. String.valueOf(range.getFMax()),
  142. String.valueOf(range.getSMin()),
  143. String.valueOf(range.getSMax())
  144. });
  145. }
  146. }
  147. private List<RivetParamRange> parseTableModel(DefaultTableModel model, String gunType) throws IllegalArgumentException {
  148. List<RivetParamRange> ranges = new ArrayList<>();
  149. for (int i = 0; i < model.getRowCount(); i++) {
  150. String startText = String.valueOf(model.getValueAt(i, 0)).trim();
  151. String endText = String.valueOf(model.getValueAt(i, 1)).trim();
  152. if (startText.isEmpty() && endText.isEmpty()) {
  153. continue;
  154. }
  155. if (startText.isEmpty() || endText.isEmpty()) {
  156. throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行起始颗/结束颗不能为空");
  157. }
  158. int start = Integer.parseInt(startText);
  159. int end = Integer.parseInt(endText);
  160. if (start < 1 || end < start) {
  161. throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行颗数范围无效");
  162. }
  163. int fMin = parseIntCell(model, i, 2);
  164. int fMax = parseIntCell(model, i, 3);
  165. int sMin = parseIntCell(model, i, 4);
  166. int sMax = parseIntCell(model, i, 5);
  167. if (fMin > fMax) {
  168. throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行 F-min 不能大于 F-max");
  169. }
  170. if (sMin > sMax) {
  171. throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行 S-min 不能大于 S-max");
  172. }
  173. // PLC Int16 写入上限,避免溢出后下发错误值
  174. if (fMin < Short.MIN_VALUE || fMax > Short.MAX_VALUE
  175. || sMin < Short.MIN_VALUE || sMax > Short.MAX_VALUE) {
  176. throw new IllegalArgumentException(gunType + "枪第" + (i + 1) + "行参数超出范围(-32768~32767)");
  177. }
  178. RivetParamRange range = new RivetParamRange();
  179. range.setGunType(gunType);
  180. range.setStartRivet(start);
  181. range.setEndRivet(end);
  182. range.setFMin(fMin);
  183. range.setFMax(fMax);
  184. range.setSMin(sMin);
  185. range.setSMax(sMax);
  186. ranges.add(range);
  187. }
  188. if (ranges.isEmpty()) {
  189. throw new IllegalArgumentException(gunType + "枪至少配置一行参数区间");
  190. }
  191. // 检查区间是否重叠,重叠会导致下发时命中错误行
  192. for (int i = 0; i < ranges.size(); i++) {
  193. RivetParamRange a = ranges.get(i);
  194. for (int j = i + 1; j < ranges.size(); j++) {
  195. RivetParamRange b = ranges.get(j);
  196. if (a.getStartRivet() <= b.getEndRivet() && b.getStartRivet() <= a.getEndRivet()) {
  197. throw new IllegalArgumentException(gunType + "枪参数区间存在重叠:"
  198. + a.getStartRivet() + "-" + a.getEndRivet()
  199. + " 与 " + b.getStartRivet() + "-" + b.getEndRivet());
  200. }
  201. }
  202. }
  203. return ranges;
  204. }
  205. private int parseIntCell(DefaultTableModel model, int row, int col) {
  206. String text = String.valueOf(model.getValueAt(row, col)).trim();
  207. if (text.isEmpty()) {
  208. return 0;
  209. }
  210. return Integer.parseInt(text);
  211. }
  212. // 提交表格编辑并将参数下发到PLC
  213. private boolean pushRivetParamsToDevice(ModbusTcp plc, List<RivetParamRange> ranges, String gunType) {
  214. if (plc == null) {
  215. System.out.println(gunType + "枪PLC未连接,跳过参数下发");
  216. return false;
  217. }
  218. try {
  219. ModbusUtil.syncRivetParamsToPlc(plc, ranges);
  220. return true;
  221. } catch (Exception ex) {
  222. ex.printStackTrace();
  223. return false;
  224. }
  225. }
  226. private void saveAllConfig() {
  227. // 保存前先结束表格编辑,确保最新输入写入模型
  228. if (tableA != null && tableA.isEditing()) {
  229. tableA.getCellEditor().stopCellEditing();
  230. }
  231. if (tableB != null && tableB.isEditing()) {
  232. tableB.getCellEditor().stopCellEditing();
  233. }
  234. try {
  235. short newASet = Short.parseShort(txtASet.getText().trim());
  236. short newBSet = Short.parseShort(txtBSet.getText().trim());
  237. String newIpA = txtIpA.getText().trim();
  238. String newIpB = txtIpB.getText().trim();
  239. if (newIpA.isEmpty() || newIpB.isEmpty()) {
  240. JOptionPane.showMessageDialog(MesClient.mesClientFrame, "IP地址不能为空", "错误", JOptionPane.ERROR_MESSAGE);
  241. return;
  242. }
  243. List<RivetParamRange> rangesA = parseTableModel(tableModelA, "A");
  244. List<RivetParamRange> rangesB = parseTableModel(tableModelB, "B");
  245. boolean configOk = JdbcUtils.updateConfig(newASet, newBSet, newIpA, newIpB);
  246. boolean saveAOk = JdbcUtils.saveRivetParams("A", rangesA);
  247. boolean saveBOk = JdbcUtils.saveRivetParams("B", rangesB);
  248. if (!configOk || !saveAOk || !saveBOk) {
  249. JOptionPane.showMessageDialog(MesClient.mesClientFrame,
  250. "配置保存失败,请查看控制台日志后重试(常见原因:数据库被占用)",
  251. "错误", JOptionPane.ERROR_MESSAGE);
  252. // 回读数据库,避免界面显示未真正落库的值
  253. reloadFromDb();
  254. return;
  255. }
  256. MesClient.aSetNum = newASet;
  257. MesClient.bSetNum = newBSet;
  258. MesClient.rivetParamRangesA = rangesA;
  259. MesClient.rivetParamRangesB = rangesB;
  260. if (MesClient.param1 != null) {
  261. MesClient.param1.setText(String.valueOf(MesClient.aSetNum));
  262. }
  263. if (MesClient.param3 != null) {
  264. MesClient.param3.setText(String.valueOf(MesClient.bSetNum));
  265. }
  266. boolean ipAChanged = !newIpA.equals(MesClient.curIpA);
  267. boolean ipBChanged = !newIpB.equals(MesClient.curIpB);
  268. if (ipAChanged) {
  269. if (MesClient.plcA != null) {
  270. MesClient.plcA.close();
  271. }
  272. MesClient.curIpA = newIpA;
  273. MesClient.plcA = new ModbusTcp(1, MesClient.curIpA);
  274. }
  275. if (ipBChanged) {
  276. if (MesClient.plcB != null) {
  277. MesClient.plcB.close();
  278. }
  279. MesClient.curIpB = newIpB;
  280. MesClient.plcB = new ModbusTcp(1, MesClient.curIpB);
  281. }
  282. // 保存后立即将拉力/行程参数下发到设备
  283. boolean syncA = pushRivetParamsToDevice(MesClient.plcA, rangesA, "A");
  284. boolean syncB = pushRivetParamsToDevice(MesClient.plcB, rangesB, "B");
  285. if (syncA && syncB) {
  286. JOptionPane.showMessageDialog(MesClient.mesClientFrame, "配置已保存,并已下发到设备", "提示", JOptionPane.INFORMATION_MESSAGE);
  287. } else {
  288. JOptionPane.showMessageDialog(MesClient.mesClientFrame,
  289. "配置已保存到本地,但部分设备参数下发失败,请检查设备连接;下次开工时会按新配置重新下发",
  290. "提示", JOptionPane.WARNING_MESSAGE);
  291. }
  292. } catch (NumberFormatException ex) {
  293. JOptionPane.showMessageDialog(MesClient.mesClientFrame, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE);
  294. } catch (IllegalArgumentException ex) {
  295. JOptionPane.showMessageDialog(MesClient.mesClientFrame, ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
  296. }
  297. }
  298. }