hou 1 неделя назад
Родитель
Сommit
f62f2905ae

+ 3 - 0
src/META-INF/MANIFEST.MF

@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.mes.ui.MesClient
+

+ 43 - 0
src/com/mes/ui/DataUtil.java

@@ -3,6 +3,7 @@ package com.mes.ui;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.alibaba.fastjson2.JSONObject;
 import com.mes.util.JdbcUtils;
 import com.mes.util.JdbcUtils;
+import com.mes.util.Ut5310TestResult;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 
 
@@ -100,8 +101,17 @@ public class DataUtil {
                     + "&deviceType=UT5310"
                     + "&deviceType=UT5310"
                     + "&deviceResult=" + encode(deviceResult)
                     + "&deviceResult=" + encode(deviceResult)
                     + "&deviceData=" + encode(deviceData);
                     + "&deviceData=" + encode(deviceData);
+            System.out.println("========== MES 上传(UT5310) ==========");
+            System.out.println("URL: " + url);
+            System.out.println("oprno=" + oprno + ", lineSn=" + lineSn + ", sn=" + sn
+                    + ", result=" + ret + ", ucode=" + user);
+            System.out.println("deviceType=UT5310, deviceResult=" + deviceResult);
+            System.out.println("deviceData(原始报文)=" + deviceData);
+            System.out.println("POST body=" + params);
             log.info("params=" + params);
             log.info("params=" + params);
             String result = doPost(url, params);
             String result = doPost(url, params);
+            System.out.println("MES 响应: " + result);
+            System.out.println("====================================");
             log.info("result=" + result);
             log.info("result=" + result);
 
 
             if (result == null || result.trim().isEmpty() || result.equalsIgnoreCase("false")) {
             if (result == null || result.trim().isEmpty() || result.equalsIgnoreCase("false")) {
@@ -116,6 +126,39 @@ public class DataUtil {
         }
         }
     }
     }
 
 
+    /** 上传 UT5310 绝缘耐压分步明细到 mes_product_insulation2 */
+    public static JSONObject uploadInsulation2(String sn, String user, Ut5310TestResult testResult) {
+        try {
+            if (testResult == null || testResult.getSteps().isEmpty()) {
+                log.info("uploadInsulation2 skipped: no step data");
+                return null;
+            }
+            String mes_server_ip = MesClient.mes_server_ip;
+            String url = "http://" + mes_server_ip + ":8980/js/a/mes/mesProductInsulation2/batchUpload";
+            String stepsJson = testResult.toUploadStepsArray().toJSONString();
+            String params = "__ajax=json&sn=" + encode(sn)
+                    + "&ucode=" + encode(user)
+                    + "&steps=" + encode(stepsJson);
+            System.out.println("========== MES 上传(绝缘耐压明细) ==========");
+            System.out.println("URL: " + url);
+            System.out.println("sn=" + sn + ", ucode=" + user);
+            System.out.println("steps=" + stepsJson);
+            log.info("uploadInsulation2 params=" + params);
+            String result = doPost(url, params);
+            System.out.println("MES 响应: " + result);
+            System.out.println("==========================================");
+            log.info("uploadInsulation2 result=" + result);
+
+            if (result == null || result.trim().isEmpty() || result.equalsIgnoreCase("false")) {
+                return null;
+            }
+            return JSONObject.parseObject(result);
+        } catch (Exception e) {
+            log.info("uploadInsulation2 e=" + e.getMessage());
+            return null;
+        }
+    }
+
     private static String encode(String value) throws UnsupportedEncodingException {
     private static String encode(String value) throws UnsupportedEncodingException {
         return URLEncoder.encode(value == null ? "" : value, "UTF-8");
         return URLEncoder.encode(value == null ? "" : value, "UTF-8");
     }
     }

+ 1 - 3
src/com/mes/ui/LoginFarme.java

