MultiStationClient.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package com.mes.ui;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.mes.component.MesWebView;
  4. import com.mes.device.DeviceStateReporter;
  5. import com.mes.util.ConfigUtils;
  6. import com.mes.util.JdbcUtils;
  7. import com.mes.util.StationConfig;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import javax.swing.*;
  11. import java.awt.*;
  12. import java.awt.event.WindowAdapter;
  13. import java.awt.event.WindowEvent;
  14. import java.io.IOException;
  15. import java.util.ArrayList;
  16. import java.util.List;
  17. import java.util.Properties;
  18. import java.util.Timer;
  19. import java.util.TimerTask;
  20. import static com.mes.ui.WorkStationPanel.*;
  21. public class MultiStationClient extends JFrame {
  22. private static final Logger log = LoggerFactory.getLogger(MultiStationClient.class);
  23. private static final int DEFAULT_DEVICE_HEARTBEAT_MINUTES = 5;
  24. private static int deviceHeartbeatMinutes = DEFAULT_DEVICE_HEARTBEAT_MINUTES;
  25. private List<WorkStationPanel> stationPanels = new ArrayList<>();
  26. // 当前工作站配置中的所有工位,用于开机、心跳、停机逐个上报。
  27. private List<StationConfig> stationConfigs = new ArrayList<>();
  28. private Timer deviceReportTimer;
  29. // 避免窗口关闭和退出菜单连续触发时重复上报停机。
  30. private boolean deviceStopReported = true;
  31. private JButton userMenu;
  32. private JLabel statusLabel;
  33. private JLabel heartBeatLabel;
  34. public static JFrame welcomeWin;
  35. public static MultiStationClient mainFrame;
  36. public static JScrollPane searchScrollPaneWork; // 已弃用,改为使用WebView
  37. public static JScrollPane searchScrollPaneCheck;
  38. public static JPanel indexPaneWork; // 已弃用,改为使用WebView
  39. public static JPanel indexPaneCheck;
  40. public static MesWebView workRecordPanel = null;
  41. public static MesWebView checkPanel = null;
  42. public static void main(String[] args) {
  43. /* if (LockUtil.getInstance().isAppActive()) {
  44. JOptionPane.showMessageDialog(null, "已有一个程序在运行,程序退出");
  45. return;
  46. }*/
  47. EventQueue.invokeLater(() -> {
  48. try {
  49. MesClient.readProperty();
  50. Properties props = ConfigUtils.loadProperties();
  51. // 设备运行心跳间隔可通过 mes.device.heartbeat.minutes 配置,未配置默认 5 分钟。
  52. deviceHeartbeatMinutes = parseDeviceHeartbeatMinutes(props);
  53. log.info("client config={}, stations={}, proxyEnabled={}, proxyBaseUrl={}",
  54. ConfigUtils.getConfigPath(),
  55. props.getProperty("mes.stations", ""),
  56. props.getProperty("plc.proxy.enabled", "false"),
  57. props.getProperty("plc.proxy.base_url", ""));
  58. System.out.println("client config=" + ConfigUtils.getConfigPath()
  59. + ", stations=" + props.getProperty("mes.stations", "")
  60. + ", proxy=" + props.getProperty("plc.proxy.base_url", ""));
  61. JdbcUtils.getConn();
  62. welcomeWin = new LoginFarme();
  63. welcomeWin.setVisible(true);
  64. MultiStationClient frame = new MultiStationClient();
  65. frame.setVisible(false);
  66. } catch (Exception e) {
  67. e.printStackTrace();
  68. JOptionPane.showMessageDialog(null, "启动失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
  69. }
  70. });
  71. }
  72. public MultiStationClient() {
  73. mainFrame = this;
  74. initUI();
  75. }
  76. private void initUI() {
  77. setTitle("MES系统客户端 - 多工位模式");
  78. setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
  79. addWindowListener(new WindowAdapter() {
  80. @Override
  81. public void windowClosing(WindowEvent e) {
  82. int choice = JOptionPane.showConfirmDialog(null, "确定要关闭窗口吗?", "关闭确认",
  83. JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
  84. if (choice == JOptionPane.YES_OPTION) {
  85. // 关闭窗口视为设备停机,先同步上报所有工位再退出。
  86. reportDeviceStopped();
  87. dispose();
  88. System.exit(0);
  89. }
  90. }
  91. });
  92. setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/bg/logo.png")));
  93. JMenuBar menuBar = new JMenuBar();
  94. setJMenuBar(menuBar);
  95. JMenu fileMenu = new JMenu("用户");
  96. fileMenu.setIcon(new ImageIcon(getClass().getResource("/bg/user.png")));
  97. fileMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  98. menuBar.add(fileMenu);
  99. JMenuItem exitMenuItem = new JMenuItem("退出");
  100. exitMenuItem.setIcon(new ImageIcon(getClass().getResource("/bg/logoff.png")));
  101. exitMenuItem.setFont(new Font("微软雅黑", Font.PLAIN, 18));
  102. exitMenuItem.addActionListener(e -> {
  103. int choice = JOptionPane.showConfirmDialog(null, "确定要退出吗?", "退出确认",
  104. JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
  105. if (choice == JOptionPane.YES_OPTION) {
  106. // 菜单退出同样视为设备停机。
  107. reportDeviceStopped();
  108. System.exit(0);
  109. }
  110. });
  111. fileMenu.add(exitMenuItem);
  112. JMenu settingMenu = new JMenu("设置");
  113. settingMenu.setIcon(new ImageIcon(getClass().getResource("/bg/menu_setting.png")));
  114. settingMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  115. menuBar.add(settingMenu);
  116. JToolBar toolBar = new JToolBar();
  117. toolBar.setFloatable(false);
  118. JLabel statusLabelTitle = new JLabel("状态:");
  119. statusLabelTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24));
  120. toolBar.add(statusLabelTitle);
  121. statusLabel = new JLabel("已连接MES服务器");
  122. statusLabel.setForeground(Color.GREEN);
  123. statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 24));
  124. statusLabel.setBackground(Color.BLACK);
  125. toolBar.add(statusLabel);
  126. toolBar.add(Box.createHorizontalStrut(20));
  127. JLabel heartBeatTitle = new JLabel("心跳:");
  128. heartBeatTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24));
  129. toolBar.add(heartBeatTitle);
  130. heartBeatLabel = new JLabel(new ImageIcon(getClass().getResource("/bg/green_dot.png")));
  131. toolBar.add(heartBeatLabel);
  132. toolBar.add(Box.createHorizontalStrut(20));
  133. JLabel userTitle = new JLabel("登录用户:");
  134. userTitle.setFont(new Font("微软雅黑", Font.PLAIN, 24));
  135. toolBar.add(userTitle);
  136. userMenu = new JButton("未知用户");
  137. userMenu.setForeground(Color.GREEN);
  138. userMenu.setFont(new Font("微软雅黑", Font.PLAIN, 24));
  139. userMenu.setBackground(Color.BLACK);
  140. toolBar.add(userMenu);
  141. add(toolBar, BorderLayout.NORTH);
  142. JTabbedPane tabbedPane = new JTabbedPane();
  143. tabbedPane.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  144. try {
  145. List<StationConfig> stations = StationConfig.loadAllStations();
  146. // 保存配置中的全部工位,状态上报时按列表逐个提交。
  147. stationConfigs = new ArrayList<>(stations);
  148. if (stations.isEmpty()) {
  149. JOptionPane.showMessageDialog(this, "未配置任何工位", "错误", JOptionPane.ERROR_MESSAGE);
  150. return;
  151. }
  152. JPanel mainPanel = new JPanel(new GridLayout(2, 3, 1, 1));
  153. mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
  154. mainPanel.setBackground(Color.WHITE);
  155. for (StationConfig config : stations) {
  156. WorkStationPanel panel = new WorkStationPanel(config);
  157. stationPanels.add(panel);
  158. mainPanel.add(panel);
  159. addResetMenuItem(settingMenu, panel, config);
  160. }
  161. tabbedPane.addTab("工作面板",new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), new JScrollPane(mainPanel));
  162. indexPaneCheck = new JPanel();
  163. searchScrollPaneCheck = new JScrollPane(indexPaneCheck);
  164. indexPaneCheck.setLayout(null);
  165. // tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")),searchScrollPaneCheck);
  166. indexPaneWork = new JPanel();
  167. searchScrollPaneWork = new JScrollPane(indexPaneWork);
  168. // tabbedPane.addTab("生产记录",new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")),searchScrollPaneWork);
  169. add(tabbedPane, BorderLayout.CENTER);
  170. } catch (IOException e) {
  171. JOptionPane.showMessageDialog(this, "加载工位配置失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
  172. }
  173. pack();
  174. setExtendedState(JFrame.MAXIMIZED_BOTH);
  175. }
  176. public void setUser(String user) {
  177. userMenu.setText(user);
  178. if (stationPanels != null) {
  179. for (WorkStationPanel panel : stationPanels) {
  180. panel.setCurrentUser(user);
  181. }
  182. }
  183. }
  184. public synchronized void reportDeviceRunning() {
  185. deviceStopReported = false;
  186. // 登录成功后上报开机/运行,当前工作站内每个工位都提交一次。
  187. DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "工位登录,开始运行");
  188. startDeviceReportTimer();
  189. log.info("device running report submitted for {} stations", stationConfigs.size());
  190. }
  191. public synchronized void reportDeviceStopped() {
  192. stopDeviceReportTimer();
  193. if (deviceStopReported) {
  194. return;
  195. }
  196. // 退出程序前同步上报停机,降低异步线程未执行完就退出的风险。
  197. int successCount = DeviceStateReporter.reportStationsSync(stationConfigs, DeviceStateReporter.STATE_STOP, "工位退出,停止运行");
  198. deviceStopReported = true;
  199. log.info("device stop report submitted for {} stations, success={}", stationConfigs.size(), successCount);
  200. }
  201. private void startDeviceReportTimer() {
  202. stopDeviceReportTimer();
  203. long intervalMs = deviceHeartbeatMinutes * 60L * 1000L;
  204. deviceReportTimer = new Timer("device-report-timer", true);
  205. deviceReportTimer.schedule(new TimerTask() {
  206. @Override
  207. public void run() {
  208. // 定时补报运行状态,保持 MES 侧设备在线状态。
  209. DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "运行心跳");
  210. }
  211. }, intervalMs, intervalMs);
  212. log.info("device heartbeat timer started, intervalMinutes={}", deviceHeartbeatMinutes);
  213. }
  214. private void stopDeviceReportTimer() {
  215. if (deviceReportTimer != null) {
  216. deviceReportTimer.cancel();
  217. deviceReportTimer = null;
  218. }
  219. }
  220. private static int parseDeviceHeartbeatMinutes(Properties props) {
  221. try {
  222. int minutes = Integer.parseInt(props.getProperty("mes.device.heartbeat.minutes", String.valueOf(DEFAULT_DEVICE_HEARTBEAT_MINUTES)).trim());
  223. return minutes > 0 ? minutes : DEFAULT_DEVICE_HEARTBEAT_MINUTES;
  224. } catch (Exception e) {
  225. return DEFAULT_DEVICE_HEARTBEAT_MINUTES;
  226. }
  227. }
  228. private void addResetMenuItem(JMenu settingMenu, WorkStationPanel panel, StationConfig config) {
  229. JMenuItem resetMenu = new JMenuItem("刷新" + config.getGw() + "工件");
  230. resetMenu.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")));
  231. resetMenu.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  232. resetMenu.addActionListener(e -> panel.resetScanA());
  233. settingMenu.add(resetMenu);
  234. }
  235. }