Jelajahi Sumber

增加点位读取信息

jingbo 2 minggu lalu
induk
melakukan
d233fcc635

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

@@ -10,6 +10,7 @@ import java.io.*;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
@@ -118,6 +119,42 @@ public class DataUtil {
         }
     }
 
+    // 保存 PLC 读取到的点焊/焊接参数,复用后端 mesProductArcWelding/batchsave 接口。
+    public static JSONObject saveWeldingParams(String sn, String oprno, String lineSn, String serverIp, String content) {
+        try {
+            if (content == null || content.trim().isEmpty()) {
+                log.info("焊接参数为空,不上传,工位=" + oprno + ",工件码=" + sn);
+                return null;
+            }
+
+            String url = "http://" + serverIp + ":8980/js/a/mes/mesProductArcWelding/batchsave";
+            JSONArray paramsArray = new JSONArray();
+            JSONObject item = new JSONObject();
+            item.put("sn", sn);
+            item.put("oprno", oprno);
+            item.put("lineSn", lineSn);
+            item.put("content", content);
+            paramsArray.add(item);
+
+            String params = "__ajax=json&__sid=" + urlEncode(MesClient.sessionid)
+                    + "&params=" + urlEncode(paramsArray.toJSONString());
+            log.info("上传焊接参数:" + paramsArray.toJSONString());
+            String result = doPost(url, params);
+            log.info("上传焊接参数结果:" + result);
+            if (result == null || result.equalsIgnoreCase("false")) {
+                return null;
+            }
+            return JSONObject.parseObject(result);
+        } catch (Exception e) {
+            log.info("上传焊接参数异常:" + e.getMessage());
+            return null;
+        }
+    }
+
+    private static String urlEncode(String value) throws UnsupportedEncodingException {
+        return URLEncoder.encode(value == null ? "" : value, "UTF-8");
+    }
+
     public static String rightPad(final String str, final int size) {
         if (str == null) {
             return null;

+ 1 - 10
src/com/mes/ui/MultiStationClient.java

@@ -243,16 +243,7 @@ public class MultiStationClient extends JFrame {
             mainPanel.setBackground(Color.WHITE);
 
             for (StationConfig config : stations) {
-                WorkStationPanel panel = new WorkStationPanel(
-                    config.getGw(),
-                    config.getGwDes(),
-                    config.getLineSn(),
-                    config.getServerIp(),
-                    config.getAllowStart(),
-                    config.getAllowDown(),
-                    config.getStartMethod(),
-                    config.getStopMethod()
-                );
+                WorkStationPanel panel = new WorkStationPanel(config);
                 stationPanels.add(panel);
                 mainPanel.add(panel);
             }

+ 42 - 9
src/com/mes/ui/WorkStationPanel.java

@@ -3,6 +3,7 @@ package com.mes.ui;
 import com.alibaba.fastjson2.JSONObject;
 import com.mes.component.MesWebView;
 import com.mes.util.PLCUtils;
+import com.mes.util.StationConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -27,11 +28,13 @@ public class WorkStationPanel extends JPanel {
     private String allowDown;
     private String startMethod;
     private String stopMethod;
+    private StationConfig stationConfig;
     
     public int workStatus = 0;
     public int plcStatus = 0;
     public boolean checkQualityResult = false;
     private String currentUser = "system";
+    private String lastWeldingParamContent = "";
 
     public static List<String> hjparams = new ArrayList<>();
     
@@ -63,6 +66,21 @@ public class WorkStationPanel extends JPanel {
         startGetCurSn();
     }
 
+    public WorkStationPanel(StationConfig stationConfig) {
+        this.gw = stationConfig.getGw();
+        this.gwDes = stationConfig.getGwDes();
+        this.lineSn = stationConfig.getLineSn();
+        this.serverIp = stationConfig.getServerIp();
+        this.allowStart = stationConfig.getAllowStart();
+        this.allowDown = stationConfig.getAllowDown();
+        this.startMethod = stationConfig.getStartMethod();
+        this.stopMethod = stationConfig.getStopMethod();
+        this.stationConfig = stationConfig;
+        initUI();
+        startHeartBeatTimer();
+        startGetCurSn();
+    }
+
     private void initUI() {
         setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.BLACK), 
             gw + " - " + gwDes, 
@@ -292,6 +310,7 @@ public class WorkStationPanel extends JPanel {
         productSnField.setText("");
         finishOkButton.setEnabled(false);
         fxLabel.setVisible(false);
+        lastWeldingParamContent = "";
     }
 
     public void resetScanA() {
@@ -303,6 +322,7 @@ public class WorkStationPanel extends JPanel {
 //        finishNgButton.setEnabled(false);
             productSnField.setText("");
             fxLabel.setVisible(false);
+            lastWeldingParamContent = "";
 
             scanButton.setEnabled(true);
             statusLabel.setText("请扫工件码");
@@ -374,35 +394,44 @@ public class WorkStationPanel extends JPanel {
                     if (plcStatus == 0){
                         // 等待扫码
                         String sn = productSnField.getText().trim();
-                        if (sn.isEmpty()) {
+                        if (!sn.isEmpty()) {
                             plcStatus = 1;
                         }
                     } else if (plcStatus == 1) {
                         // 发送允许启动信号
-                        Boolean ret = PLCUtils.writeStartMethod(allowStart);
+                        Boolean ret = PLCUtils.writeStartMethod(stationConfig);
                         if (ret) {
                             plcStatus = 2;
                         }
                     } else if (plcStatus == 2) {
                         // 等待开始加工信号
-                        Boolean ret = PLCUtils.readStartMethod(startMethod);
+                        Boolean ret = PLCUtils.readStartMethod(stationConfig);
                         if (ret){
                             // 将允许启动置空
-                            boolean ret2 = PLCUtils.writeStopMethod(allowStart);
+                            boolean ret2 = PLCUtils.writeStopMethod(stationConfig);
                             if (ret2){
                                 plcStatus = 3;
                             }
                         }
                     } else if (plcStatus ==3) {
                         // 等待加工结束信号
-                        Boolean ret = PLCUtils.readStopMethod(stopMethod);
+                        Boolean ret = PLCUtils.readStopMethod(stationConfig);
                         if (ret){
                             // 发送结果
                             String sn = productSnField.getText().trim();
+                            if (lastWeldingParamContent == null || lastWeldingParamContent.trim().isEmpty()) {
+                                lastWeldingParamContent = PLCUtils.getWeldingParamContent(stationConfig);
+                            }
                             JSONObject retObj = DataUtil.sendQuality( sn,"OK", currentUser, gw, lineSn, serverIp);
                             if (retObj != null && "true".equalsIgnoreCase(retObj.getString("result"))) {
+                                if (lastWeldingParamContent != null && !lastWeldingParamContent.trim().isEmpty()) {
+                                    JSONObject saveParamRet = DataUtil.saveWeldingParams(sn, gw, lineSn, serverIp, lastWeldingParamContent);
+                                    if (saveParamRet == null || !"true".equalsIgnoreCase(saveParamRet.getString("result"))) {
+                                        log.warn("焊接参数上传失败,工位={}, 工件码={}", gw, sn);
+                                    }
+                                }
                                 // 发送允许下料
-                                PLCUtils.writeDownMethod(allowDown);
+                                PLCUtils.writeDownMethod(stationConfig);
                                 statusLabel.setForeground(Color.GREEN);
                                 statusLabel.setText("结果提交成功,请扫下一件");
                                 finishNgButton.setEnabled(false);
@@ -414,13 +443,17 @@ public class WorkStationPanel extends JPanel {
                             }
                         }else {
                             // 获取参数
-                            PLCUtils.getParameter();
+                            lastWeldingParamContent = PLCUtils.getWeldingParamContent(stationConfig);
+                            hjparams = new ArrayList<>();
+                            if (lastWeldingParamContent != null && !lastWeldingParamContent.trim().isEmpty()) {
+                                hjparams.add(lastWeldingParamContent);
+                            }
                         }
                     }else if (plcStatus == 4){
                         // 等待加工结束为空
-                        Boolean ret = PLCUtils.readStopMethod(stopMethod);
+                        Boolean ret = PLCUtils.readStopMethod(stationConfig);
                         if (!ret){
-                            PLCUtils.writeDownStopMethod(allowDown);
+                            PLCUtils.writeDownStopMethod(stationConfig);
                             resetState();
                         }
                     }

+ 585 - 0
src/com/mes/util/PLCPointTestMain.java

@@ -0,0 +1,585 @@
+package com.mes.util;
+
+import com.github.xingshuangs.iot.protocol.melsec.enums.EMcSeries;
+import com.github.xingshuangs.iot.protocol.melsec.service.McPLC;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Scanner;
+
+public class PLCPointTestMain {
+
+    private static final int Q26_PORT = 8000;
+    private static final int FX5U_SLMP_PORT = 5001;
+    private static final int CONNECT_TIMEOUT_MS = 2000;
+    private static final List<StationPoint> STATIONS = new ArrayList<>();
+
+    static {
+        STATIONS.add(StationPoint.spot("OP40", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6601", "M6602", "M6603", "M6604", "M6605",
+                "M6700", "M6750", "M6800", "M6850",
+                "D7001", "D7002", "D7051", "D7061", "D7052", "D7062"));
+        STATIONS.add(StationPoint.bitOnly("OP70", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6611", "M6612", "M6613", "M6614", "M6615",
+                "M6900", "M6950", "M7000", "M7050"));
+        STATIONS.add(StationPoint.spot("OP30", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6621", "M6622", "M6623", "M6624", "M6625",
+                "M7100", "M7150", "M7200", "M7250",
+                "D7003", "D7004", "D7071", "D7081", "D7072", "D7082"));
+        STATIONS.add(StationPoint.bitOnly("OP80", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6631", "M6632", "M6633", "M6634", "M6635",
+                "M7300", "M7350", "M7400", "M7450"));
+        STATIONS.add(StationPoint.spot("OP20", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6641", "M6642", "M6643", "M6644", "M6645",
+                "M7500", "M7550", "M7600", "M7650",
+                "D7005", "D7006", "D7091", "D7101", "D7092", "D7102"));
+        STATIONS.add(StationPoint.bitOnly("OP90", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6651", "M6652", "M6653", "M6654", "M6655",
+                "M7700", "M7750", "M7800", "M7850"));
+        STATIONS.add(StationPoint.arc("OP10", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6661", "M6662", "M6663", "M6664", "M6665",
+                "4号机器人", "D7041", "D7042",
+                "3号机器人", "D7031", "D7032"));
+        STATIONS.add(StationPoint.simple("OP100", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6671", "M6672", "M6673", "M6674", "M6675"));
+        STATIONS.add(StationPoint.arc("OP120", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6681", "M6682", "M6683", "M6684", "M6685",
+                "2号机器人", "D7021", "D7022",
+                "1号机器人", "D7011", "D7012"));
+        STATIONS.add(StationPoint.simple("OP110", "192.168.0.35", Q26_PORT, EMcSeries.QnA,
+                "M6691", "M6692", "M6693", "M6694", "M6695"));
+
+        // 192.168.0.10 是 SLMP 通讯,现场测试 5001 端口可读。
+        STATIONS.add(StationPoint.spot("OP50", "192.168.0.10", FX5U_SLMP_PORT, EMcSeries.Q_L,
+                "M6001", "M6002", "M6003", "M6004", "M6005",
+                "M6100", "M6150", "M6300", "M6350",
+                "D6001", "D6011", "D6002", "D6012", "D6003", "D6013"));
+        STATIONS.add(StationPoint.bitOnly("OP60", "192.168.0.10", FX5U_SLMP_PORT, EMcSeries.Q_L,
+                "M6011", "M6012", "M6013", "M6014", "M6015",
+                "M6200", "M6250", "M6400", "M6450"));
+    }
+
+    public static void main(String[] args) {
+        System.out.println("PLC点位测试工具启动:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+        System.out.println("说明:输入工位号读取该工位点位;输入 ALL 读取全部;输入 Q 退出。");
+        printStationList();
+
+        Scanner scanner = new Scanner(System.in);
+        while (true) {
+            System.out.println();
+            System.out.print("请输入要测试的工位号,例如 OP40 / OP50 / ALL / Q:");
+            String input = scanner.nextLine();
+            if (input == null) {
+                continue;
+            }
+
+            String command = input.trim().toUpperCase(Locale.ROOT);
+            if (command.isEmpty()) {
+                continue;
+            }
+            if ("Q".equals(command) || "QUIT".equals(command) || "EXIT".equals(command)) {
+                System.out.println("已退出PLC点位测试。");
+                break;
+            }
+            if ("ALL".equals(command) || "全部".equals(command)) {
+                for (StationPoint station : STATIONS) {
+                    testStation(station);
+                }
+                continue;
+            }
+
+            StationPoint station = findStation(command);
+            if (station == null) {
+                System.out.println("未找到工位:" + command);
+                printStationList();
+                continue;
+            }
+
+            handleStation(scanner, station);
+        }
+    }
+
+    private static void handleStation(Scanner scanner, StationPoint station) {
+        while (true) {
+            System.out.println();
+            System.out.println("已选择工位:" + station.code + "(PLC:" + station.ip + ":" + station.port
+                    + ",系列:" + station.series + ",允许启动点位:" + station.allowStart + ")");
+            System.out.println("请选择操作:");
+            System.out.println("  1:读取该工位全部点位");
+            System.out.println("  2:手动写允许启动 = true");
+            System.out.println("  3:手动写允许启动 = false");
+            System.out.println("  4:测试该PLC常见端口");
+            System.out.println("  5:临时修改该工位PLC端口");
+            System.out.println("  6:临时切换该工位PLC系列");
+            System.out.println("  7:诊断允许启动写入方式");
+            System.out.println("  B:返回工位选择");
+            System.out.print("请输入:");
+
+            String input = scanner.nextLine();
+            if (input == null) {
+                continue;
+            }
+            String command = input.trim().toUpperCase(Locale.ROOT);
+            if (command.isEmpty()) {
+                continue;
+            }
+            if ("B".equals(command) || "BACK".equals(command)) {
+                return;
+            }
+            if ("1".equals(command) || "R".equals(command) || "READ".equals(command) || "读取".equals(command)) {
+                testStation(station);
+            } else if ("2".equals(command) || "T".equals(command) || "TRUE".equals(command)) {
+                writeAllowStart(station, true);
+            } else if ("3".equals(command) || "F".equals(command) || "FALSE".equals(command)) {
+                writeAllowStart(station, false);
+            } else if ("4".equals(command) || "PORT".equals(command)) {
+                testPorts(station.ip);
+            } else if ("5".equals(command) || "SETPORT".equals(command)) {
+                changeStationPort(scanner, station);
+            } else if ("6".equals(command) || "SETSERIES".equals(command)) {
+                changeStationSeries(scanner, station);
+            } else if ("7".equals(command) || "WRITEDEBUG".equals(command)) {
+                diagnoseAllowStartWrite(scanner, station);
+            } else {
+                System.out.println("输入无效,请输入 1 / 2 / 3 / 4 / 5 / 6 / 7 / B。");
+            }
+        }
+    }
+
+    private static void testStation(StationPoint station) {
+        printStationTitle(station);
+        printPlcConnectTip(station);
+
+        McPLC plc = createPlc(station);
+        try {
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(3000);
+
+            readBoolean(plc, "允许启动", station.allowStart);
+            readBoolean(plc, "允许下料", station.allowDown);
+            readBoolean(plc, "开始加工", station.start);
+            readBoolean(plc, "加工结束", station.finish);
+            readBoolean(plc, "故障", station.fault);
+
+            if ("spot".equals(station.type) || "bitOnly".equals(station.type)) {
+                readBooleanRange(plc, "1号机器人焊接开始", station.robot1Start, 50);
+                readBooleanRange(plc, "1号机器人焊接完成", station.robot1Done, 50);
+                readBooleanRange(plc, "2号机器人焊接开始", station.robot2Start, 50);
+                readBooleanRange(plc, "2号机器人焊接完成", station.robot2Done, 50);
+            }
+
+            if ("spot".equals(station.type)) {
+                readInt16(plc, "1号机器人压力", station.robot1Pressure);
+                readInt16(plc, "2号机器人压力", station.robot2Pressure);
+                readScaledInt16(plc, "1号机器人焊接电流(A)", station.robot1Current, 100, false);
+                readScaledInt16(plc, "2号机器人焊接电流(A)", station.robot2Current, 100, false);
+                readScaledInt16(plc, "1号机器人焊接时间(ms)", station.robot1Time, 20, true);
+                readScaledInt16(plc, "2号机器人焊接时间(ms)", station.robot2Time, 20, true);
+            } else if ("arc".equals(station.type)) {
+                readInt16(plc, station.upperRobotName + "电流", station.upperCurrent);
+                readScaledInt16(plc, station.upperRobotName + "电压(V)", station.upperVoltage, 10, false);
+                readInt16(plc, station.lowerRobotName + "电流", station.lowerCurrent);
+                readScaledInt16(plc, station.lowerRobotName + "电压(V)", station.lowerVoltage, 10, false);
+            }
+        } finally {
+            plc.close();
+        }
+    }
+
+    private static void writeAllowStart(StationPoint station, boolean value) {
+        System.out.println();
+        System.out.println("准备写入:" + station.code + " 允许启动 " + station.allowStart + " = " + value);
+        printPlcConnectTip(station);
+
+        McPLC plc = createPlc(station);
+        try {
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(3000);
+
+            System.out.println("写入方式:单点写。");
+            System.out.println("  " + station.allowStart + " = " + value);
+            plc.writeBoolean(station.allowStart, value);
+            System.out.println("写入命令已完成,正在重新连接读取确认:");
+        } catch (Exception e) {
+            printError("允许启动写入", station.allowStart, e);
+        } finally {
+            plc.close();
+        }
+
+        sleep(300);
+        confirmAllowStart(station);
+    }
+
+    private static void diagnoseAllowStartWrite(Scanner scanner, StationPoint station) {
+        System.out.println();
+        System.out.println("写入诊断会直接写 " + station.allowStart + ",请确认设备处于允许测试状态。");
+        System.out.print("请输入要写入的值 true / false,或输入 B 返回:");
+        String input = scanner.nextLine();
+        if (input == null) {
+            return;
+        }
+        String command = input.trim().toUpperCase(Locale.ROOT);
+        if ("B".equals(command) || "BACK".equals(command)) {
+            return;
+        }
+        if (!"TRUE".equals(command) && !"FALSE".equals(command)) {
+            System.out.println("输入无效,本次不写入。");
+            return;
+        }
+
+        boolean value = Boolean.parseBoolean(command.toLowerCase(Locale.ROOT));
+        System.out.println("诊断目标:" + station.code + " " + station.ip + ":" + station.port
+                + ",系列:" + station.series + ",点位:" + station.allowStart + " = " + value);
+
+        System.out.println();
+        System.out.println("步骤1:写入前读取确认。");
+        confirmAllowStart(station);
+
+        System.out.println();
+        System.out.println("步骤2:单点写入测试。");
+        writeAllowStartSingle(station, value);
+        sleep(800);
+        confirmAllowStart(station);
+
+        System.out.println();
+        System.out.println("步骤3:连续2个位写入测试。");
+        writeAllowStartBatch(station, value);
+        sleep(800);
+        confirmAllowStart(station);
+
+        System.out.println();
+        System.out.println("诊断说明:如果单点写成功、连续写失败,菜单2/3应使用单点写;说明该PLC不适合用连续位写方式操作允许启动。");
+    }
+
+    private static void writeAllowStartSingle(StationPoint station, boolean value) {
+        printPlcConnectTip(station);
+        McPLC plc = createPlc(station);
+        try {
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(3000);
+            System.out.println("  单点写:" + station.allowStart + " = " + value);
+            plc.writeBoolean(station.allowStart, value);
+            System.out.println("  单点写命令已完成。");
+        } catch (Exception e) {
+            printError("单点写允许启动", station.allowStart, e);
+        } finally {
+            plc.close();
+        }
+    }
+
+    private static void writeAllowStartBatch(StationPoint station, boolean value) {
+        printPlcConnectTip(station);
+        McPLC plc = createPlc(station);
+        try {
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(3000);
+            boolean allowDownNow = plc.readBoolean(station.allowDown);
+            System.out.println("  连续写2个位:" + station.allowStart + " = " + value
+                    + "," + station.allowDown + " = " + allowDownNow + "(保持原值)");
+            plc.writeBoolean(station.allowStart, Arrays.asList(value, allowDownNow));
+            System.out.println("  连续写命令已完成。");
+        } catch (Exception e) {
+            printError("连续写允许启动", station.allowStart, e);
+        } finally {
+            plc.close();
+        }
+    }
+
+    private static void confirmAllowStart(StationPoint station) {
+        McPLC plc = createPlc(station);
+        try {
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(3000);
+            readBoolean(plc, "允许启动确认", station.allowStart);
+            readBoolean(plc, "允许下料确认", station.allowDown);
+        } catch (Exception e) {
+            printError("写入后确认读取", station.allowStart, e);
+        } finally {
+            plc.close();
+        }
+    }
+
+    private static McPLC createPlc(StationPoint station) {
+        return new McPLC(station.series, station.ip, station.port);
+    }
+
+    private static void printPlcConnectTip(StationPoint station) {
+        System.out.println("准备通过SLMP/MC连接PLC:" + station.ip + ":" + station.port
+                + ",系列:" + station.series + ",超时 " + CONNECT_TIMEOUT_MS + "ms ...");
+    }
+
+    private static boolean checkConnection(String ip, int port) {
+        System.out.println("正在连接PLC:" + ip + ":" + port + ",超时 " + CONNECT_TIMEOUT_MS + "ms ...");
+        try (Socket socket = new Socket()) {
+            socket.connect(new InetSocketAddress(ip, port), CONNECT_TIMEOUT_MS);
+            System.out.println("PLC连接成功。");
+            return true;
+        } catch (IOException e) {
+            System.out.println("PLC连接失败:" + e.getMessage());
+            System.out.println("请检查:IP能ping通不等于SLMP/MC端口可用,需要确认PLC开放的TCP端口。");
+            return false;
+        }
+    }
+
+    private static void testPorts(String ip) {
+        int[] ports = {5000, 5001, 5002, 8000};
+        System.out.println("开始测试 " + ip + " 的常见SLMP/MC端口...");
+        for (int port : ports) {
+            boolean ok = checkConnection(ip, port);
+            System.out.println("  " + ip + ":" + port + " -> " + (ok ? "通" : "不通"));
+        }
+    }
+
+    private static void changeStationPort(Scanner scanner, StationPoint station) {
+        System.out.println("当前工位 " + station.code + " 端口=" + station.port);
+        System.out.print("请输入新端口,例如 5000 / 5001 / 8000:");
+        String input = scanner.nextLine();
+        try {
+            int port = Integer.parseInt(input.trim());
+            station.port = port;
+            System.out.println("已将 " + station.code + " 端口改为 " + port + ",可立即输入 1 读取测试。");
+        } catch (Exception e) {
+            System.out.println("端口输入无效。");
+        }
+    }
+
+    private static void changeStationSeries(Scanner scanner, StationPoint station) {
+        System.out.println("当前工位 " + station.code + " 系列=" + station.series);
+        System.out.println("可选:1=QnA,2=Q_L,3=IQ_R");
+        System.out.print("请输入:");
+        String input = scanner.nextLine();
+        if ("1".equals(input.trim())) {
+            station.series = EMcSeries.QnA;
+        } else if ("2".equals(input.trim())) {
+            station.series = EMcSeries.Q_L;
+        } else if ("3".equals(input.trim())) {
+            station.series = EMcSeries.IQ_R;
+        } else {
+            System.out.println("系列输入无效。");
+            return;
+        }
+        System.out.println("已将 " + station.code + " 系列改为 " + station.series + ",可立即输入 1 读取测试。");
+    }
+
+    private static void readBoolean(McPLC plc, String name, String address) {
+        try {
+            System.out.printf("  %-24s %-12s = %s%n", name, address, toChineseBoolean(plc.readBoolean(address)));
+        } catch (Exception e) {
+            printError(name, address, e);
+        }
+    }
+
+    private static void readBooleanRange(McPLC plc, String name, String startAddress, int count) {
+        try {
+            List<Boolean> values = plc.readBoolean(startAddress, count);
+            System.out.printf("  %-24s %-12s 置位数量=%d 明细=%s%n",
+                    name, formatBitRange(startAddress, count), countTrue(values), toChineseBooleanList(values));
+        } catch (Exception e) {
+            printError(name, startAddress, e);
+        }
+    }
+
+    private static void readInt16(McPLC plc, String name, String address) {
+        try {
+            System.out.printf("  %-24s %-12s = %d%n", name, address, plc.readInt16(address));
+        } catch (Exception e) {
+            printError(name, address, e);
+        }
+    }
+
+    private static void readScaledInt16(McPLC plc, String name, String address, int factor, boolean multiply) {
+        try {
+            short raw = plc.readInt16(address);
+            double value = multiply ? raw * factor : raw * 1.0 / factor;
+            String formula = multiply ? "原始值*" + factor : "原始值/" + factor;
+            System.out.printf(Locale.ROOT, "  %-24s %-12s 原始值=%d 换算值=%.2f(%s)%n",
+                    name, address, raw, value, formula);
+        } catch (Exception e) {
+            printError(name, address, e);
+        }
+    }
+
+    private static StationPoint findStation(String stationCode) {
+        for (StationPoint station : STATIONS) {
+            if (station.code.equalsIgnoreCase(stationCode)) {
+                return station;
+            }
+        }
+        return null;
+    }
+
+    private static void printStationList() {
+        System.out.println("可测试工位:");
+        for (StationPoint station : STATIONS) {
+            System.out.println("  " + station.code + "(PLC:" + station.ip + ":" + station.port
+                    + ",系列:" + station.series + ",类型:" + station.typeName() + ")");
+        }
+    }
+
+    private static int countTrue(List<Boolean> values) {
+        int count = 0;
+        for (Boolean value : values) {
+            if (Boolean.TRUE.equals(value)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private static String formatBitRange(String startAddress, int count) {
+        String prefix = startAddress.substring(0, 1);
+        int start = Integer.parseInt(startAddress.substring(1));
+        return prefix + start + "-" + prefix + (start + count - 1);
+    }
+
+    private static String toChineseBoolean(boolean value) {
+        return value ? "有信号(true)" : "无信号(false)";
+    }
+
+    private static String toChineseBooleanList(List<Boolean> values) {
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < values.size(); i++) {
+            if (i > 0) {
+                sb.append(",");
+            }
+            sb.append(Boolean.TRUE.equals(values.get(i)) ? "1" : "0");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private static void printStationTitle(StationPoint station) {
+        System.out.println();
+        System.out.println("============================================================");
+        System.out.println("开始读取:" + station.code + ",PLC:" + station.ip + ":" + station.port
+                + ",系列:" + station.series + ",类型:" + station.typeName());
+        System.out.println("============================================================");
+    }
+
+    private static void printError(String name, String address, Exception e) {
+        System.out.printf("  %-24s %-12s 操作失败:%s%n", name, address, e.getMessage());
+    }
+
+    private static void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    private static class StationPoint {
+        String code;
+        String ip;
+        int port;
+        EMcSeries series;
+        String type;
+        String allowStart;
+        String allowDown;
+        String start;
+        String finish;
+        String fault;
+        String robot1Start;
+        String robot1Done;
+        String robot2Start;
+        String robot2Done;
+        String robot1Pressure;
+        String robot2Pressure;
+        String robot1Current;
+        String robot2Current;
+        String robot1Time;
+        String robot2Time;
+        String upperRobotName;
+        String upperCurrent;
+        String upperVoltage;
+        String lowerRobotName;
+        String lowerCurrent;
+        String lowerVoltage;
+
+        static StationPoint simple(String code, String ip, int port, EMcSeries series,
+                                   String allowStart, String allowDown, String start,
+                                   String finish, String fault) {
+            return base(code, ip, port, series, "simple", allowStart, allowDown, start, finish, fault);
+        }
+
+        static StationPoint bitOnly(String code, String ip, int port, EMcSeries series,
+                                    String allowStart, String allowDown, String start,
+                                    String finish, String fault, String robot1Start, String robot1Done,
+                                    String robot2Start, String robot2Done) {
+            StationPoint point = base(code, ip, port, series, "bitOnly", allowStart, allowDown, start, finish, fault);
+            point.robot1Start = robot1Start;
+            point.robot1Done = robot1Done;
+            point.robot2Start = robot2Start;
+            point.robot2Done = robot2Done;
+            return point;
+        }
+
+        static StationPoint spot(String code, String ip, int port, EMcSeries series,
+                                 String allowStart, String allowDown, String start,
+                                 String finish, String fault, String robot1Start, String robot1Done,
+                                 String robot2Start, String robot2Done, String robot1Pressure, String robot2Pressure,
+                                 String robot1Current, String robot2Current, String robot1Time, String robot2Time) {
+            StationPoint point = bitOnly(code, ip, port, series, allowStart, allowDown, start, finish, fault,
+                    robot1Start, robot1Done, robot2Start, robot2Done);
+            point.type = "spot";
+            point.robot1Pressure = robot1Pressure;
+            point.robot2Pressure = robot2Pressure;
+            point.robot1Current = robot1Current;
+            point.robot2Current = robot2Current;
+            point.robot1Time = robot1Time;
+            point.robot2Time = robot2Time;
+            return point;
+        }
+
+        static StationPoint arc(String code, String ip, int port, EMcSeries series,
+                                String allowStart, String allowDown, String start,
+                                String finish, String fault, String upperRobotName, String upperCurrent,
+                                String upperVoltage, String lowerRobotName, String lowerCurrent, String lowerVoltage) {
+            StationPoint point = base(code, ip, port, series, "arc", allowStart, allowDown, start, finish, fault);
+            point.upperRobotName = upperRobotName;
+            point.upperCurrent = upperCurrent;
+            point.upperVoltage = upperVoltage;
+            point.lowerRobotName = lowerRobotName;
+            point.lowerCurrent = lowerCurrent;
+            point.lowerVoltage = lowerVoltage;
+            return point;
+        }
+
+        private static StationPoint base(String code, String ip, int port, EMcSeries series, String type,
+                                         String allowStart, String allowDown,
+                                         String start, String finish, String fault) {
+            StationPoint point = new StationPoint();
+            point.code = code;
+            point.ip = ip;
+            point.port = port;
+            point.series = series;
+            point.type = type;
+            point.allowStart = allowStart;
+            point.allowDown = allowDown;
+            point.start = start;
+            point.finish = finish;
+            point.fault = fault;
+            return point;
+        }
+
+        String typeName() {
+            if ("spot".equals(type)) {
+                return "点焊";
+            }
+            if ("arc".equals(type)) {
+                return "弧焊";
+            }
+            if ("bitOnly".equals(type)) {
+                return "焊点信号";
+            }
+            return "基础信号";
+        }
+    }
+}

+ 314 - 18
src/com/mes/util/PLCUtils.java

@@ -1,59 +1,355 @@
 package com.mes.util;
 
-import com.github.xingshuangs.iot.protocol.melsec.service.McPLC;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
 import com.github.xingshuangs.iot.protocol.melsec.enums.EMcSeries;
+import com.github.xingshuangs.iot.protocol.melsec.service.McPLC;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
 
 public class PLCUtils {
 
-    private static McPLC plc = new McPLC(EMcSeries.QnA, "192.168.0.35", 8000);
+    private static final Logger log = LoggerFactory.getLogger(PLCUtils.class);
+    private static final int CONNECT_TIMEOUT_MS = 2000;
+    private static final int RECEIVE_TIMEOUT_MS = 3000;
 
-    // 写入允许启动方法
+    private static McPLC defaultPlc = new McPLC(EMcSeries.QnA, "192.168.0.35", 8000);
+
+    // 旧代码还会直接传点位地址,这里保留默认 PLC,避免影响旧调用。
     public static boolean writeStartMethod(String allowStart) {
-        // 对plc写入ture
-        plc.writeBoolean(allowStart, true);
+        defaultPlc.writeBoolean(allowStart, true);
+        return true;
+    }
+
+    // 正式多工位流程走这个方法:根据配置里的 PLC IP、端口、系列去写允许启动。
+    public static boolean writeStartMethod(StationConfig config) {
+        writeBoolean(config, config.getAllowStart(), true);
         return true;
     }
 
-    // 将允许启动置空
+    // 旧调用:将允许启动置空
     public static boolean writeStopMethod(String allowStart) {
+        defaultPlc.writeBoolean(allowStart, false);
+        return true;
+    }
 
-        plc.writeBoolean(allowStart, false);
+    public static boolean writeStopMethod(StationConfig config) {
+        writeBoolean(config, config.getAllowStart(), false);
         return true;
     }
 
-    // 读取PLC开始加工信号
+    // 旧调用:读取 PLC 开始加工信号
     public static boolean readStartMethod(String startMethod) {
+        return defaultPlc.readBoolean(startMethod);
+    }
 
-        return plc.readBoolean(startMethod);
+    public static boolean readStartMethod(StationConfig config) {
+        return readBoolean(config, config.getStartMethod());
     }
 
-    // 读取PLC结束加工信号
+    // 旧调用:读取 PLC 加工结束信号
     public static boolean readStopMethod(String stopMethod) {
+        return defaultPlc.readBoolean(stopMethod);
+    }
 
-        return plc.readBoolean(stopMethod);
+    public static boolean readStopMethod(StationConfig config) {
+        return readBoolean(config, config.getStopMethod());
     }
 
-    // 给出允许下料信号
+    // 旧调用:给出允许下料信号
     public static boolean writeDownMethod(String allowDown) {
+        defaultPlc.writeBoolean(allowDown, true);
+        return true;
+    }
 
-        plc.writeBoolean(allowDown, true);
+    public static boolean writeDownMethod(StationConfig config) {
+        writeBoolean(config, config.getAllowDown(), true);
         return true;
     }
 
-    // 允许下料置空
+    // 旧调用:允许下料置空
     public static boolean writeDownStopMethod(String allowDown) {
+        defaultPlc.writeBoolean(allowDown, false);
+        return true;
+    }
 
-        plc.writeBoolean(allowDown, false);
+    public static boolean writeDownStopMethod(StationConfig config) {
+        writeBoolean(config, config.getAllowDown(), false);
         return true;
     }
 
-    // 获取参数
+    // 旧调用保留,不读取任何工位参数。
     public static void getParameter() {
-        //  TODO
-        return;
+        getParameter((StationConfig) null);
+    }
+
+    // 通过 MES 工位号查配置,再读取该工位参数。
+    public static List<String> getParameter(String gw) {
+        try {
+            for (StationConfig config : StationConfig.loadAllStations()) {
+                if (config.getGw().equalsIgnoreCase(gw)) {
+                    return getParameter(config);
+                }
+            }
+        } catch (Exception e) {
+            log.error("加载工位配置失败,工位=" + gw, e);
+        }
+        return Collections.emptyList();
+    }
+
+    // 按配置读取参数:弧焊和点焊点位不一样,所以分开处理。
+    public static List<String> getParameter(StationConfig config) {
+        String content = getWeldingParamContent(config);
+        if (isEmpty(content)) {
+            return Collections.emptyList();
+        }
+        List<String> values = new ArrayList<>();
+        values.add(content);
+        return values;
+    }
+
+    // 生成后端 batchsave 接口要求的 content JSON 字符串。
+    public static String getWeldingParamContent(StationConfig config) {
+        if (config == null || config.getWeldType() == null || config.getWeldType().trim().isEmpty()) {
+            return "";
+        }
+
+        McPLC plc = createPlc(config);
+        try {
+            if ("arc".equalsIgnoreCase(config.getWeldType())) {
+                return buildArcWeldContent(plc, config).toJSONString();
+            }
+            if ("spot".equalsIgnoreCase(config.getWeldType())) {
+                return buildSpotWeldContent(plc, config).toJSONString();
+            }
+            log.info("未配置该工位的PLC参数点位,工位={}", config.getGw());
+            return "";
+        } catch (Exception e) {
+            log.error("读取PLC参数失败,工位=" + config.getGw(), e);
+            return "";
+        } finally {
+            plc.close();
+        }
+    }
+
+    // 所有 M 点写入统一走单点写,现场验证 192.168.0.10 不适合连续位写。
+    private static void writeBoolean(StationConfig config, String address, boolean value) {
+        if (isEmpty(address)) {
+            log.warn("PLC写入跳过:点位地址为空,工位={}", config == null ? "" : config.getGw());
+            return;
+        }
+
+        McPLC plc = createPlc(config);
+        try {
+            // 现场验证:192.168.0.10 的SLMP端口支持单点写,不适合连续位写。
+            plc.writeBoolean(address, value);
+        } finally {
+            plc.close();
+        }
+    }
+
+    // 读取单个 M 点状态,例如开始加工、加工结束。
+    private static boolean readBoolean(StationConfig config, String address) {
+        if (isEmpty(address)) {
+            log.warn("PLC读取跳过:点位地址为空,工位={}", config == null ? "" : config.getGw());
+            return false;
+        }
+
+        McPLC plc = createPlc(config);
+        try {
+            return plc.readBoolean(address);
+        } finally {
+            plc.close();
+        }
+    }
+
+    // 弧焊参数:目前只读电流、电压。倍率在这里统一换算,后面存储可直接用。
+    private static List<String> readArcWeldParams(McPLC plc, StationConfig config) {
+        List<String> values = baseValues(config, "弧焊");
+        addScaledInt16(plc, values, "1号机器人电流", config.getRobot1Current(), 1, false);
+        addScaledInt16(plc, values, "1号机器人电压", config.getRobot1Voltage(), 10, false);
+        addScaledInt16(plc, values, "2号机器人电流", config.getRobot2Current(), 1, false);
+        addScaledInt16(plc, values, "2号机器人电压", config.getRobot2Voltage(), 10, false);
+        return values;
+    }
+
+    private static JSONObject buildArcWeldContent(McPLC plc, StationConfig config) {
+        JSONObject content = baseContent(config, "arc");
+        JSONObject sample = new JSONObject();
+        sample.put("time", currentTime());
+        addScaledNumber(plc, sample, "robot1Current", config.getRobot1Current(), 1, false);
+        addScaledNumber(plc, sample, "robot1Voltage", config.getRobot1Voltage(), 10, false);
+        addScaledNumber(plc, sample, "robot2Current", config.getRobot2Current(), 1, false);
+        addScaledNumber(plc, sample, "robot2Voltage", config.getRobot2Voltage(), 10, false);
+
+        JSONArray samples = new JSONArray();
+        samples.add(sample);
+        content.put("samples", samples);
+        return content;
+    }
+
+    // 点焊参数:先读焊接开始/完成数量,再读压力、电流、时间。
+    private static List<String> readSpotWeldParams(McPLC plc, StationConfig config) {
+        List<String> values = baseValues(config, "点焊");
+        values.add("1号机器人焊接开始数量=" + countTrue(plc.readBoolean(config.getRobot1Start(), 50)));
+        values.add("1号机器人焊接完成数量=" + countTrue(plc.readBoolean(config.getRobot1Done(), 50)));
+        values.add("2号机器人焊接开始数量=" + countTrue(plc.readBoolean(config.getRobot2Start(), 50)));
+        values.add("2号机器人焊接完成数量=" + countTrue(plc.readBoolean(config.getRobot2Done(), 50)));
+        addInt16(plc, values, "1号机器人压力", config.getRobot1Pressure());
+        addInt16(plc, values, "2号机器人压力", config.getRobot2Pressure());
+        addScaledInt16(plc, values, "1号机器人电流", config.getRobot1Current(), 100, false);
+        addScaledInt16(plc, values, "2号机器人电流", config.getRobot2Current(), 100, false);
+        addScaledInt16(plc, values, "1号机器人焊接时间", config.getRobot1Time(), 20, true);
+        addScaledInt16(plc, values, "2号机器人焊接时间", config.getRobot2Time(), 20, true);
+        return values;
+    }
+
+    private static JSONObject buildSpotWeldContent(McPLC plc, StationConfig config) {
+        List<Boolean> robot1Start = plc.readBoolean(config.getRobot1Start(), 50);
+        List<Boolean> robot1Done = plc.readBoolean(config.getRobot1Done(), 50);
+        List<Boolean> robot2Start = plc.readBoolean(config.getRobot2Start(), 50);
+        List<Boolean> robot2Done = plc.readBoolean(config.getRobot2Done(), 50);
+
+        JSONObject content = baseContent(config, "spot");
+        JSONObject sample = new JSONObject();
+        sample.put("time", currentTime());
+        addIntNumber(plc, sample, "robot1Pressure", config.getRobot1Pressure());
+        addIntNumber(plc, sample, "robot2Pressure", config.getRobot2Pressure());
+        addScaledNumber(plc, sample, "robot1Current", config.getRobot1Current(), 100, false);
+        addScaledNumber(plc, sample, "robot2Current", config.getRobot2Current(), 100, false);
+        addScaledNumber(plc, sample, "robot1WeldTime", config.getRobot1Time(), 20, true);
+        addScaledNumber(plc, sample, "robot2WeldTime", config.getRobot2Time(), 20, true);
+
+        JSONObject spot = new JSONObject();
+        JSONObject robot1 = new JSONObject();
+        robot1.put("startPoints", toPointNumbers(robot1Start));
+        robot1.put("finishPoints", toPointNumbers(robot1Done));
+        JSONObject robot2 = new JSONObject();
+        robot2.put("startPoints", toPointNumbers(robot2Start));
+        robot2.put("finishPoints", toPointNumbers(robot2Done));
+        spot.put("robot1", robot1);
+        spot.put("robot2", robot2);
+        sample.put("spot", spot);
+
+        JSONArray samples = new JSONArray();
+        samples.add(sample);
+        content.put("samples", samples);
+
+        JSONObject summary = new JSONObject();
+        summary.put("robot1StartCount", countTrue(robot1Start));
+        summary.put("robot1FinishCount", countTrue(robot1Done));
+        summary.put("robot2StartCount", countTrue(robot2Start));
+        summary.put("robot2FinishCount", countTrue(robot2Done));
+        content.put("summary", summary);
+        return content;
+    }
+
+    private static JSONObject baseContent(StationConfig config, String weldingType) {
+        JSONObject content = new JSONObject();
+        content.put("version", 2);
+        content.put("weldingType", weldingType);
+        content.put("mesOprno", config.getGw());
+        content.put("plcOprno", config.getPlcStation());
+        return content;
+    }
+
+    // 公共基础信息,后续存数据库时可以直接带上 MES 工位和 PLC 工位。
+    private static List<String> baseValues(StationConfig config, String type) {
+        List<String> values = new ArrayList<>();
+        values.add("MES工位=" + config.getGw());
+        values.add("PLC工位=" + config.getPlcStation());
+        values.add("焊接类型=" + type);
+        return values;
     }
 
+    private static void addInt16(McPLC plc, List<String> values, String name, String address) {
+        if (!isEmpty(address)) {
+            values.add(name + "=" + plc.readInt16(address));
+        }
+    }
 
+    private static void addScaledInt16(McPLC plc, List<String> values, String name, String address, int factor, boolean multiply) {
+        if (isEmpty(address)) {
+            return;
+        }
+        short raw = plc.readInt16(address);
+        values.add(name + "原始值=" + raw);
+        double value = multiply ? raw * factor : raw * 1.0 / factor;
+        values.add(name + "=" + String.format(Locale.ROOT, "%.2f", value));
+    }
 
+    private static void addIntNumber(McPLC plc, JSONObject sample, String name, String address) {
+        if (!isEmpty(address)) {
+            sample.put(name, plc.readInt16(address));
+        }
+    }
+
+    private static void addScaledNumber(McPLC plc, JSONObject sample, String name, String address, int factor, boolean multiply) {
+        if (isEmpty(address)) {
+            return;
+        }
+        short raw = plc.readInt16(address);
+        double value = multiply ? raw * factor : raw * 1.0 / factor;
+        sample.put(name, value);
+    }
+
+    private static JSONArray toPointNumbers(List<Boolean> values) {
+        JSONArray points = new JSONArray();
+        for (int i = 0; i < values.size(); i++) {
+            if (Boolean.TRUE.equals(values.get(i))) {
+                points.add(i + 1);
+            }
+        }
+        return points;
+    }
+
+    private static String currentTime() {
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
+    }
+
+    // 按 StationConfig 创建 PLC 连接;不同工位可配置不同 PLC、端口和系列。
+    private static McPLC createPlc(StationConfig config) {
+        if (config == null || isEmpty(config.getPlcIp())) {
+            McPLC plc = new McPLC(EMcSeries.QnA, "192.168.0.35", 8000);
+            plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+            plc.setReceiveTimeout(RECEIVE_TIMEOUT_MS);
+            return plc;
+        }
+
+        McPLC plc = new McPLC(parseSeries(config.getPlcSeries()), config.getPlcIp(), config.getPlcPort());
+        plc.setConnectTimeout(CONNECT_TIMEOUT_MS);
+        plc.setReceiveTimeout(RECEIVE_TIMEOUT_MS);
+        return plc;
+    }
+
+    private static EMcSeries parseSeries(String series) {
+        if ("Q_L".equalsIgnoreCase(series)) {
+            return EMcSeries.Q_L;
+        }
+        if ("IQ_R".equalsIgnoreCase(series)) {
+            return EMcSeries.IQ_R;
+        }
+        return EMcSeries.QnA;
+    }
+
+    private static int countTrue(List<Boolean> values) {
+        int count = 0;
+        for (Boolean value : values) {
+            if (Boolean.TRUE.equals(value)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private static boolean isEmpty(String value) {
+        return value == null || value.trim().isEmpty();
+    }
 }

+ 196 - 2
src/com/mes/util/StationConfig.java

@@ -23,6 +23,26 @@ public class StationConfig {
     // 加工结束
     private String stopMethod;
     // 故障
+    private String fault;
+
+    private String plcIp;
+    private int plcPort;
+    private String plcSeries;
+    private String plcStation;
+    private String weldType;
+
+    private String robot1Start;
+    private String robot1Done;
+    private String robot2Start;
+    private String robot2Done;
+    private String robot1Pressure;
+    private String robot2Pressure;
+    private String robot1Current;
+    private String robot2Current;
+    private String robot1Time;
+    private String robot2Time;
+    private String robot1Voltage;
+    private String robot2Voltage;
 
     public StationConfig(String gw, String gwDes, String lineSn, String serverIp) {
         this.gw = gw;
@@ -107,6 +127,150 @@ public class StationConfig {
         this.stopMethod = stopMethod;
     }
 
+    public String getFault() {
+        return fault;
+    }
+
+    public void setFault(String fault) {
+        this.fault = fault;
+    }
+
+    public String getPlcIp() {
+        return plcIp;
+    }
+
+    public void setPlcIp(String plcIp) {
+        this.plcIp = plcIp;
+    }
+
+    public int getPlcPort() {
+        return plcPort;
+    }
+
+    public void setPlcPort(int plcPort) {
+        this.plcPort = plcPort;
+    }
+
+    public String getPlcSeries() {
+        return plcSeries;
+    }
+
+    public void setPlcSeries(String plcSeries) {
+        this.plcSeries = plcSeries;
+    }
+
+    public String getPlcStation() {
+        return plcStation;
+    }
+
+    public void setPlcStation(String plcStation) {
+        this.plcStation = plcStation;
+    }
+
+    public String getWeldType() {
+        return weldType;
+    }
+
+    public void setWeldType(String weldType) {
+        this.weldType = weldType;
+    }
+
+    public String getRobot1Start() {
+        return robot1Start;
+    }
+
+    public void setRobot1Start(String robot1Start) {
+        this.robot1Start = robot1Start;
+    }
+
+    public String getRobot1Done() {
+        return robot1Done;
+    }
+
+    public void setRobot1Done(String robot1Done) {
+        this.robot1Done = robot1Done;
+    }
+
+    public String getRobot2Start() {
+        return robot2Start;
+    }
+
+    public void setRobot2Start(String robot2Start) {
+        this.robot2Start = robot2Start;
+    }
+
+    public String getRobot2Done() {
+        return robot2Done;
+    }
+
+    public void setRobot2Done(String robot2Done) {
+        this.robot2Done = robot2Done;
+    }
+
+    public String getRobot1Pressure() {
+        return robot1Pressure;
+    }
+
+    public void setRobot1Pressure(String robot1Pressure) {
+        this.robot1Pressure = robot1Pressure;
+    }
+
+    public String getRobot2Pressure() {
+        return robot2Pressure;
+    }
+
+    public void setRobot2Pressure(String robot2Pressure) {
+        this.robot2Pressure = robot2Pressure;
+    }
+
+    public String getRobot1Current() {
+        return robot1Current;
+    }
+
+    public void setRobot1Current(String robot1Current) {
+        this.robot1Current = robot1Current;
+    }
+
+    public String getRobot2Current() {
+        return robot2Current;
+    }
+
+    public void setRobot2Current(String robot2Current) {
+        this.robot2Current = robot2Current;
+    }
+
+    public String getRobot1Time() {
+        return robot1Time;
+    }
+
+    public void setRobot1Time(String robot1Time) {
+        this.robot1Time = robot1Time;
+    }
+
+    public String getRobot2Time() {
+        return robot2Time;
+    }
+
+    public void setRobot2Time(String robot2Time) {
+        this.robot2Time = robot2Time;
+    }
+
+    public String getRobot1Voltage() {
+        return robot1Voltage;
+    }
+
+    public void setRobot1Voltage(String robot1Voltage) {
+        this.robot1Voltage = robot1Voltage;
+    }
+
+    public String getRobot2Voltage() {
+        return robot2Voltage;
+    }
+
+    public void setRobot2Voltage(String robot2Voltage) {
+        this.robot2Voltage = robot2Voltage;
+    }
+
     public static List<StationConfig> loadAllStations() throws IOException {
         List<StationConfig> stations = new ArrayList<>();
         Properties props = new Properties();
@@ -127,13 +291,43 @@ public class StationConfig {
                     String allowDown = props.getProperty("mes.station." + gw + ".allowDown", "");
                     String startMethod = props.getProperty("mes.station." + gw + ".startMethod", "");
                     String stopMethod = props.getProperty("mes.station." + gw + ".stopMethod", "");
-                    stations.add(new StationConfig(gw, gwDes, lineSn, serverIp, allowStart, allowDown, startMethod, stopMethod));
+                    StationConfig config = new StationConfig(gw, gwDes, lineSn, serverIp, allowStart, allowDown, startMethod, stopMethod);
+                    loadPlcConfig(props, config);
+                    stations.add(config);
                 }
             }
         }
         return stations;
     }
 
+    private static void loadPlcConfig(Properties props, StationConfig config) {
+        String prefix = "mes.station." + config.getGw() + ".";
+        config.setFault(props.getProperty(prefix + "fault", ""));
+        config.setPlcIp(props.getProperty(prefix + "plc.ip", "192.168.0.35"));
+        config.setPlcPort(parseInt(props.getProperty(prefix + "plc.port"), 8000));
+        config.setPlcSeries(props.getProperty(prefix + "plc.series", "QnA"));
+        config.setPlcStation(props.getProperty(prefix + "plc.station", ""));
+        config.setWeldType(props.getProperty(prefix + "weldType", ""));
+        config.setRobot1Start(props.getProperty(prefix + "robot1.start", ""));
+        config.setRobot1Done(props.getProperty(prefix + "robot1.done", ""));
+        config.setRobot2Start(props.getProperty(prefix + "robot2.start", ""));
+        config.setRobot2Done(props.getProperty(prefix + "robot2.done", ""));
+        config.setRobot1Pressure(props.getProperty(prefix + "robot1.pressure", ""));
+        config.setRobot2Pressure(props.getProperty(prefix + "robot2.pressure", ""));
+        config.setRobot1Current(props.getProperty(prefix + "robot1.current", ""));
+        config.setRobot2Current(props.getProperty(prefix + "robot2.current", ""));
+        config.setRobot1Time(props.getProperty(prefix + "robot1.time", ""));
+        config.setRobot2Time(props.getProperty(prefix + "robot2.time", ""));
+        config.setRobot1Voltage(props.getProperty(prefix + "robot1.voltage", ""));
+        config.setRobot2Voltage(props.getProperty(prefix + "robot2.voltage", ""));
+    }
 
+    private static int parseInt(String value, int defaultValue) {
+        try {
+            return Integer.parseInt(value);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
 
-}
+}

+ 243 - 0
src/com/mes/util/WeldingApiTestMain.java

@@ -0,0 +1,243 @@
+package com.mes.util;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.ui.DataUtil;
+import com.mes.ui.MesClient;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Scanner;
+
+/**
+ * 焊接参数接口测试工具。
+ *
+ * 只用于现场手动验证后端接口,不会启动正式 MES 界面。
+ */
+public class WeldingApiTestMain {
+
+    private static final String DEFAULT_USER = "system";
+
+    public static void main(String[] args) throws Exception {
+        Scanner scanner = new Scanner(System.in, "UTF-8");
+        List<StationConfig> stations = StationConfig.loadAllStations();
+
+        System.out.println("焊接参数接口测试工具启动:" + now());
+        System.out.println("用途:手动测试 PDA 扫码校验、加工结果上传、焊接参数 batchsave 上传。");
+        System.out.println();
+
+        StationConfig station = chooseStation(scanner, stations);
+        String serverIp = ask(scanner, "请输入 MES 后端 IP", station.getServerIp());
+        String lineSn = ask(scanner, "请输入线体编号 lineSn", station.getLineSn());
+        String user = ask(scanner, "请输入操作用户 ucode", DEFAULT_USER);
+        MesClient.sessionid = ask(scanner, "请输入登录后的 __sid/sessionid,没有就直接回车", "");
+
+        while (true) {
+            System.out.println();
+            System.out.println("当前配置:工位=" + station.getGw()
+                    + ",线体=" + lineSn
+                    + ",后端=" + serverIp
+                    + ",焊接类型=" + station.getWeldType());
+            System.out.println("请选择测试项:");
+            System.out.println("  1:测试扫码校验 pccheck");
+            System.out.println("  2:测试上传加工结果 pcresult");
+            System.out.println("  3:测试上传示例焊接参数 batchsave");
+            System.out.println("  4:手写 content JSON 后上传 batchsave");
+            System.out.println("  5:从 PLC 读取参数后上传 batchsave");
+            System.out.println("  6:一键测试:扫码校验 -> 上传 OK 结果 -> 上传示例参数");
+            System.out.println("  S:切换工位");
+            System.out.println("  Q:退出");
+            String command = ask(scanner, "请输入选项", "").trim().toUpperCase(Locale.ROOT);
+
+            if ("Q".equals(command) || "QUIT".equals(command) || "EXIT".equals(command)) {
+                System.out.println("已退出。");
+                return;
+            }
+            if ("S".equals(command)) {
+                station = chooseStation(scanner, stations);
+                continue;
+            }
+
+            String sn = ask(scanner, "请输入工件码 SN", "");
+            if (sn.trim().isEmpty()) {
+                System.out.println("SN 不能为空,本次操作跳过。");
+                continue;
+            }
+
+            if ("1".equals(command)) {
+                printResult("扫码校验", DataUtil.checkQuality(sn, user, station.getGw(), lineSn, serverIp));
+            } else if ("2".equals(command)) {
+                String result = ask(scanner, "请输入加工结果 OK/NG", "OK").trim().toUpperCase(Locale.ROOT);
+                printResult("上传加工结果", DataUtil.sendQuality(sn, result, user, station.getGw(), lineSn, serverIp));
+            } else if ("3".equals(command)) {
+                String content = buildDemoContent(station);
+                System.out.println("即将上传的 content:");
+                System.out.println(content);
+                printResult("上传示例焊接参数", DataUtil.saveWeldingParams(sn, station.getGw(), lineSn, serverIp, content));
+            } else if ("4".equals(command)) {
+                System.out.println("请输入 content JSON,一整行即可。content 是 params 数组里每条记录的 content 字符串。");
+                String content = scanner.nextLine();
+                printResult("上传手写焊接参数", DataUtil.saveWeldingParams(sn, station.getGw(), lineSn, serverIp, content));
+            } else if ("5".equals(command)) {
+                String content = PLCUtils.getWeldingParamContent(station);
+                if (content == null || content.trim().isEmpty()) {
+                    System.out.println("PLC 参数读取为空,请先检查 PLC 网络、工位配置和点位。");
+                    continue;
+                }
+                System.out.println("PLC 读取到的 content:");
+                System.out.println(content);
+                printResult("上传 PLC 焊接参数", DataUtil.saveWeldingParams(sn, station.getGw(), lineSn, serverIp, content));
+            } else if ("6".equals(command)) {
+                JSONObject checkRet = DataUtil.checkQuality(sn, user, station.getGw(), lineSn, serverIp);
+                printResult("扫码校验", checkRet);
+                if (!isSuccess(checkRet)) {
+                    System.out.println("扫码校验没有成功,后续上传跳过。");
+                    continue;
+                }
+
+                JSONObject resultRet = DataUtil.sendQuality(sn, "OK", user, station.getGw(), lineSn, serverIp);
+                printResult("上传 OK 结果", resultRet);
+                if (!isSuccess(resultRet)) {
+                    System.out.println("结果上传没有成功,参数上传跳过。");
+                    continue;
+                }
+
+                String content = buildDemoContent(station);
+                printResult("上传示例焊接参数", DataUtil.saveWeldingParams(sn, station.getGw(), lineSn, serverIp, content));
+            } else {
+                System.out.println("未识别的选项:" + command);
+            }
+        }
+    }
+
+    private static StationConfig chooseStation(Scanner scanner, List<StationConfig> stations) {
+        while (true) {
+            System.out.println("可选工位:");
+            for (int i = 0; i < stations.size(); i++) {
+                StationConfig station = stations.get(i);
+                System.out.println("  " + (i + 1) + ":" + station.getGw()
+                        + ",PLC工位=" + station.getPlcStation()
+                        + ",类型=" + station.getWeldType());
+            }
+
+            String input = ask(scanner, "请输入工位序号或工位号", stations.isEmpty() ? "" : stations.get(0).getGw());
+            for (int i = 0; i < stations.size(); i++) {
+                StationConfig station = stations.get(i);
+                if (input.equals(String.valueOf(i + 1)) || input.equalsIgnoreCase(station.getGw())) {
+                    return station;
+                }
+            }
+            System.out.println("没有找到该工位,请重新输入。");
+        }
+    }
+
+    private static String ask(Scanner scanner, String title, String defaultValue) {
+        if (defaultValue == null || defaultValue.isEmpty()) {
+            System.out.print(title + ":");
+        } else {
+            System.out.print(title + "(默认 " + defaultValue + "):");
+        }
+        String input = scanner.nextLine();
+        if (input == null || input.trim().isEmpty()) {
+            return defaultValue == null ? "" : defaultValue;
+        }
+        return input.trim();
+    }
+
+    private static void printResult(String title, JSONObject result) {
+        System.out.println();
+        System.out.println("[" + title + "] 返回:");
+        if (result == null) {
+            System.out.println("null,可能是接口返回 false、网络异常、session 失效或后端报错。");
+        } else {
+            System.out.println(result.toJSONString());
+        }
+    }
+
+    private static boolean isSuccess(JSONObject result) {
+        return result != null && "true".equalsIgnoreCase(result.getString("result"));
+    }
+
+    private static String buildDemoContent(StationConfig station) {
+        JSONObject content = new JSONObject();
+        content.put("version", 2);
+        content.put("weldingType", station.getWeldType());
+        content.put("mesOprno", station.getGw());
+        content.put("plcOprno", station.getPlcStation());
+
+        JSONArray samples = new JSONArray();
+        long startTime = System.currentTimeMillis() - 29 * 1000L;
+
+        if ("spot".equalsIgnoreCase(station.getWeldType())) {
+            for (int i = 0; i < 30; i++) {
+                JSONObject sample = new JSONObject();
+                sample.put("time", formatTime(startTime + i * 1000L));
+                sample.put("robot1Current", round2(12.0 + Math.sin(i / 4.0) * 0.8 + i * 0.03));
+                sample.put("robot2Current", round2(13.0 + Math.cos(i / 5.0) * 0.7 + i * 0.02));
+                sample.put("robot1Pressure", 98 + (i % 6));
+                sample.put("robot2Pressure", 101 + (i % 5));
+                sample.put("robot1WeldTime", 220 + (i % 8) * 5);
+                sample.put("robot2WeldTime", 235 + (i % 7) * 5);
+
+                JSONObject robot1 = new JSONObject();
+                robot1.put("startPoints", pointRange(1, Math.min(30, i + 1)));
+                robot1.put("finishPoints", pointRange(1, Math.min(30, Math.max(0, i - 1))));
+                JSONObject robot2 = new JSONObject();
+                robot2.put("startPoints", pointRange(2, Math.min(28, i + 2), 2));
+                robot2.put("finishPoints", pointRange(2, Math.min(28, Math.max(0, i)), 2));
+
+                JSONObject spot = new JSONObject();
+                spot.put("robot1", robot1);
+                spot.put("robot2", robot2);
+                sample.put("spot", spot);
+                samples.add(sample);
+            }
+
+            JSONObject summary = new JSONObject();
+            summary.put("robot1StartCount", 30);
+            summary.put("robot1FinishCount", 28);
+            summary.put("robot2StartCount", 14);
+            summary.put("robot2FinishCount", 14);
+            content.put("summary", summary);
+        } else {
+            for (int i = 0; i < 30; i++) {
+                JSONObject sample = new JSONObject();
+                sample.put("time", formatTime(startTime + i * 1000L));
+                sample.put("robot1Current", round2(118 + Math.sin(i / 3.0) * 7 + i * 0.15));
+                sample.put("robot1Voltage", round2(23.0 + Math.cos(i / 4.0) * 0.9));
+                sample.put("robot2Current", round2(115 + Math.cos(i / 3.5) * 6 + i * 0.12));
+                sample.put("robot2Voltage", round2(22.8 + Math.sin(i / 5.0) * 0.8));
+                samples.add(sample);
+            }
+        }
+
+        content.put("samples", samples);
+        return content.toJSONString();
+    }
+
+    private static JSONArray pointRange(int start, int end) {
+        return pointRange(start, end, 1);
+    }
+
+    private static JSONArray pointRange(int start, int end, int step) {
+        JSONArray array = new JSONArray();
+        for (int value = start; value <= end; value += step) {
+            array.add(value);
+        }
+        return array;
+    }
+
+    private static double round2(double value) {
+        return Math.round(value * 100.0) / 100.0;
+    }
+
+    private static String formatTime(long timeMillis) {
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(timeMillis));
+    }
+
+    private static String now() {
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
+    }
+}

+ 93 - 5
src/resources/config/config.properties

@@ -12,33 +12,121 @@ mes.station.OP050A.allowStart=M6661
 mes.station.OP050A.allowDown=M6662
 mes.station.OP050A.startMethod=M6663
 mes.station.OP050A.stopMethod=M6664
+mes.station.OP050A.fault=M6665
+mes.station.OP050A.plc.ip=192.168.0.35
+mes.station.OP050A.plc.port=8000
+mes.station.OP050A.plc.series=QnA
+mes.station.OP050A.plc.station=OP10
+mes.station.OP050A.weldType=arc
+mes.station.OP050A.robot1.current=D7041
+mes.station.OP050A.robot1.voltage=D7042
+mes.station.OP050A.robot2.current=D7031
+mes.station.OP050A.robot2.voltage=D7032
 
 mes.station.OP060A.name=左右吊耳组件点焊
 mes.station.OP060A.allowStart=M6641
 mes.station.OP060A.allowDown=M6642
 mes.station.OP060A.startMethod=M6643
 mes.station.OP060A.stopMethod=M6644
+mes.station.OP060A.fault=M6645
+mes.station.OP060A.plc.ip=192.168.0.35
+mes.station.OP060A.plc.port=8000
+mes.station.OP060A.plc.series=QnA
+mes.station.OP060A.plc.station=OP20
+mes.station.OP060A.weldType=spot
+mes.station.OP060A.robot1.start=M7500
+mes.station.OP060A.robot1.done=M7550
+mes.station.OP060A.robot2.start=M7600
+mes.station.OP060A.robot2.done=M7650
+mes.station.OP060A.robot1.pressure=D7005
+mes.station.OP060A.robot2.pressure=D7006
+mes.station.OP060A.robot1.current=D7091
+mes.station.OP060A.robot2.current=D7101
+mes.station.OP060A.robot1.time=D7092
+mes.station.OP060A.robot2.time=D7102
 
 mes.station.OP070A.name=电池壳分总成1点焊
 mes.station.OP070A.allowStart=M6621
 mes.station.OP070A.allowDown=M6622
 mes.station.OP070A.startMethod=M6623
 mes.station.OP070A.stopMethod=M6624
+mes.station.OP070A.fault=M6625
+mes.station.OP070A.plc.ip=192.168.0.35
+mes.station.OP070A.plc.port=8000
+mes.station.OP070A.plc.series=QnA
+mes.station.OP070A.plc.station=OP30
+mes.station.OP070A.weldType=spot
+mes.station.OP070A.robot1.start=M7100
+mes.station.OP070A.robot1.done=M7150
+mes.station.OP070A.robot2.start=M7200
+mes.station.OP070A.robot2.done=M7250
+mes.station.OP070A.robot1.pressure=D7003
+mes.station.OP070A.robot2.pressure=D7004
+mes.station.OP070A.robot1.current=D7071
+mes.station.OP070A.robot2.current=D7081
+mes.station.OP070A.robot1.time=D7072
+mes.station.OP070A.robot2.time=D7082
 
 mes.station.OP080A.name=电池壳分总成1点焊补焊
 mes.station.OP080A.allowStart=M6601
 mes.station.OP080A.allowDown=M6602
 mes.station.OP080A.startMethod=M6603
 mes.station.OP080A.stopMethod=M6604
+mes.station.OP080A.fault=M6605
+mes.station.OP080A.plc.ip=192.168.0.35
+mes.station.OP080A.plc.port=8000
+mes.station.OP080A.plc.series=QnA
+mes.station.OP080A.plc.station=OP40
+mes.station.OP080A.weldType=spot
+mes.station.OP080A.robot1.start=M6700
+mes.station.OP080A.robot1.done=M6750
+mes.station.OP080A.robot2.start=M6800
+mes.station.OP080A.robot2.done=M6850
+mes.station.OP080A.robot1.pressure=D7001
+mes.station.OP080A.robot2.pressure=D7002
+mes.station.OP080A.robot1.current=D7051
+mes.station.OP080A.robot2.current=D7061
+mes.station.OP080A.robot1.time=D7052
+mes.station.OP080A.robot2.time=D7062
 
 mes.station.OP090A.name=电池壳分总成2点焊
+mes.station.OP090A.allowStart=M6001
+mes.station.OP090A.allowDown=M6002
+mes.station.OP090A.startMethod=M6003
+mes.station.OP090A.stopMethod=M6004
+mes.station.OP090A.fault=M6005
+mes.station.OP090A.plc.ip=192.168.0.10
+mes.station.OP090A.plc.port=5001
+mes.station.OP090A.plc.series=Q_L
+mes.station.OP090A.plc.station=OP50
+mes.station.OP090A.weldType=spot
+mes.station.OP090A.robot1.start=M6100
+mes.station.OP090A.robot1.done=M6150
+mes.station.OP090A.robot2.start=M6300
+mes.station.OP090A.robot2.done=M6350
+mes.station.OP090A.robot1.pressure=D6001
+mes.station.OP090A.robot2.pressure=D6011
+mes.station.OP090A.robot1.current=D6002
+mes.station.OP090A.robot2.current=D6012
+mes.station.OP090A.robot1.time=D6003
+mes.station.OP090A.robot2.time=D6013
 
 mes.station.OP160A.name=电池壳分总成8弧焊
-mes.station.OP090A.allowStart=M6681
-mes.station.OP090A.allowDown=M6682
-mes.station.OP090A.startMethod=M6683
-mes.station.OP090A.stopMethod=M6684
+mes.station.OP160A.allowStart=M6681
+mes.station.OP160A.allowDown=M6682
+mes.station.OP160A.startMethod=M6683
+mes.station.OP160A.stopMethod=M6684
+mes.station.OP160A.fault=M6685
+mes.station.OP160A.plc.ip=192.168.0.35
+mes.station.OP160A.plc.port=8000
+mes.station.OP160A.plc.series=QnA
+mes.station.OP160A.plc.station=OP120
+mes.station.OP160A.weldType=arc
+mes.station.OP160A.robot1.current=D7021
+mes.station.OP160A.robot1.voltage=D7022
+mes.station.OP160A.robot2.current=D7011
+mes.station.OP160A.robot2.voltage=D7012
 
 # 单工位配置(保留原配置以兼容旧代码)
 mes.gw=OP050A
-mes.gw_des=工位1-上料
+mes.gw_des=工位1-上料