@@ -198,9 +198,7 @@ public class LoginFarme extends JFrame {
 					if (MesClient.jfxPanel2 == null) {
 					if (MesClient.jfxPanel2 == null) {
 						String url = "http://" + MesClient.mes_server_ip + ":8980/js/a/mes/mesProcessCheckRecord/ulist?ucode=" + user_id + "&oprno=" + MesClient.mes_gw + "&lineSn=" + MesClient.mes_line_sn;
 						String url = "http://" + MesClient.mes_server_ip + ":8980/js/a/mes/mesProcessCheckRecord/ulist?ucode=" + user_id + "&oprno=" + MesClient.mes_gw + "&lineSn=" + MesClient.mes_line_sn;
 						MesClient.jfxPanel2 = new MesWebView(url);
 						MesClient.jfxPanel2 = new MesWebView(url);
-						MesClient.jfxPanel2.setSize(990, 550);
-						MesClient.jfxPanel2.setBounds(0, 0, 990, 550);
-						MesClient.indexPanelC.add(MesClient.jfxPanel2);
+						MesClient.indexPanelC.add(MesClient.jfxPanel2, BorderLayout.CENTER);
 					}
 					}
 
 
                     MesClient.getMaterailData();
                     MesClient.getMaterailData();

+ 151 - 12
src/com/mes/ui/MesClient.java

@@ -3,6 +3,7 @@ package com.mes.ui;
 import com.alibaba.fastjson2.JSONObject;
 import com.alibaba.fastjson2.JSONObject;
 import com.mes.component.MesRadio;
 import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.component.MesWebView;
+import com.fazecast.jSerialComm.SerialPort;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.DateLocalUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.HttpUtils;
 import com.mes.util.JdbcUtils;
 import com.mes.util.JdbcUtils;
@@ -75,6 +76,8 @@ public class MesClient extends JFrame {
     private JTextField softwareIpField;
     private JTextField softwareIpField;
     private JTextField softwareOpField;
     private JTextField softwareOpField;
     private JTextField softwareOpNameField;
     private JTextField softwareOpNameField;
+    private static final String APP_CFG_UT5310_PORT = "ut5310_port";
+    private JComboBox<String> softwarePortCombo;
     private JButton softwareEditBtn;
     private JButton softwareEditBtn;
     private JButton softwareSaveBtn;
     private JButton softwareSaveBtn;
     // 记录上一次非“软件设置”页签,用于密码校验失败时回退
     // 记录上一次非“软件设置”页签,用于密码校验失败时回退
@@ -164,14 +167,19 @@ public class MesClient extends JFrame {
         String plcIp = pro.getProperty("mes.plc_ip");
         String plcIp = pro.getProperty("mes.plc_ip");
         mes_ut5310_enable = Boolean.parseBoolean(pro.getProperty("mes.ut5310_enable", "false"));
         mes_ut5310_enable = Boolean.parseBoolean(pro.getProperty("mes.ut5310_enable", "false"));
         Ut5310ScpiService.configure(
         Ut5310ScpiService.configure(
-                pro.getProperty("mes.ut5310_port", "COM1"),
+                pro.getProperty("mes.ut5310_port", "COM3"),
                 getIntProperty(pro, "mes.ut5310_baud_rate", 9600),
                 getIntProperty(pro, "mes.ut5310_baud_rate", 9600),
                 getIntProperty(pro, "mes.ut5310_data_bits", 8),
                 getIntProperty(pro, "mes.ut5310_data_bits", 8),
                 getIntProperty(pro, "mes.ut5310_stop_bits", 1),
                 getIntProperty(pro, "mes.ut5310_stop_bits", 1),
                 pro.getProperty("mes.ut5310_parity", "NONE"),
                 pro.getProperty("mes.ut5310_parity", "NONE"),
-                getIntProperty(pro, "mes.ut5310_read_timeout_ms", 1000),
-                getIntProperty(pro, "mes.ut5310_test_timeout_ms", 60000),
-                getIntProperty(pro, "mes.ut5310_fetch_interval_ms", 500));
+                getIntProperty(pro, "mes.ut5310_read_timeout_ms", 2000),
+                getIntProperty(pro, "mes.ut5310_test_timeout_ms", 300000),
+                getIntProperty(pro, "mes.ut5310_fetch_interval_ms", 500),
+                pro.getProperty("mes.ut5310_protocol", "MODBUS"),
+                getIntProperty(pro, "mes.ut5310_modbus_slave", 1),
+                getIntProperty(pro, "mes.ut5310_modbus_max_steps", 4),
+                Boolean.parseBoolean(pro.getProperty("mes.ut5310_modbus_send_start", "false")),
+                getIntProperty(pro, "mes.ut5310_modbus_expected_steps", 2));
 
 
         if (mes_gw == null) {
         if (mes_gw == null) {
             mes_gw = "";
             mes_gw = "";
@@ -184,6 +192,13 @@ public class MesClient extends JFrame {
             PlcService.setPlcIp(mes_plc_ip);
             PlcService.setPlcIp(mes_plc_ip);
         }
         }
 
 
+        // 页面保存的串口优先于 properties 默认值
+        String portFromDb = JdbcUtils.getAppConfig(APP_CFG_UT5310_PORT);
+        if (portFromDb != null && !portFromDb.isEmpty()) {
+            Ut5310ScpiService.setPortName(portFromDb);
+            log.info("UT5310 串口使用本地配置: {}", portFromDb);
+        }
+
         String ipFromDb = JdbcUtils.getServerIpByOp(mes_gw);
         String ipFromDb = JdbcUtils.getServerIpByOp(mes_gw);
         if (ipFromDb != null && !ipFromDb.trim().isEmpty()) {
         if (ipFromDb != null && !ipFromDb.trim().isEmpty()) {
             mes_server_ip = ipFromDb.trim();
             mes_server_ip = ipFromDb.trim();
@@ -697,6 +712,22 @@ public class MesClient extends JFrame {
             SwingUtilities.invokeLater(() -> setMenuStatus("当前状态不允许提交绝缘耐压结果", -1));
             SwingUtilities.invokeLater(() -> setMenuStatus("当前状态不允许提交绝缘耐压结果", -1));
             return false;
             return false;
         }
         }
+
+        if (testResult != null && "ERROR".equalsIgnoreCase(testResult.getJudge())) {
+            plcPhase = PLC_PHASE_WAIT_REQUEST;
+            SwingUtilities.invokeLater(() -> {
+                setMenuStatus("UT5310通信失败: " + testResult.getRawResponse(), -1);
+                finish_ok_bt.setEnabled(true);
+                finish_ng_bt.setEnabled(true);
+            });
+            return false;
+        }
+        if (testResult != null && "STOP".equalsIgnoreCase(testResult.getJudge())) {
+            plcPhase = PLC_PHASE_WAIT_REQUEST;
+            SwingUtilities.invokeLater(() -> setMenuStatus("UT5310测试被停止", -1));
+            return false;
+        }
+
         if (!PlcService.ensureConnected()) {
         if (!PlcService.ensureConnected()) {
             SwingUtilities.invokeLater(() -> {
             SwingUtilities.invokeLater(() -> {
                 setMenuStatus("PLC未连接,请检查网络", -1);
                 setMenuStatus("PLC未连接,请检查网络", -1);
@@ -714,6 +745,26 @@ public class MesClient extends JFrame {
             return false;
             return false;
         }
         }
 
 
+        if (testResult != null) {
+            testResult.printToConsole();
+        }
+        System.out.println("[UT5310] 准备上传 MES: sn=" + sn + ", mesResult=" + mesResult
+                + ", deviceResult=" + (testResult == null ? "" : testResult.getJudge()));
+
+        JSONObject insObj = DataUtil.uploadInsulation2(sn, user20, testResult);
+        if (insObj == null || insObj.get("result") == null
+                || !insObj.get("result").toString().equalsIgnoreCase("true")) {
+            if (isOk) {
+                PlcService.setTestOk(false);
+            } else {
+                PlcService.setTestNg(false);
+            }
+            String msg = insObj != null && insObj.get("message") != null
+                    ? insObj.getString("message") : "绝缘耐压明细上传失败,请重试";
+            SwingUtilities.invokeLater(() -> setMenuStatus(msg, -1));
+            return false;
+        }
+
         JSONObject retObj = DataUtil.sendQuality(sn, mesResult, user20,
         JSONObject retObj = DataUtil.sendQuality(sn, mesResult, user20,
                 testResult == null ? "" : testResult.getJudge(),
                 testResult == null ? "" : testResult.getJudge(),
                 testResult == null ? "" : testResult.getRawResponse());
                 testResult == null ? "" : testResult.getRawResponse());
@@ -1066,6 +1117,7 @@ public class MesClient extends JFrame {
         toolBar.add(plc_status_menu);
         toolBar.add(plc_status_menu);
 
 
         tabbedPane = new JTabbedPane(JTabbedPane.TOP);
         tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+        tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
         tabbedPane.setMinimumSize(new Dimension(400, 50));
         tabbedPane.setMinimumSize(new Dimension(400, 50));
         tabbedPane.setFont(new Font("宋体", Font.BOLD, 22));
         tabbedPane.setFont(new Font("宋体", Font.BOLD, 22));
         contentPane.add(tabbedPane, BorderLayout.CENTER);
         contentPane.add(tabbedPane, BorderLayout.CENTER);
@@ -1179,11 +1231,9 @@ public class MesClient extends JFrame {
 
 
 //		searchScrollPane = new JScrollPane((Component) null);
 //		searchScrollPane = new JScrollPane((Component) null);
 
 
-        indexPanelC = new JPanel();
-        searchScrollPaneDj = new JScrollPane(indexPanelC);
-        indexPanelC.setLayout(null);
+        indexPanelC = new JPanel(new BorderLayout());
 
 
-        tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), searchScrollPaneDj, null);
+        tabbedPane.addTab("开班点检", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), indexPanelC, null);
 
 
 
 
         // ========== 工作记录 Tab(改造:当前工位Tab + 所有工位Tab) ==========
         // ========== 工作记录 Tab(改造:当前工位Tab + 所有工位Tab) ==========
@@ -1300,9 +1350,22 @@ public class MesClient extends JFrame {
         softwareOpNameField.setEditable(false);
         softwareOpNameField.setEditable(false);
         panel.add(softwareOpNameField);
         panel.add(softwareOpNameField);
 
 
+        JLabel portLabel = new JLabel("耐压仪串口:");
+        portLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        portLabel.setBounds(40, 250, 200, 30);
+        panel.add(portLabel);
+
+        softwarePortCombo = new JComboBox<String>();
+        softwarePortCombo.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        softwarePortCombo.setBounds(220, 250, 360, 34);
+        softwarePortCombo.setEditable(true);
+        softwarePortCombo.setEnabled(false);
+        refreshSerialPortCombo();
+        panel.add(softwarePortCombo);
+
         softwareEditBtn = new JButton("编辑");
         softwareEditBtn = new JButton("编辑");
         softwareEditBtn.setFont(new Font("微软雅黑", Font.PLAIN, 20));
         softwareEditBtn.setFont(new Font("微软雅黑", Font.PLAIN, 20));
-        softwareEditBtn.setBounds(220, 270, 140, 44);
+        softwareEditBtn.setBounds(220, 320, 140, 44);
         softwareEditBtn.addActionListener(new ActionListener() {
         softwareEditBtn.addActionListener(new ActionListener() {
             @Override
             @Override
             public void actionPerformed(ActionEvent e) {
             public void actionPerformed(ActionEvent e) {
@@ -1310,6 +1373,10 @@ public class MesClient extends JFrame {
                 softwareIpField.setEditable(true);
                 softwareIpField.setEditable(true);
                 softwareOpField.setEditable(true);
                 softwareOpField.setEditable(true);
                 softwareOpNameField.setEditable(true);
                 softwareOpNameField.setEditable(true);
+                refreshSerialPortCombo();
+                if (softwarePortCombo != null) {
+                    softwarePortCombo.setEnabled(true);
+                }
                 if (softwareSaveBtn != null) {
                 if (softwareSaveBtn != null) {
                     softwareSaveBtn.setEnabled(true);
                     softwareSaveBtn.setEnabled(true);
                 }
                 }
@@ -1319,7 +1386,7 @@ public class MesClient extends JFrame {
 
 
         softwareSaveBtn = new JButton("保存");
         softwareSaveBtn = new JButton("保存");
         softwareSaveBtn.setFont(new Font("微软雅黑", Font.PLAIN, 20));
         softwareSaveBtn.setFont(new Font("微软雅黑", Font.PLAIN, 20));
-        softwareSaveBtn.setBounds(380, 270, 140, 44);
+        softwareSaveBtn.setBounds(380, 320, 140, 44);
         softwareSaveBtn.setEnabled(false);
         softwareSaveBtn.setEnabled(false);
         softwareSaveBtn.addActionListener(new ActionListener() {
         softwareSaveBtn.addActionListener(new ActionListener() {
             @Override
             @Override
@@ -1328,11 +1395,16 @@ public class MesClient extends JFrame {
                 String ip = softwareIpField.getText() == null ? "" : softwareIpField.getText().trim();
                 String ip = softwareIpField.getText() == null ? "" : softwareIpField.getText().trim();
                 String op = softwareOpField.getText() == null ? "" : softwareOpField.getText().trim();
                 String op = softwareOpField.getText() == null ? "" : softwareOpField.getText().trim();
                 String opName = softwareOpNameField.getText() == null ? "" : softwareOpNameField.getText().trim();
                 String opName = softwareOpNameField.getText() == null ? "" : softwareOpNameField.getText().trim();
+                String port = getSelectedSerialPort();
 
 
                 if (ip.isEmpty() || op.isEmpty()) {
                 if (ip.isEmpty() || op.isEmpty()) {
                     JOptionPane.showMessageDialog(mesClientFrame, "服务器IP和工位号不能为空", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
                     JOptionPane.showMessageDialog(mesClientFrame, "服务器IP和工位号不能为空", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
                     return;
                     return;
                 }
                 }
+                if (port.isEmpty()) {
+                    JOptionPane.showMessageDialog(mesClientFrame, "耐压仪串口不能为空", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                    return;
+                }
                 if (opName.isEmpty()) {
                 if (opName.isEmpty()) {
                     opName = OprnoUtil.getGwDes(mes_line_sn, op);
                     opName = OprnoUtil.getGwDes(mes_line_sn, op);
                 }
                 }
@@ -1343,19 +1415,23 @@ public class MesClient extends JFrame {
 
 
                 // 写入本地配置并刷新显示
                 // 写入本地配置并刷新显示
                 applyOpConfig(op, opName, ip);
                 applyOpConfig(op, opName, ip);
+                applyUt5310PortConfig(port);
                 loadSoftwareSettingsFromDb();
                 loadSoftwareSettingsFromDb();
                 softwareIpField.setEditable(false);
                 softwareIpField.setEditable(false);
                 softwareOpField.setEditable(false);
                 softwareOpField.setEditable(false);
                 softwareOpNameField.setEditable(false);
                 softwareOpNameField.setEditable(false);
+                if (softwarePortCombo != null) {
+                    softwarePortCombo.setEnabled(false);
+                }
                 softwareSaveBtn.setEnabled(false);
                 softwareSaveBtn.setEnabled(false);
-                JOptionPane.showMessageDialog(mesClientFrame, "保存成功,请重启软件", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                JOptionPane.showMessageDialog(mesClientFrame, "保存成功", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
             }
             }
         });
         });
         panel.add(softwareSaveBtn);
         panel.add(softwareSaveBtn);
 
 
         JButton reloadBtn = new JButton("从本地配置读取");
         JButton reloadBtn = new JButton("从本地配置读取");
         reloadBtn.setFont(new Font("微软雅黑", Font.PLAIN, 18));
         reloadBtn.setFont(new Font("微软雅黑", Font.PLAIN, 18));
-        reloadBtn.setBounds(540, 270, 200, 44);
+        reloadBtn.setBounds(540, 320, 200, 44);
         reloadBtn.addActionListener(new ActionListener() {
         reloadBtn.addActionListener(new ActionListener() {
             @Override
             @Override
 
 
@@ -1370,6 +1446,60 @@ public class MesClient extends JFrame {
         return panel;
         return panel;
     }
     }
 
 
+    private void refreshSerialPortCombo() {
+        if (softwarePortCombo == null) {
+            return;
+        }
+        String current = getSelectedSerialPort();
+        if (current.isEmpty()) {
+            current = Ut5310ScpiService.getPortName();
+        }
+        softwarePortCombo.removeAllItems();
+        SerialPort[] ports = SerialPort.getCommPorts();
+        if (ports != null) {
+            for (SerialPort p : ports) {
+                if (p != null && p.getSystemPortName() != null) {
+                    softwarePortCombo.addItem(p.getSystemPortName());
+                }
+            }
+        }
+        if (current != null && !current.isEmpty()) {
+            boolean found = false;
+            for (int i = 0; i < softwarePortCombo.getItemCount(); i++) {
+                if (current.equalsIgnoreCase(softwarePortCombo.getItemAt(i))) {
+                    softwarePortCombo.setSelectedIndex(i);
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                softwarePortCombo.addItem(current);
+                softwarePortCombo.setSelectedItem(current);
+            }
+        }
+    }
+
+    private String getSelectedSerialPort() {
+        if (softwarePortCombo == null) {
+            return "";
+        }
+        Object selected = softwarePortCombo.getEditor().getItem();
+        if (selected == null) {
+            selected = softwarePortCombo.getSelectedItem();
+        }
+        return selected == null ? "" : selected.toString().trim();
+    }
+
+    private void applyUt5310PortConfig(String port) {
+        if (port == null || port.trim().isEmpty()) {
+            return;
+        }
+        String next = port.trim();
+        Ut5310ScpiService.setPortName(next);
+        JdbcUtils.setAppConfig(APP_CFG_UT5310_PORT, next);
+        log.info("保存耐压仪串口配置: {}", next);
+    }
+
     // 读取并回填“软件设置”页面数据
     // 读取并回填“软件设置”页面数据
     private void loadSoftwareSettingsFromDb() {
     private void loadSoftwareSettingsFromDb() {
         String[] cfg = JdbcUtils.getAnyOpIpConfig();
         String[] cfg = JdbcUtils.getAnyOpIpConfig();
@@ -1384,6 +1514,12 @@ public class MesClient extends JFrame {
             softwareIpField.setText(mes_server_ip == null ? "" : mes_server_ip);
             softwareIpField.setText(mes_server_ip == null ? "" : mes_server_ip);
         }
         }
 
 
+        String portFromDb = JdbcUtils.getAppConfig(APP_CFG_UT5310_PORT);
+        if (portFromDb != null && !portFromDb.isEmpty()) {
+            Ut5310ScpiService.setPortName(portFromDb);
+        }
+        refreshSerialPortCombo();
+
         if (softwareSaveBtn != null) {
         if (softwareSaveBtn != null) {
             softwareSaveBtn.setEnabled(false);
             softwareSaveBtn.setEnabled(false);
         }
         }
@@ -1396,6 +1532,9 @@ public class MesClient extends JFrame {
         if (softwareOpNameField != null) {
         if (softwareOpNameField != null) {
             softwareOpNameField.setEditable(false);
             softwareOpNameField.setEditable(false);
         }
         }
+        if (softwarePortCombo != null) {
+            softwarePortCombo.setEnabled(false);
+        }
     }
     }
 
 
     // 进入“软件设置”页签前的密码校验(固定密码:mes)
     // 进入“软件设置”页签前的密码校验(固定密码:mes)

+ 52 - 0
src/com/mes/util/JdbcUtils.java

@@ -86,9 +86,61 @@ public class JdbcUtils {
 				")";
 				")";
 		statement.executeUpdate(opIpConfig);
 		statement.executeUpdate(opIpConfig);
 
 
+		String appConfig = "CREATE TABLE if not exists app_config(\n" +
+				"   key VARCHAR(50) PRIMARY KEY,\n" +
+				"   value VARCHAR(200)\n" +
+				")";
+		statement.executeUpdate(appConfig);
+
         statement.close();
         statement.close();
     }
     }
 
 
+	public static String getAppConfig(String key) {
+		try {
+			if (key == null || key.trim().isEmpty()) {
+				return null;
+			}
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			String sql = "SELECT value FROM app_config WHERE key = ?";
+			try (PreparedStatement ps = conn.prepareStatement(sql)) {
+				ps.setString(1, key.trim());
+				try (ResultSet rs = ps.executeQuery()) {
+					if (rs.next()) {
+						String value = rs.getString("value");
+						return value == null ? null : value.trim();
+					}
+				}
+			}
+		} catch (Exception e) {
+			log.info("查询app_config失败: {}", e.getMessage());
+		}
+		return null;
+	}
+
+	public static boolean setAppConfig(String key, String value) {
+		try {
+			if (key == null || key.trim().isEmpty()) {
+				return false;
+			}
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			String sql = "INSERT INTO app_config(key, value) VALUES(?, ?) " +
+					"ON CONFLICT(key) DO UPDATE SET value=excluded.value";
+			try (PreparedStatement ps = conn.prepareStatement(sql)) {
+				ps.setString(1, key.trim());
+				ps.setString(2, value == null ? "" : value.trim());
+				ps.executeUpdate();
+			}
+			return true;
+		} catch (Exception e) {
+			log.info("保存app_config失败: {}", e.getMessage());
+			return false;
+		}
+	}
+
 	public static String getServerIpByOp(String op) {
 	public static String getServerIpByOp(String op) {
 		try {
 		try {
 			if (op == null || op.trim().isEmpty()) {
 			if (op == null || op.trim().isEmpty()) {

+ 217 - 0
src/com/mes/util/ModbusRtuClient.java

@@ -0,0 +1,217 @@
+package com.mes.util;
+
+import com.fazecast.jSerialComm.SerialPort;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Modbus RTU over RS232/RS485 serial port.
+ */
+public class ModbusRtuClient {
+
+    public static final Logger log = LoggerFactory.getLogger(ModbusRtuClient.class);
+
+    private static final int INTER_FRAME_MS = 5;
+
+    private final SerialPort serialPort;
+    private final int slaveId;
+    private final int readTimeoutMs;
+
+    public ModbusRtuClient(SerialPort serialPort, int slaveId, int readTimeoutMs) {
+        this.serialPort = serialPort;
+        this.slaveId = slaveId & 0xFF;
+        this.readTimeoutMs = readTimeoutMs;
+    }
+
+    public int[] readHoldingRegisters(int startAddress, int quantity) throws IOException {
+        if (quantity <= 0 || quantity > 125) {
+            throw new IOException("Invalid register quantity: " + quantity);
+        }
+        byte[] request = new byte[8];
+        request[0] = (byte) slaveId;
+        request[1] = 0x03;
+        request[2] = (byte) ((startAddress >> 8) & 0xFF);
+        request[3] = (byte) (startAddress & 0xFF);
+        request[4] = (byte) ((quantity >> 8) & 0xFF);
+        request[5] = (byte) (quantity & 0xFF);
+        appendCrc(request, 6);
+
+        byte[] response = transact(request);
+        validateResponse(response, 0x03);
+        int byteCount = response[2] & 0xFF;
+        if (byteCount != quantity * 2) {
+            throw new IOException("Unexpected byte count: " + byteCount);
+        }
+        int[] registers = new int[quantity];
+        for (int i = 0; i < quantity; i++) {
+            int hi = response[3 + i * 2] & 0xFF;
+            int lo = response[4 + i * 2] & 0xFF;
+            registers[i] = (hi << 8) | lo;
+        }
+        return registers;
+    }
+
+    public void writeSingleRegister(int address, int value) throws IOException {
+        writeMultipleRegisters(address, new int[]{value & 0xFFFF});
+    }
+
+    public void writeMultipleRegisters(int startAddress, int[] values) throws IOException {
+        if (values == null || values.length == 0 || values.length > 123) {
+            throw new IOException("Invalid write values");
+        }
+        int byteCount = values.length * 2;
+        byte[] request = new byte[9 + byteCount];
+        request[0] = (byte) slaveId;
+        request[1] = 0x10;
+        request[2] = (byte) ((startAddress >> 8) & 0xFF);
+        request[3] = (byte) (startAddress & 0xFF);
+        request[4] = (byte) ((values.length >> 8) & 0xFF);
+        request[5] = (byte) (values.length & 0xFF);
+        request[6] = (byte) byteCount;
+        for (int i = 0; i < values.length; i++) {
+            request[7 + i * 2] = (byte) ((values[i] >> 8) & 0xFF);
+            request[8 + i * 2] = (byte) (values[i] & 0xFF);
+        }
+        appendCrc(request, 7 + byteCount);
+
+        byte[] response = transact(request);
+        validateResponse(response, 0x10);
+    }
+
+    private byte[] transact(byte[] request) throws IOException {
+        drainInput();
+        int written = serialPort.writeBytes(request, request.length);
+        if (written != request.length) {
+            throw new IOException("Modbus write incomplete");
+        }
+        log.info("Modbus TX: {}", toHex(request));
+        sleepQuiet(INTER_FRAME_MS);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[256];
+        long deadline = System.currentTimeMillis() + readTimeoutMs;
+        long lastDataTime = 0;
+        while (System.currentTimeMillis() < deadline) {
+            int read = serialPort.readBytes(buffer, buffer.length);
+            if (read > 0) {
+                out.write(buffer, 0, read);
+                lastDataTime = System.currentTimeMillis();
+                byte[] data = out.toByteArray();
+                if (isResponseComplete(data)) {
+                    log.info("Modbus RX: {}", toHex(data));
+                    return data;
+                }
+            } else if (out.size() > 0 && lastDataTime > 0
+                    && System.currentTimeMillis() - lastDataTime > 80) {
+                break;
+            } else {
+                sleepQuiet(20);
+            }
+        }
+        if (out.size() == 0) {
+            throw new IOException("Modbus response timeout");
+        }
+        byte[] partial = out.toByteArray();
+        log.info("Modbus RX(partial): {}", toHex(partial));
+        return partial;
+    }
+
+    private static boolean isResponseComplete(byte[] data) {
+        if (data.length < 5) {
+            return false;
+        }
+        int functionCode = data[1] & 0xFF;
+        if ((functionCode & 0x80) != 0) {
+            return data.length >= 5;
+        }
+        if (functionCode == 0x03) {
+            int byteCount = data[2] & 0xFF;
+            return data.length >= 3 + byteCount + 2;
+        }
+        if (functionCode == 0x10) {
+            return data.length >= 8;
+        }
+        return data.length >= 5;
+    }
+
+    private static void validateResponse(byte[] response, int functionCode) throws IOException {
+        if (response.length < 5) {
+            throw new IOException("Modbus response too short");
+        }
+        if ((response[1] & 0xFF) == (functionCode | 0x80)) {
+            throw new IOException("Modbus exception code: " + (response[2] & 0xFF));
+        }
+        if ((response[1] & 0xFF) != functionCode) {
+            throw new IOException("Unexpected function code: " + (response[1] & 0xFF));
+        }
+        if (!verifyCrc(response)) {
+            throw new IOException("Modbus CRC error");
+        }
+    }
+
+    private void drainInput() {
+        byte[] buf = new byte[256];
+        try {
+            while (serialPort.bytesAvailable() > 0) {
+                serialPort.readBytes(buf, Math.min(buf.length, serialPort.bytesAvailable()));
+            }
+        } catch (Exception ignored) {
+        }
+    }
+
+    static int crc16(byte[] data, int length) {
+        int crc = 0xFFFF;
+        for (int i = 0; i < length; i++) {
+            crc ^= (data[i] & 0xFF);
+            for (int j = 0; j < 8; j++) {
+                if ((crc & 0x0001) != 0) {
+                    crc = (crc >> 1) ^ 0xA001;
+                } else {
+                    crc >>= 1;
+                }
+            }
+        }
+        return crc & 0xFFFF;
+    }
+
+    private static void appendCrc(byte[] frame, int length) {
+        int crc = crc16(frame, length);
+        frame[length] = (byte) (crc & 0xFF);
+        frame[length + 1] = (byte) ((crc >> 8) & 0xFF);
+    }
+
+    private static boolean verifyCrc(byte[] frame) {
+        if (frame.length < 3) {
+            return false;
+        }
+        int crc = crc16(frame, frame.length - 2);
+        int lo = frame[frame.length - 2] & 0xFF;
+        int hi = frame[frame.length - 1] & 0xFF;
+        return crc == ((hi << 8) | lo);
+    }
+
+    static float registersToFloat(int regHi, int regLo) {
+        int bits = ((regHi & 0xFFFF) << 16) | (regLo & 0xFFFF);
+        return Float.intBitsToFloat(bits);
+    }
+
+    static String toHex(byte[] data) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : data) {
+            sb.append(String.format("%02X ", b & 0xFF));
+        }
+        return sb.toString().trim();
+    }
+
+    private static void sleepQuiet(int ms) throws IOException {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IOException("Modbus interrupted", e);
+        }
+    }
+}

+ 5 - 1
src/com/mes/util/PlcService.java

@@ -94,6 +94,10 @@ public class PlcService {
         return writeMesBit(Op310PlcPoint.TEST_OK_BIT, value);
         return writeMesBit(Op310PlcPoint.TEST_OK_BIT, value);
     }
     }
 
 
+    public static void main(String[] args) {
+        setTestOk(false);
+    }
+
     public static boolean setTestNg(boolean value) {
     public static boolean setTestNg(boolean value) {
         return writeMesBit(Op310PlcPoint.TEST_NG_BIT, value);
         return writeMesBit(Op310PlcPoint.TEST_NG_BIT, value);
     }
     }
@@ -147,7 +151,7 @@ public class PlcService {
                 current[0] = (byte) (current[0] & ~(1 << bitOffset));
                 current[0] = (byte) (current[0] & ~(1 << bitOffset));
             }
             }
             connector.write(DaveArea.DB, Op310PlcPoint.DB_NUMBER, byteOffset, current);
             connector.write(DaveArea.DB, Op310PlcPoint.DB_NUMBER, byteOffset, current);
-            log.info("PLC写入 DB{}.DBB{}.{} = {}", Op310PlcPoint.DB_NUMBER, byteOffset, bitOffset, value);
+//            log.info("PLC写入 DB{}.DBB{}.{} = {}", Op310PlcPoint.DB_NUMBER, byteOffset, bitOffset, value);
             return true;
             return true;
         } catch (Exception e) {
         } catch (Exception e) {
             connected = false;
             connected = false;

+ 375 - 78
src/com/mes/util/Ut5310ScpiService.java

@@ -5,35 +5,75 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 
 
 import java.io.IOException;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
 
+/**
+ * UT5310 绝缘耐压仪通信(Modbus RTU,参考 UNI-T Programming Manual MODBUS RTU)。
+ */
 public class Ut5310ScpiService {
 public class Ut5310ScpiService {
 
 
     public static final Logger log = LoggerFactory.getLogger(Ut5310ScpiService.class);
     public static final Logger log = LoggerFactory.getLogger(Ut5310ScpiService.class);
 
 
-    private static final Charset COMMAND_CHARSET = StandardCharsets.US_ASCII;
-    private static final String START_COMMAND = "FUNC:START";
-    private static final String FETCH_COMMAND = "FETCh?";
+    public enum Protocol {
+        MODBUS, SCPI, AT, AUTO
+    }
+
+    /** 测试结果起始寄存器 0x0100:步骤1电压,每步占5个寄存器 */
+    private static final int REG_RESULT_START = 0x0100;
+    /**
+     * 每步 Modbus 寄存器布局(手册 3.1):
+     * +0,+1 测试电压(32位浮点,单位 kV)
+     * +2,+3 测试电流或电阻(32位浮点;仪器原始单位:绝缘步骤=MΩ,耐压步骤=mA;显示时转为GΩ/μA)
+     * +4    判读结果(16位整数,0=测试中;0x0003=PASS 等)
+     */
+    private static final int REGS_PER_STEP = 5;
+    /** 启停测试寄存器 0x0500:写 0x0002 启动,0x0000 停止 */
+    private static final int REG_START_STOP = 0x0500;
+    private static final int CMD_START = 0x0002;
+    private static final int CMD_STOP = 0x0000;
+    /** 判读码:PASS(手册示例 0x0003) */
+    private static final int SORT_PASS = 0x0003;
 
 
     private static String portName = "COM1";
     private static String portName = "COM1";
     private static int baudRate = 9600;
     private static int baudRate = 9600;
     private static int dataBits = 8;
     private static int dataBits = 8;
     private static int stopBits = SerialPort.ONE_STOP_BIT;
     private static int stopBits = SerialPort.ONE_STOP_BIT;
     private static int parity = SerialPort.NO_PARITY;
     private static int parity = SerialPort.NO_PARITY;
-    private static int readTimeoutMs = 1000;
-    private static int testTimeoutMs = 60000;
+    private static int readTimeoutMs = 2000;
+    private static int testTimeoutMs = 300000;
     private static int fetchIntervalMs = 500;
     private static int fetchIntervalMs = 500;
+    private static Protocol protocol = Protocol.MODBUS;
+    private static int modbusSlaveId = 1;
+    /** 单次 Modbus 读取的最大步骤数(寄存器块长度) */
+    private static int modbusMaxSteps = 4;
+    /** 测试文件实际步骤数,全部步骤判读非0后才提交结果(如 PANEL_01=2:绝缘电阻+直流耐压) */
+    private static int modbusExpectedSteps = 2;
+    /** false=仅读取PLC/工装已触发的测试结果,不在Modbus上重复发START */
+    private static boolean modbusSendStart = false;
 
 
     private static SerialPort serialPort;
     private static SerialPort serialPort;
-    private static InputStream inputStream;
-    private static OutputStream outputStream;
     private static volatile boolean connected = false;
     private static volatile boolean connected = false;
+    private static volatile Protocol lastUsedProtocol;
 
 
     public static synchronized void configure(String port, int baud, int data, int stop, String parityName,
     public static synchronized void configure(String port, int baud, int data, int stop, String parityName,
                                               int readTimeout, int testTimeout, int fetchInterval) {
                                               int readTimeout, int testTimeout, int fetchInterval) {
+        configure(port, baud, data, stop, parityName, readTimeout, testTimeout, fetchInterval,
+                "MODBUS", 1, 4, false, 2);
+    }
+
+    public static synchronized void configure(String port, int baud, int data, int stop, String parityName,
+                                              int readTimeout, int testTimeout, int fetchInterval,
+                                              String protocolName, int slaveId, int maxSteps,
+                                              boolean sendStart) {
+        configure(port, baud, data, stop, parityName, readTimeout, testTimeout, fetchInterval,
+                protocolName, slaveId, maxSteps, sendStart, 2);
+    }
+
+    public static synchronized void configure(String port, int baud, int data, int stop, String parityName,
+                                              int readTimeout, int testTimeout, int fetchInterval,
+                                              String protocolName, int slaveId, int maxSteps,
+                                              boolean sendStart, int expectedSteps) {
         if (port != null && !port.trim().isEmpty()) {
         if (port != null && !port.trim().isEmpty()) {
             portName = port.trim();
             portName = port.trim();
         }
         }
@@ -54,6 +94,40 @@ public class Ut5310ScpiService {
         if (fetchInterval > 0) {
         if (fetchInterval > 0) {
             fetchIntervalMs = fetchInterval;
             fetchIntervalMs = fetchInterval;
         }
         }
+        protocol = parseProtocol(protocolName);
+        if (slaveId >= 0 && slaveId <= 255) {
+            modbusSlaveId = slaveId;
+        }
+        if (maxSteps > 0 && maxSteps <= 20) {
+            modbusMaxSteps = maxSteps;
+        }
+        modbusSendStart = sendStart;
+        if (expectedSteps > 0 && expectedSteps <= 20) {
+            modbusExpectedSteps = expectedSteps;
+        }
+    }
+
+    public static Protocol getLastUsedProtocol() {
+        return lastUsedProtocol;
+    }
+
+    public static synchronized String getPortName() {
+        return portName;
+    }
+
+    /** 仅更新串口号;若变更则断开,下次通信时按新口重连 */
+    public static synchronized void setPortName(String port) {
+        if (port == null || port.trim().isEmpty()) {
+            return;
+        }
+        String next = port.trim();
+        if (!next.equalsIgnoreCase(portName)) {
+            portName = next;
+            disconnect();
+            log.info("UT5310 port set to {}", portName);
+        } else {
+            portName = next;
+        }
     }
     }
 
 
     public static synchronized boolean isConnected() {
     public static synchronized boolean isConnected() {
@@ -65,16 +139,15 @@ public class Ut5310ScpiService {
         try {
         try {
             serialPort = SerialPort.getCommPort(portName);
             serialPort = SerialPort.getCommPort(portName);
             serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
             serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
-            serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, readTimeoutMs, readTimeoutMs);
+            serialPort.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 0, 0);
             if (!serialPort.openPort()) {
             if (!serialPort.openPort()) {
                 connected = false;
                 connected = false;
                 log.info("UT5310 open port failed: {}", portName);
                 log.info("UT5310 open port failed: {}", portName);
                 return false;
                 return false;
             }
             }
-            inputStream = serialPort.getInputStream();
-            outputStream = serialPort.getOutputStream();
             connected = true;
             connected = true;
-            log.info("UT5310 connected: {} {}bps", portName, baudRate);
+            log.info("UT5310 connected: {} {}bps, protocol={}, slave={}",
+                    portName, baudRate, protocol, modbusSlaveId);
             return true;
             return true;
         } catch (Exception e) {
         } catch (Exception e) {
             connected = false;
             connected = false;
@@ -86,10 +159,6 @@ public class Ut5310ScpiService {
 
 
     public static synchronized void disconnect() {
     public static synchronized void disconnect() {
         connected = false;
         connected = false;
-        closeQuietly(inputStream);
-        closeQuietly(outputStream);
-        inputStream = null;
-        outputStream = null;
         if (serialPort != null) {
         if (serialPort != null) {
             try {
             try {
                 serialPort.closePort();
                 serialPort.closePort();
@@ -111,76 +180,318 @@ public class Ut5310ScpiService {
             throw new IOException("UT5310 not connected");
             throw new IOException("UT5310 not connected");
         }
         }
 
 
-        drainInput();
-        writeCommand(START_COMMAND);
-        log.info("UT5310 command sent: {}", START_COMMAND);
+        lastUsedProtocol = protocol == Protocol.AUTO ? Protocol.MODBUS : protocol;
+        System.out.println("[UT5310] 使用协议: Modbus RTU, 站号=" + modbusSlaveId
+                + ", 等待步骤数=" + modbusExpectedSteps);
+        log.info("UT5310 using Modbus RTU, slave={}, expectedSteps={}", modbusSlaveId, modbusExpectedSteps);
+
+        Ut5310TestResult result = runModbusTest();
+        if (result != null) {
+            result.printToConsole();
+        }
+        return result;
+    }
+
+    private static Ut5310TestResult runModbusTest() throws IOException {
+        ModbusRtuClient client = new ModbusRtuClient(serialPort, modbusSlaveId, readTimeoutMs);
+        int registerCount = modbusMaxSteps * REGS_PER_STEP;
+
+        // 先尝试直接读取(工装/PLC 可能已完成测试)
+        Ut5310TestResult existing = tryReadModbusResult(client, registerCount);
+        if (existing != null) {
+            System.out.println("[UT5310] 读取到已有测试结果(未发START)");
+            return existing;
+        }
+
+        if (modbusSendStart) {
+            System.out.println("[UT5310] Modbus 写 0x0500: STOP -> START");
+            log.info("Modbus write 0x0500 STOP then START");
+            client.writeSingleRegister(REG_START_STOP, CMD_STOP);
+            sleepQuiet(200);
+            client.writeSingleRegister(REG_START_STOP, CMD_START);
+            sleepQuiet(500);
+            int runState = readStartStopState(client);
+            System.out.println("[UT5310] 0x0500 当前值=0x" + Integer.toHexString(runState).toUpperCase());
+            log.info("Modbus 0x0500 state=0x{}", Integer.toHexString(runState));
+
+            existing = tryReadModbusResult(client, registerCount);
+            if (existing != null) {
+                return existing;
+            }
+        } else {
+            System.out.println("[UT5310] 仅读取Modbus结果,不发送START( mes.ut5310_modbus_send_start=false )");
+            log.info("Modbus read-only mode, skip START command");
+        }
 
 
         long deadline = System.currentTimeMillis() + testTimeoutMs;
         long deadline = System.currentTimeMillis() + testTimeoutMs;
-        String lastResponse = "";
+        int pollCount = 0;
         while (System.currentTimeMillis() < deadline) {
         while (System.currentTimeMillis() < deadline) {
-            sleep(fetchIntervalMs);
-            writeCommand(FETCH_COMMAND);
-            lastResponse = readResponse();
-            log.info("UT5310 fetch response: {}", lastResponse);
-
-            Ut5310TestResult result = parseFetchResult(lastResponse);
+            pollCount++;
+            int[] regs = client.readHoldingRegisters(REG_RESULT_START, registerCount);
+            if (pollCount == 1 || pollCount % 10 == 0) {
+                logPollProgress(pollCount, regs);
+            }
+            Ut5310TestResult result = parseModbusResult(regs);
             if (result != null) {
             if (result != null) {
                 return result;
                 return result;
             }
             }
+            sleepQuiet(fetchIntervalMs);
         }
         }
-        throw new IOException("UT5310 test timeout, last response: " + lastResponse);
+
+        if (modbusSendStart) {
+            try {
+                client.writeSingleRegister(REG_START_STOP, CMD_STOP);
+            } catch (Exception e) {
+                log.info("Modbus stop ignored: {}", e.getMessage());
+            }
+        }
+
+        throw new IOException("UT5310 Modbus 超时:"
+                + formatCompletionStatus(null)
+                + "。请增大 mes.ut5310_test_timeout_ms(当前" + testTimeoutMs + "ms)");
     }
     }
 
 
-    public static Ut5310TestResult parseFetchResult(String response) {
-        if (response == null) {
-            return null;
+    /** 轮询时打印各步骤实时电压、电阻/电流、判读(判读仅该步骤结束后才有值) */
+    private static void logPollProgress(int pollCount, int[] regs) {
+        int steps = Math.min(modbusExpectedSteps, regs.length / REGS_PER_STEP);
+        StringBuilder sb = new StringBuilder();
+        sb.append(String.format("[UT5310] 轮询#%d:", pollCount));
+        for (int i = 0; i < steps; i++) {
+            int base = i * REGS_PER_STEP;
+            float volt = ModbusRtuClient.registersToFloat(regs[base], regs[base + 1]);
+            float rawData = ModbusRtuClient.registersToFloat(regs[base + 2], regs[base + 3]);
+            float displayData = convertStepData(i, rawData);
+            int sort = regs[base + 4];
+            sb.append(String.format(" [%d]%s 电压=%.3fkV %s=%s 判读=%s",
+                    i + 1, stepTypeName(i), volt, stepDataUnit(i),
+                    formatStepData(i, displayData), sortingToChinese(sort)));
         }
         }
-        String raw = response.trim();
-        if (raw.isEmpty()) {
+        System.out.println(sb);
+        int[] sorting = extractSorting(regs);
+        if (modbusExpectedSteps > 1 && sorting[0] != 0 && sorting.length > 1 && sorting[1] == 0) {
+            System.out.println("[UT5310] 步骤1(" + stepTypeName(0) + ")已判读完成,等待步骤2("
+                    + stepTypeName(1) + ")结束后再提交...");
+        }
+    }
+
+    private static String formatCompletionStatus(int[] sorting) {
+        if (sorting == null) {
+            return "未完成全部" + modbusExpectedSteps + "个测试步骤";
+        }
+        StringBuilder sb = new StringBuilder("已完成");
+        int done = 0;
+        for (int i = 0; i < modbusExpectedSteps && i < sorting.length; i++) {
+            if (sorting[i] != 0) {
+                done++;
+            }
+        }
+        sb.append(done).append('/').append(modbusExpectedSteps).append("步");
+        return sb.toString();
+    }
+
+    private static Ut5310TestResult tryReadModbusResult(ModbusRtuClient client, int registerCount) throws IOException {
+        int[] regs = client.readHoldingRegisters(REG_RESULT_START, registerCount);
+        return parseModbusResult(regs);
+    }
+
+    private static Ut5310TestResult parseModbusResult(int[] regs) {
+        int[] sorting = extractSorting(regs);
+        float[] voltages = extractVoltage(regs);
+        float[] values = extractValue(regs);
+        if (!isSortingComplete(sorting, voltages, values)) {
             return null;
             return null;
         }
         }
-        String normalized = raw.toUpperCase();
-        if (normalized.contains("FAIL") || normalized.contains("NG")) {
-            return new Ut5310TestResult(false, "FAIL", raw);
+        return buildModbusResult(sorting, voltages, values);
+    }
+
+    private static int readStartStopState(ModbusRtuClient client) {
+        try {
+            int[] state = client.readHoldingRegisters(REG_START_STOP, 1);
+            return state[0];
+        } catch (Exception e) {
+            log.info("Read 0x0500 failed: {}", e.getMessage());
+            return -1;
         }
         }
-        if (normalized.contains("PASS") || normalized.equals("OK") || normalized.startsWith("OK,")) {
-            return new Ut5310TestResult(true, "PASS", raw);
+    }
+
+    /** 从寄存器块提取各步骤判读值(每步偏移+4) */
+    private static int[] extractSorting(int[] regs) {
+        int steps = regs.length / REGS_PER_STEP;
+        int[] sorting = new int[steps];
+        for (int i = 0; i < steps; i++) {
+            sorting[i] = regs[i * REGS_PER_STEP + 4];
         }
         }
-        return null;
+        return sorting;
     }
     }
 
 
-    private static void writeCommand(String command) throws IOException {
-        outputStream.write((command + "\n").getBytes(COMMAND_CHARSET));
-        outputStream.flush();
+    /** 从寄存器块提取各步骤测试电压(kV,每步偏移+0/+1) */
+    private static float[] extractVoltage(int[] regs) {
+        int steps = regs.length / REGS_PER_STEP;
+        float[] values = new float[steps];
+        for (int i = 0; i < steps; i++) {
+            int base = i * REGS_PER_STEP;
+            values[i] = ModbusRtuClient.registersToFloat(regs[base], regs[base + 1]);
+        }
+        return values;
     }
     }
 
 
-    private static String readResponse() throws IOException {
-        StringBuilder response = new StringBuilder();
-        byte[] buffer = new byte[256];
-        long deadline = System.currentTimeMillis() + readTimeoutMs;
-        while (System.currentTimeMillis() < deadline) {
-            int len = inputStream.read(buffer);
-            if (len > 0) {
-                response.append(new String(buffer, 0, len, COMMAND_CHARSET));
-                if (response.indexOf("\n") >= 0) {
-                    break;
-                }
-            } else if (response.length() > 0) {
-                break;
+    /** 从寄存器块提取各步骤电流或电阻(仪器原始:绝缘=MΩ,耐压=mA) */
+    private static float[] extractValue(int[] regs) {
+        int steps = regs.length / REGS_PER_STEP;
+        float[] values = new float[steps];
+        for (int i = 0; i < steps; i++) {
+            int base = i * REGS_PER_STEP;
+            values[i] = ModbusRtuClient.registersToFloat(regs[base + 2], regs[base + 3]);
+        }
+        return values;
+    }
+
+    /**
+     * 全部配置步骤的判读寄存器均非0时,才视为整次测试结束并提交MES/PLC。
+     * 避免步骤1(绝缘电阻)完成后过早提交、工装抬起而步骤2(直流耐压)仍在进行。
+     */
+    static boolean isSortingComplete(int[] sorting, float[] voltages, float[] values) {
+        int expected = Math.min(modbusExpectedSteps, sorting.length);
+        if (expected <= 0) {
+            expected = 1;
+        }
+        for (int i = 0; i < expected; i++) {
+            if (sorting[i] == 0) {
+                return false;
             }
             }
         }
         }
-        return response.toString().trim();
+        return true;
     }
     }
 
 
-    private static void drainInput() {
-        try {
-            while (inputStream != null && inputStream.available() > 0) {
-                inputStream.read();
+    /** PANEL_01 等测试文件:步骤1绝缘电阻,步骤2直流耐压 */
+    private static String stepTypeName(int stepIndex) {
+        switch (stepIndex) {
+            case 0:
+                return "绝缘电阻";
+            case 1:
+                return "直流耐压";
+            default:
+                return "测试";
+        }
+    }
+
+    private static String stepDataUnit(int stepIndex) {
+        switch (stepIndex) {
+            case 0:
+                return "电阻(GΩ)";
+            case 1:
+                return "电流(μA)";
+            default:
+                return "测试值";
+        }
+    }
+
+    /** 仪器原始值转显示单位:MΩ→GΩ,mA→μA */
+    private static float convertStepData(int stepIndex, float rawValue) {
+        if (stepIndex == 0) {
+            return rawValue / 1000f;
+        }
+        if (stepIndex == 1) {
+            return rawValue * 1000f;
+        }
+        return rawValue;
+    }
+
+    private static String formatStepData(int stepIndex, float displayValue) {
+        if (stepIndex == 0) {
+            return String.format("%.4f", displayValue);
+        }
+        if (stepIndex == 1) {
+            return String.format("%.2f", displayValue);
+        }
+        return String.format("%.6f", displayValue);
+    }
+
+    private static String sortingToChinese(int sortCode) {
+        if (sortCode == 0) {
+            return "0x0000(测试中)";
+        }
+        return sortingToJudge(sortCode) + "(0x" + Integer.toHexString(sortCode).toUpperCase() + ")";
+    }
+
+    private static Ut5310TestResult buildModbusResult(int[] sorting, float[] voltages, float[] values) {
+        List<Ut5310TestResult.StepData> steps = new ArrayList<>();
+        StringBuilder raw = new StringBuilder();
+        raw.append("ModbusRTU,slave=").append(modbusSlaveId).append('\n');
+
+        boolean allPass = true;
+        String totalJudge = "OK";
+        int activeSteps = 0;
+
+        for (int i = 0; i < sorting.length; i++) {
+            if (sorting[i] == 0) {
+                continue;
             }
             }
-        } catch (Exception ignored) {
+            activeSteps++;
+            int stepNo = i + 1;
+            String stepJudge = sortingToJudge(sorting[i]);
+            if (!"OK".equals(stepJudge) && !"PASS".equals(stepJudge)) {
+                allPass = false;
+                totalJudge = stepJudge;
+            }
+            float volt = voltages[i];
+            float displayData = convertStepData(i, values[i]);
+            String typeName = stepTypeName(i);
+            String dataUnit = stepDataUnit(i);
+            String dataText = formatStepData(i, displayData);
+            raw.append(String.format("步骤%d(%s),判读=0x%04X(%s),电压=%.4fkV,%s=%s%n",
+                    stepNo, typeName, sorting[i], stepJudge, volt, dataUnit, dataText));
+            steps.add(new Ut5310TestResult.StepData(
+                    stepNo, typeName, stepJudge,
+                    "", "", String.format("%.4f", volt), "",
+                    dataText));
+            System.out.println(String.format("[UT5310] 步骤%d(%s): 判读=%s, 电压=%.4fkV, %s=%s",
+                    stepNo, typeName, sortingToChinese(sorting[i]), volt, dataUnit, dataText));
+        }
+
+        if (activeSteps == 0) {
+            return new Ut5310TestResult(false, "FAIL", raw.toString());
+        }
+        if (allPass) {
+            totalJudge = "OK";
+        }
+        System.out.println("[UT5310] 全部" + modbusExpectedSteps + "个测试步骤已完成,提交结果");
+        return new Ut5310TestResult(allPass, totalJudge, raw.toString(), totalJudge, steps);
+    }
+
+    static String sortingToJudge(int sortCode) {
+        if (sortCode == SORT_PASS) {
+            return "OK";
+        }
+        switch (sortCode) {
+            case 0x0100: return "短路";
+            case 0x0101: return "电弧";
+            case 0x0110: return "接地失败";
+            case 0x0111: return "过压";
+            case 0x1000: return "上限不良";
+            case 0x1001: return "下限不良";
+            case 0x1010: return "充电下限";
+            default: return "NG(0x" + Integer.toHexString(sortCode).toUpperCase() + ")";
         }
         }
     }
     }
 
 
+    private static Protocol parseProtocol(String value) {
+        if (value == null) {
+            return Protocol.MODBUS;
+        }
+        String v = value.trim().toUpperCase();
+        if ("SCPI".equals(v)) {
+            return Protocol.SCPI;
+        }
+        if ("AT".equals(v) || "TEST".equals(v)) {
+            return Protocol.AT;
+        }
+        if ("AUTO".equals(v)) {
+            return Protocol.AUTO;
+        }
+        return Protocol.MODBUS;
+    }
+
     private static int toStopBits(int stop) {
     private static int toStopBits(int stop) {
         if (stop == 2) {
         if (stop == 2) {
             return SerialPort.TWO_STOP_BITS;
             return SerialPort.TWO_STOP_BITS;
@@ -202,7 +513,7 @@ public class Ut5310ScpiService {
         return SerialPort.NO_PARITY;
         return SerialPort.NO_PARITY;
     }
     }
 
 
-    private static void sleep(int millis) throws IOException {
+    private static void sleepQuiet(int millis) throws IOException {
         try {
         try {
             Thread.sleep(millis);
             Thread.sleep(millis);
         } catch (InterruptedException e) {
         } catch (InterruptedException e) {
@@ -210,18 +521,4 @@ public class Ut5310ScpiService {
             throw new IOException("UT5310 test interrupted", e);
             throw new IOException("UT5310 test interrupted", e);
         }
         }
     }
     }
-
-    private static void closeQuietly(Object closeable) {
-        if (closeable == null) {
-            return;
-        }
-        try {
-            if (closeable instanceof InputStream) {
-                ((InputStream) closeable).close();
-            } else if (closeable instanceof OutputStream) {
-                ((OutputStream) closeable).close();
-            }
-        } catch (Exception ignored) {
-        }
-    }
 }
 }

+ 179 - 0
src/com/mes/util/Ut5310TestResult.java

@@ -1,15 +1,91 @@
 package com.mes.util;
 package com.mes.util;
 
 
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 public class Ut5310TestResult {
 public class Ut5310TestResult {
 
 
+    public static class StepData {
+
+        private final int step;
+        private final String func;
+        private final String stepJudge;
+        private final String time;
+        private final String scan;
+        private final String volt;
+        private final String limit;
+        private final String dataValue;
+
+        public StepData(int step, String func, String stepJudge,
+                        String time, String scan, String volt, String limit, String dataValue) {
+            this.step = step;
+            this.func = func;
+            this.stepJudge = stepJudge;
+            this.time = time;
+            this.scan = scan;
+            this.volt = volt;
+            this.limit = limit;
+            this.dataValue = dataValue;
+        }
+
+        public int getStep() {
+            return step;
+        }
+
+        public String getFunc() {
+            return func;
+        }
+
+        public String getStepJudge() {
+            return stepJudge;
+        }
+
+        public String getTime() {
+            return time;
+        }
+
+        public String getScan() {
+            return scan;
+        }
+
+        public String getVolt() {
+            return volt;
+        }
+
+        public String getLimit() {
+            return limit;
+        }
+
+        public String getDataValue() {
+            return dataValue;
+        }
+
+        public boolean isOk() {
+            return "OK".equalsIgnoreCase(stepJudge) || "PASS".equalsIgnoreCase(stepJudge);
+        }
+    }
+
     private final boolean ok;
     private final boolean ok;
     private final String judge;
     private final String judge;
     private final String rawResponse;
     private final String rawResponse;
+    private final String totalJudge;
+    private final List<StepData> steps;
 
 
     public Ut5310TestResult(boolean ok, String judge, String rawResponse) {
     public Ut5310TestResult(boolean ok, String judge, String rawResponse) {
+        this(ok, judge, rawResponse, null, Collections.emptyList());
+    }
+
+    public Ut5310TestResult(boolean ok, String judge, String rawResponse,
+                            String totalJudge, List<StepData> steps) {
         this.ok = ok;
         this.ok = ok;
         this.judge = judge;
         this.judge = judge;
         this.rawResponse = rawResponse;
         this.rawResponse = rawResponse;
+        this.totalJudge = totalJudge;
+        this.steps = steps == null ? Collections.emptyList() : new ArrayList<>(steps);
     }
     }
 
 
     public boolean isOk() {
     public boolean isOk() {
@@ -23,4 +99,107 @@ public class Ut5310TestResult {
     public String getRawResponse() {
     public String getRawResponse() {
         return rawResponse;
         return rawResponse;
     }
     }
+
+    public String getTotalJudge() {
+        return totalJudge;
+    }
+
+    public List<StepData> getSteps() {
+        return Collections.unmodifiableList(steps);
+    }
+
+    public Integer getStep() {
+        if (steps.isEmpty()) {
+            return null;
+        }
+        return steps.get(steps.size() - 1).getStep();
+    }
+
+    public String getFunc() {
+        return lastStepField(StepData::getFunc);
+    }
+
+    public String getStepJudge() {
+        return lastStepField(StepData::getStepJudge);
+    }
+
+    public String getTime() {
+        return lastStepField(StepData::getTime);
+    }
+
+    public String getScan() {
+        return lastStepField(StepData::getScan);
+    }
+
+    public String getVolt() {
+        return lastStepField(StepData::getVolt);
+    }
+
+    public String getLimit() {
+        return lastStepField(StepData::getLimit);
+    }
+
+    public String getDataValue() {
+        return lastStepField(StepData::getDataValue);
+    }
+
+    private String lastStepField(java.util.function.Function<StepData, String> getter) {
+        if (steps.isEmpty()) {
+            return null;
+        }
+        return getter.apply(steps.get(steps.size() - 1));
+    }
+
+    public void printToConsole() {
+        System.out.println("========== UT5310 原始报文(Modbus RTU) ==========");
+        System.out.println(rawResponse == null ? "" : rawResponse);
+        System.out.println("========== UT5310 解析结果 ==========");
+        System.out.println("总判定(ok): " + ok);
+        System.out.println("总判定(judge): " + safe(judge));
+        if (totalJudge != null) {
+            System.out.println("RU总判读: " + totalJudge);
+        }
+        if (steps.isEmpty()) {
+            System.out.println("(无分步明细)");
+        } else {
+            for (StepData step : steps) {
+                System.out.println("--- 步骤 " + step.getStep() + " (" + safe(step.getFunc()) + ") ---");
+                System.out.println("测试类型: " + safe(step.getFunc()));
+                System.out.println("当步判读: " + safe(step.getStepJudge()));
+                System.out.println("测试电压(kV): " + safe(step.getVolt()));
+                System.out.println("测试值(电阻GΩ或电流μA): " + safe(step.getDataValue()));
+            }
+        }
+        System.out.println("====================================");
+    }
+
+    private static String safe(Object value) {
+        return value == null ? "" : value.toString();
+    }
+
+    /** 转为 mesProductInsulation2/batchUpload 接口所需的 steps JSON */
+    public JSONArray toUploadStepsArray() {
+        JSONArray arr = new JSONArray();
+        for (StepData step : steps) {
+            JSONObject obj = new JSONObject();
+            obj.put("step", String.valueOf(step.getStep()));
+            obj.put("voltage", safe(step.getVolt()));
+            obj.put("voltageUnit", "kV");
+            obj.put("data", safe(step.getDataValue()));
+            obj.put("dataUnit", dataUnitForStep(step.getStep()));
+            obj.put("result", step.isOk() ? "OK" : "NG");
+            arr.add(obj);
+        }
+        return arr;
+    }
+
+    private static String dataUnitForStep(int stepNo) {
+        if (stepNo == 1) {
+            return "GΩ";
+        }
+        if (stepNo == 2) {
+            return "μA";
+        }
+        return "";
+    }
 }
 }

+ 10 - 3
src/resources/config/config.properties

@@ -5,11 +5,18 @@
 mes.line_sn=XT
 mes.line_sn=XT
 mes.plc_ip=192.168.0.3
 mes.plc_ip=192.168.0.3
 mes.ut5310_enable=true
 mes.ut5310_enable=true
-mes.ut5310_port=COM1
+mes.ut5310_port=COM8
 mes.ut5310_baud_rate=9600
 mes.ut5310_baud_rate=9600
 mes.ut5310_data_bits=8
 mes.ut5310_data_bits=8
 mes.ut5310_stop_bits=1
 mes.ut5310_stop_bits=1
 mes.ut5310_parity=NONE
 mes.ut5310_parity=NONE
-mes.ut5310_read_timeout_ms=1000
-mes.ut5310_test_timeout_ms=60000
+mes.ut5310_read_timeout_ms=2000
+mes.ut5310_test_timeout_ms=300000
 mes.ut5310_fetch_interval_ms=500
 mes.ut5310_fetch_interval_ms=500
+mes.ut5310_protocol=MODBUS
+mes.ut5310_modbus_slave=1
+mes.ut5310_modbus_max_steps=4
+# 测试文件实际步骤数:PANEL_01=2(步骤1绝缘电阻 + 步骤2直流耐压),全部判读完成后才提交
+mes.ut5310_modbus_expected_steps=2
+# false=工装/PLC已触发测试,MES只读结果;true=MES通过0x0500发START
+mes.ut5310_modbus_send_start=true