|
@@ -2,6 +2,7 @@ package com.mes.ui;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.mes.component.MesWebView;
|
|
import com.mes.component.MesWebView;
|
|
|
|
|
+import com.mes.device.DeviceStateReporter;
|
|
|
import com.mes.util.ConfigUtils;
|
|
import com.mes.util.ConfigUtils;
|
|
|
import com.mes.util.JdbcUtils;
|
|
import com.mes.util.JdbcUtils;
|
|
|
import com.mes.util.StationConfig;
|
|
import com.mes.util.StationConfig;
|
|
@@ -16,12 +17,22 @@ import java.io.IOException;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Properties;
|
|
import java.util.Properties;
|
|
|
|
|
+import java.util.Timer;
|
|
|
|
|
+import java.util.TimerTask;
|
|
|
|
|
|
|
|
import static com.mes.ui.WorkStationPanel.*;
|
|
import static com.mes.ui.WorkStationPanel.*;
|
|
|
|
|
|
|
|
public class MultiStationClient extends JFrame {
|
|
public class MultiStationClient extends JFrame {
|
|
|
private static final Logger log = LoggerFactory.getLogger(MultiStationClient.class);
|
|
private static final Logger log = LoggerFactory.getLogger(MultiStationClient.class);
|
|
|
|
|
+ private static final int DEFAULT_DEVICE_HEARTBEAT_MINUTES = 5;
|
|
|
|
|
+ private static int deviceHeartbeatMinutes = DEFAULT_DEVICE_HEARTBEAT_MINUTES;
|
|
|
|
|
+
|
|
|
private List<WorkStationPanel> stationPanels = new ArrayList<>();
|
|
private List<WorkStationPanel> stationPanels = new ArrayList<>();
|
|
|
|
|
+ // 当前工作站配置中的所有工位,用于开机、心跳、停机逐个上报。
|
|
|
|
|
+ private List<StationConfig> stationConfigs = new ArrayList<>();
|
|
|
|
|
+ private Timer deviceReportTimer;
|
|
|
|
|
+ // 避免窗口关闭和退出菜单连续触发时重复上报停机。
|
|
|
|
|
+ private boolean deviceStopReported = true;
|
|
|
|
|
|
|
|
private JButton userMenu;
|
|
private JButton userMenu;
|
|
|
private JLabel statusLabel;
|
|
private JLabel statusLabel;
|
|
@@ -49,6 +60,8 @@ public class MultiStationClient extends JFrame {
|
|
|
try {
|
|
try {
|
|
|
MesClient.readProperty();
|
|
MesClient.readProperty();
|
|
|
Properties props = ConfigUtils.loadProperties();
|
|
Properties props = ConfigUtils.loadProperties();
|
|
|
|
|
+ // 设备运行心跳间隔可通过 mes.device.heartbeat.minutes 配置,未配置默认 5 分钟。
|
|
|
|
|
+ deviceHeartbeatMinutes = parseDeviceHeartbeatMinutes(props);
|
|
|
log.info("client config={}, stations={}, proxyEnabled={}, proxyBaseUrl={}",
|
|
log.info("client config={}, stations={}, proxyEnabled={}, proxyBaseUrl={}",
|
|
|
ConfigUtils.getConfigPath(),
|
|
ConfigUtils.getConfigPath(),
|
|
|
props.getProperty("mes.stations", ""),
|
|
props.getProperty("mes.stations", ""),
|
|
@@ -83,6 +96,8 @@ public class MultiStationClient extends JFrame {
|
|
|
int choice = JOptionPane.showConfirmDialog(null, "确定要关闭窗口吗?", "关闭确认",
|
|
int choice = JOptionPane.showConfirmDialog(null, "确定要关闭窗口吗?", "关闭确认",
|
|
|
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
|
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
|
|
if (choice == JOptionPane.YES_OPTION) {
|
|
if (choice == JOptionPane.YES_OPTION) {
|
|
|
|
|
+ // 关闭窗口视为设备停机,先同步上报所有工位再退出。
|
|
|
|
|
+ reportDeviceStopped();
|
|
|
dispose();
|
|
dispose();
|
|
|
System.exit(0);
|
|
System.exit(0);
|
|
|
}
|
|
}
|
|
@@ -106,6 +121,8 @@ public class MultiStationClient extends JFrame {
|
|
|
int choice = JOptionPane.showConfirmDialog(null, "确定要退出吗?", "退出确认",
|
|
int choice = JOptionPane.showConfirmDialog(null, "确定要退出吗?", "退出确认",
|
|
|
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
|
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
|
|
if (choice == JOptionPane.YES_OPTION) {
|
|
if (choice == JOptionPane.YES_OPTION) {
|
|
|
|
|
+ // 菜单退出同样视为设备停机。
|
|
|
|
|
+ reportDeviceStopped();
|
|
|
System.exit(0);
|
|
System.exit(0);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
@@ -156,6 +173,8 @@ public class MultiStationClient extends JFrame {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
List<StationConfig> stations = StationConfig.loadAllStations();
|
|
List<StationConfig> stations = StationConfig.loadAllStations();
|
|
|
|
|
+ // 保存配置中的全部工位,状态上报时按列表逐个提交。
|
|
|
|
|
+ stationConfigs = new ArrayList<>(stations);
|
|
|
if (stations.isEmpty()) {
|
|
if (stations.isEmpty()) {
|
|
|
JOptionPane.showMessageDialog(this, "未配置任何工位", "错误", JOptionPane.ERROR_MESSAGE);
|
|
JOptionPane.showMessageDialog(this, "未配置任何工位", "错误", JOptionPane.ERROR_MESSAGE);
|
|
|
return;
|
|
return;
|
|
@@ -200,6 +219,55 @@ public class MultiStationClient extends JFrame {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public synchronized void reportDeviceRunning() {
|
|
|
|
|
+ deviceStopReported = false;
|
|
|
|
|
+ // 登录成功后上报开机/运行,当前工作站内每个工位都提交一次。
|
|
|
|
|
+ DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "工位登录,开始运行");
|
|
|
|
|
+ startDeviceReportTimer();
|
|
|
|
|
+ log.info("device running report submitted for {} stations", stationConfigs.size());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public synchronized void reportDeviceStopped() {
|
|
|
|
|
+ stopDeviceReportTimer();
|
|
|
|
|
+ if (deviceStopReported) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 退出程序前同步上报停机,降低异步线程未执行完就退出的风险。
|
|
|
|
|
+ int successCount = DeviceStateReporter.reportStationsSync(stationConfigs, DeviceStateReporter.STATE_STOP, "工位退出,停止运行");
|
|
|
|
|
+ deviceStopReported = true;
|
|
|
|
|
+ log.info("device stop report submitted for {} stations, success={}", stationConfigs.size(), successCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void startDeviceReportTimer() {
|
|
|
|
|
+ stopDeviceReportTimer();
|
|
|
|
|
+ long intervalMs = deviceHeartbeatMinutes * 60L * 1000L;
|
|
|
|
|
+ deviceReportTimer = new Timer("device-report-timer", true);
|
|
|
|
|
+ deviceReportTimer.schedule(new TimerTask() {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ // 定时补报运行状态,保持 MES 侧设备在线状态。
|
|
|
|
|
+ DeviceStateReporter.reportStationsAsync(stationConfigs, DeviceStateReporter.STATE_RUNNING, "运行心跳");
|
|
|
|
|
+ }
|
|
|
|
|
+ }, intervalMs, intervalMs);
|
|
|
|
|
+ log.info("device heartbeat timer started, intervalMinutes={}", deviceHeartbeatMinutes);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void stopDeviceReportTimer() {
|
|
|
|
|
+ if (deviceReportTimer != null) {
|
|
|
|
|
+ deviceReportTimer.cancel();
|
|
|
|
|
+ deviceReportTimer = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int parseDeviceHeartbeatMinutes(Properties props) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ int minutes = Integer.parseInt(props.getProperty("mes.device.heartbeat.minutes", String.valueOf(DEFAULT_DEVICE_HEARTBEAT_MINUTES)).trim());
|
|
|
|
|
+ return minutes > 0 ? minutes : DEFAULT_DEVICE_HEARTBEAT_MINUTES;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return DEFAULT_DEVICE_HEARTBEAT_MINUTES;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void addResetMenuItem(JMenu settingMenu, WorkStationPanel panel, StationConfig config) {
|
|
private void addResetMenuItem(JMenu settingMenu, WorkStationPanel panel, StationConfig config) {
|
|
|
JMenuItem resetMenu = new JMenuItem("刷新" + config.getGw() + "工件");
|
|
JMenuItem resetMenu = new JMenuItem("刷新" + config.getGw() + "工件");
|
|
|
resetMenu.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")));
|
|
resetMenu.setIcon(new ImageIcon(MesClient.class.getResource("/bg/reset_logo.png")));
|