소스 검색

初始化

wangxichen 3 일 전
커밋
0532bea6a2
100개의 변경된 파일4375개의 추가작업 그리고 0개의 파일을 삭제
  1. 23 0
      .classpath
  2. 7 0
      .gitignore
  3. 0 0
      .lock
  4. 2 0
      image/.gitignore
  5. BIN
      image/background.png
  6. BIN
      image/bg/a_side.png
  7. BIN
      image/bg/b_side.png
  8. BIN
      image/bg/bar_add.png
  9. BIN
      image/bg/bar_bookmark.png
  10. BIN
      image/bg/bar_cmd_go.png
  11. BIN
      image/bg/bar_edit.png
  12. BIN
      image/bg/bar_pause.png
  13. BIN
      image/bg/bar_sampling.png
  14. BIN
      image/bg/bar_setting.png
  15. BIN
      image/bg/bar_stop.png
  16. BIN
      image/bg/close_bt.png
  17. BIN
      image/bg/company_setting_logo.png
  18. BIN
      image/bg/delete.png
  19. BIN
      image/bg/department_setting_logo.png
  20. BIN
      image/bg/download.png
  21. BIN
      image/bg/equipment_setting_logo.png
  22. BIN
      image/bg/gj_001_1_20.png
  23. BIN
      image/bg/gj_001_21_40.png
  24. BIN
      image/bg/gj_001_41_60.png
  25. BIN
      image/bg/green_dot.png
  26. BIN
      image/bg/grey_dot.png
  27. BIN
      image/bg/icon.png
  28. BIN
      image/bg/inductance_logo.png
  29. BIN
      image/bg/insulation_logo.png
  30. BIN
      image/bg/login.png
  31. BIN
      image/bg/logo.png
  32. BIN
      image/bg/logoff.png
  33. BIN
      image/bg/menu_data_analysis.png
  34. BIN
      image/bg/menu_data_preprocess.png
  35. BIN
      image/bg/menu_file.png
  36. BIN
      image/bg/menu_setting.png
  37. BIN
      image/bg/min_bt.png
  38. BIN
      image/bg/ng_bg.png
  39. BIN
      image/bg/ok_bg.png
  40. BIN
      image/bg/open_file.png
  41. BIN
      image/bg/refresh.png
  42. BIN
      image/bg/reset_logo.png
  43. BIN
      image/bg/resistance_logo.png
  44. BIN
      image/bg/save_bg.png
  45. BIN
      image/bg/scan_barcode.png
  46. BIN
      image/bg/tree_folder_icon.png
  47. BIN
      image/bg/tree_leaf_icon.png
  48. BIN
      image/bg/user.png
  49. BIN
      image/bg/vertical_line.png
  50. BIN
      image/bg/zoom_in.png
  51. BIN
      image/bg/zoom_out.png
  52. BIN
      image/close_bt.png
  53. 0 0
      image/cmd.txt
  54. BIN
      image/icon.ico
  55. BIN
      image/info_bt.png
  56. BIN
      image/min_bt.png
  57. BIN
      lib/commons-codec-1.15.jar
  58. BIN
      lib/core-3.5.1.jar
  59. BIN
      lib/fastjson2-2.0.16.jar
  60. BIN
      lib/gson-2.10.jar
  61. BIN
      lib/iot-communication-1.4.4.jar
  62. BIN
      lib/jSerialComm-2.6.2.jar
  63. BIN
      lib/javase-3.5.1.jar
  64. BIN
      lib/jfreechart-1.5.4.jar
  65. BIN
      lib/jshortcut-0.4-oberzalek.jar
  66. BIN
      lib/kotlin-stdlib-1.8.10.jar
  67. BIN
      lib/logback-classic-1.2.13.jar
  68. BIN
      lib/logback-core-1.2.13.jar
  69. BIN
      lib/netty-all-4.1.48.Final.jar
  70. BIN
      lib/okhttp-4.10.0.jar
  71. BIN
      lib/okio-jvm-3.3.0.jar
  72. BIN
      lib/s7connector-2.1.jar
  73. BIN
      lib/slf4j-api-1.7.36.jar
  74. BIN
      lib/sqlite-jdbc-3.36.0.3.jar
  75. 13 0
      src/com/mes/component/CallFromJs.java
  76. 62 0
      src/com/mes/component/MesRadio.java
  77. 47 0
      src/com/mes/component/MesWebView.java
  78. 30 0
      src/com/mes/component/MyDialog.java
  79. 92 0
      src/com/mes/netty/MesMsgUtils.java
  80. 70 0
      src/com/mes/netty/NettyClient.java
  81. 91 0
      src/com/mes/netty/NettyClientHandler.java
  82. 77 0
      src/com/mes/netty/ProtocolParam.java
  83. 182 0
      src/com/mes/netty/XDecoder.java
  84. 560 0
      src/com/mes/test/PlcDebugTest.java
  85. 58 0
      src/com/mes/ui/BindMaterialResp.java
  86. 329 0
      src/com/mes/ui/DataUtil.java
  87. 85 0
      src/com/mes/ui/LockUtil.java
  88. 193 0
      src/com/mes/ui/LoginFrame.java
  89. 1619 0
      src/com/mes/ui/MesClient.java
  90. 261 0
      src/com/mes/ui/MesRevice.java
  91. 67 0
      src/com/mes/ui/OprnoUtil.java
  92. 157 0
      src/com/mes/ui/SettingDialog.java
  93. 48 0
      src/com/mes/ui/TableCellEditorButton.java
  94. 14 0
      src/com/mes/ui/TableCellRendererButton.java
  95. 17 0
      src/com/mes/util/Base64Utils.java
  96. 97 0
      src/com/mes/util/Config.java
  97. 93 0
      src/com/mes/util/DataUtils.java
  98. 34 0
      src/com/mes/util/DateLocalUtils.java
  99. 47 0
      src/com/mes/util/ErrorMsg.java
  100. 0 0
      src/com/mes/util/HttpUtils.java

+ 23 - 0
.classpath

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_121"/>
+	<classpathentry kind="src" path="image"/>
+	<classpathentry excluding="resources/" kind="src" path="src"/>
+	<classpathentry kind="src" path="src/resources"/>
+	<classpathentry kind="lib" path="lib/s7connector-2.1.jar"/>
+	<classpathentry kind="lib" path="lib/okhttp-4.10.0.jar"/>
+	<classpathentry kind="lib" path="lib/kotlin-stdlib-1.8.10.jar"/>
+	<classpathentry kind="lib" path="lib/okio-jvm-3.3.0.jar"/>
+	<classpathentry kind="lib" path="lib/gson-2.10.jar"/>
+	<classpathentry kind="lib" path="lib/jfreechart-1.5.4.jar"/>
+	<classpathentry kind="lib" path="lib/jshortcut-0.4-oberzalek.jar"/>
+	<classpathentry kind="lib" path="lib/netty-all-4.1.48.Final.jar"/>
+	<classpathentry kind="lib" path="lib/sqlite-jdbc-3.36.0.3.jar"/>
+	<classpathentry kind="lib" path="lib/commons-codec-1.15.jar"/>
+	<classpathentry kind="lib" path="lib/fastjson2-2.0.16.jar"/>
+	<classpathentry kind="lib" path="C:/Users/jcxa/Desktop/p04/khd/mesclient-op150/lib/slf4j-api-1.7.36.jar"/>
+	<classpathentry kind="lib" path="C:/Users/jcxa/Desktop/p04/khd/mesclient-op150/lib/jSerialComm-2.6.2.jar"/>
+	<classpathentry kind="lib" path="C:/Users/jcxa/Desktop/p04/khd/mesclient-op150/lib/logback-classic-1.2.13.jar"/>
+	<classpathentry kind="lib" path="C:/Users/jcxa/Desktop/p04/khd/mesclient-op150/lib/logback-core-1.2.13.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+.idea
+.settings
+classes
+bin
+out
+*.db
+*.iml

+ 0 - 0
.lock


+ 2 - 0
image/.gitignore

@@ -0,0 +1,2 @@
+META-INF
+_system~.ini

BIN
image/background.png


BIN
image/bg/a_side.png


BIN
image/bg/b_side.png


BIN
image/bg/bar_add.png


BIN
image/bg/bar_bookmark.png


BIN
image/bg/bar_cmd_go.png


BIN
image/bg/bar_edit.png


BIN
image/bg/bar_pause.png


BIN
image/bg/bar_sampling.png


BIN
image/bg/bar_setting.png


BIN
image/bg/bar_stop.png


BIN
image/bg/close_bt.png


BIN
image/bg/company_setting_logo.png


BIN
image/bg/delete.png


BIN
image/bg/department_setting_logo.png


BIN
image/bg/download.png


BIN
image/bg/equipment_setting_logo.png


BIN
image/bg/gj_001_1_20.png


BIN
image/bg/gj_001_21_40.png


BIN
image/bg/gj_001_41_60.png


BIN
image/bg/green_dot.png


BIN
image/bg/grey_dot.png


BIN
image/bg/icon.png


BIN
image/bg/inductance_logo.png


BIN
image/bg/insulation_logo.png


BIN
image/bg/login.png


BIN
image/bg/logo.png


BIN
image/bg/logoff.png


BIN
image/bg/menu_data_analysis.png


BIN
image/bg/menu_data_preprocess.png


BIN
image/bg/menu_file.png


BIN
image/bg/menu_setting.png


BIN
image/bg/min_bt.png


BIN
image/bg/ng_bg.png


BIN
image/bg/ok_bg.png


BIN
image/bg/open_file.png


BIN
image/bg/refresh.png


BIN
image/bg/reset_logo.png


BIN
image/bg/resistance_logo.png


BIN
image/bg/save_bg.png


BIN
image/bg/scan_barcode.png


BIN
image/bg/tree_folder_icon.png


BIN
image/bg/tree_leaf_icon.png


BIN
image/bg/user.png


BIN
image/bg/vertical_line.png


BIN
image/bg/zoom_in.png


BIN
image/bg/zoom_out.png


BIN
image/close_bt.png


+ 0 - 0
image/cmd.txt


BIN
image/icon.ico


BIN
image/info_bt.png


BIN
image/min_bt.png


BIN
lib/commons-codec-1.15.jar


BIN
lib/core-3.5.1.jar


BIN
lib/fastjson2-2.0.16.jar


BIN
lib/gson-2.10.jar


BIN
lib/iot-communication-1.4.4.jar


BIN
lib/jSerialComm-2.6.2.jar


BIN
lib/javase-3.5.1.jar


BIN
lib/jfreechart-1.5.4.jar


BIN
lib/jshortcut-0.4-oberzalek.jar


BIN
lib/kotlin-stdlib-1.8.10.jar


BIN
lib/logback-classic-1.2.13.jar


BIN
lib/logback-core-1.2.13.jar


BIN
lib/netty-all-4.1.48.Final.jar


BIN
lib/okhttp-4.10.0.jar


BIN
lib/okio-jvm-3.3.0.jar


BIN
lib/s7connector-2.1.jar


BIN
lib/slf4j-api-1.7.36.jar


BIN
lib/sqlite-jdbc-3.36.0.3.jar


+ 13 - 0
src/com/mes/component/CallFromJs.java

@@ -0,0 +1,13 @@
+package com.mes.component;
+
+import com.mes.ui.MesClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CallFromJs {
+    public static final Logger log =  LoggerFactory.getLogger(CallFromJs.class);
+    public String callFromJs(String info){
+        log.info("ss:"+info);
+        return MesClient.sessionid+":java:" + info;
+    }
+}

+ 62 - 0
src/com/mes/component/MesRadio.java

@@ -0,0 +1,62 @@
+package com.mes.component;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class MesRadio extends JPanel {
+
+    public static final Logger log =  LoggerFactory.getLogger(MesRadio.class);
+
+    private String result = "";
+    private Integer i = 0;
+
+    public MesRadio(String[] radioTitles,String[] radioVals){
+        this.setLayout(new FlowLayout());
+        ButtonGroup group = new ButtonGroup();
+        i = 0;
+        for (String title:radioTitles){
+            JRadioButton radioButton = new JRadioButton(title);
+            radioButton.setFont(new Font("微软雅黑", Font.PLAIN, 28));
+
+            String val = radioVals[i];
+            radioButton.setName(val);
+
+            this.add(radioButton);
+            group.add(radioButton);
+
+            radioButton.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(ActionEvent e) {
+                    result = val;
+                    log.info("选择:"+result);
+                }
+            });
+            i++;
+        }
+    }
+
+    public void setResult(String ret){
+        this.result = "";
+        Component[] components = this.getComponents();
+        for (Component jRadioButton:components){
+            if(jRadioButton instanceof JRadioButton){
+                JRadioButton jRadioButtonParam = (JRadioButton)jRadioButton;
+                if(ret.equals(jRadioButtonParam.getName())){
+                    jRadioButtonParam.setSelected(true);
+                    this.result = jRadioButtonParam.getName();
+                }else{
+                    jRadioButtonParam.setSelected(false);
+                }
+            }
+        }
+    }
+
+    public String getResult(){
+        return this.result;
+    }
+}

+ 47 - 0
src/com/mes/component/MesWebView.java

@@ -0,0 +1,47 @@
+package com.mes.component;
+
+import com.mes.netty.ProtocolParam;
+import javafx.application.Platform;
+import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Worker;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.Scene;
+import javafx.scene.web.WebEngine;
+import javafx.scene.web.WebView;
+import netscape.javascript.JSObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+public class MesWebView extends JFXPanel {
+
+    public static final Logger log =  LoggerFactory.getLogger(MesWebView.class);
+
+    public static WebEngine engine = null;
+
+    public MesWebView(String url){
+
+        Platform.runLater(() -> {
+            WebView webView = new WebView();
+            setScene(new Scene(webView));
+            Date date = new Date();
+            engine = webView.getEngine();
+            CallFromJs bridge = new CallFromJs();
+            engine.getLoadWorker().stateProperty().addListener(
+                    (ObservableValue<? extends Worker.State> ov, Worker.State oldState, Worker.State newState) -> {
+                        if (newState == Worker.State.SUCCEEDED) {
+                            JSObject window = (JSObject) engine.executeScript("window");
+                            window.setMember("apps", bridge);
+                            window.setMember("sout", System.out);
+                        }
+                    }
+            );
+            engine.load(url);
+        });
+    }
+
+    public void reloadWeb(){
+        engine.reload();
+    }
+}

+ 30 - 0
src/com/mes/component/MyDialog.java

@@ -0,0 +1,30 @@
+package com.mes.component;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class MyDialog extends JDialog {
+
+    public MyDialog(Frame owner, String title, String msg){
+        super(owner, title);
+        init(msg);
+    }
+
+    private void init(String msg){
+        Container container = this.getContentPane();
+        container.setLayout(null);
+        container.setBackground(Color.BLACK);
+        this.setSize(800,220);
+        this.setLocationRelativeTo(null);
+
+        JLabel tslabel = new JLabel(msg);
+        tslabel.setHorizontalAlignment(SwingConstants.CENTER);
+        tslabel.setForeground(Color.RED);
+        tslabel.setFont(new Font("微软雅黑", Font.PLAIN,48));
+        tslabel.setBounds(0,30,800,100);
+
+        container.add(tslabel);
+        this.setModal(true);
+        this.setVisible(true);
+    }
+}

+ 92 - 0
src/com/mes/netty/MesMsgUtils.java

@@ -0,0 +1,92 @@
+package com.mes.netty;
+
+public class MesMsgUtils {
+
+    public static int SYNR_LEN = 46;
+    public static int AXTW_LEN = 46;
+    public static int ACLW_LEN = 46;
+    public static int MCJW_LEN = 96;
+    public static int AQDW_LEN = 96;
+    public static int MBDW_LEN = 96;
+    public static int MJBW_LEN = 96;
+    public static int VLDW_LEN = 96;
+    public static int MQDW_LEN = 96;
+    public static int MKSW_LEN = 96;
+    public static int MSBW_LEN = 96;
+    public static int MCSW_LEN = 96;
+    public static int AQRW_LEN = 96;
+
+    public static String MSG_TYPE[] = {
+        "SYNR",
+        "AXTW",
+        "ACLW",
+        "MCJW",
+        "AQDW",
+        "MBDW",
+        "MJBW",
+        "VLDW",
+        "MQDW",
+        "MKSW",
+        "MSBW",
+        "MCSW",
+        "AQRW",
+    };
+
+    public static int isMsgContentOK(String msg, String msg_type) {
+        //ret=0OK,1 空内容,2 长度不符
+        int ret = 0;
+        if(msg==null||msg.equalsIgnoreCase("")) {
+            ret = 1;
+            return ret;
+        }
+
+        int len = msg.length();
+        //System.out.println("len="+len);
+        switch(msg_type) {
+            case "SYNR":
+            case "AXTW":
+            case "ACLW":
+                if(len==SYNR_LEN) {
+                    ret = 0;
+                }else {
+                    ret = 2;
+                }
+                break;
+            default:
+                if(len==AQDW_LEN) {
+                    ret = 0;
+                }else {
+                    ret = 2;
+                }
+                break;
+        }
+        return ret;
+    }
+
+    // 判断报文类型是否在清单里
+    public static boolean isMsgTypeOk(String msg_type) {
+        for (String str : MSG_TYPE) {
+            if (str.equals(msg_type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // 处理报文数据
+    public static String processMsg(String msg, String msg_type) {
+        String processMsgRet = "";
+        if(msg_type.equalsIgnoreCase("SYNR")) {
+
+        }else if(msg_type.equalsIgnoreCase("AXTW")) {
+
+        }else if(msg_type.equalsIgnoreCase("ACLW")) {
+
+        }else{
+            processMsgRet = ProtocolParam.getResult(msg);
+        }
+        return processMsgRet;
+    }
+
+
+}

+ 70 - 0
src/com/mes/netty/NettyClient.java

@@ -0,0 +1,70 @@
+package com.mes.netty;
+
+import com.mes.util.Config;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.string.StringEncoder;
+import java.util.concurrent.TimeUnit;
+
+import com.mes.ui.MesClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NettyClient {
+
+    public static final Logger log =  LoggerFactory.getLogger(NettyClientHandler.class);
+    public SocketChannel socketChannel;
+    public static ChannelFuture future;
+
+    // 全局唯一线程组,避免每次连接都创建新的导致内存泄漏
+    private final EventLoopGroup group = new NioEventLoopGroup(2);
+    private final Bootstrap bootstrap;
+
+    public NettyClient() {
+        bootstrap = new Bootstrap();
+        bootstrap.group(group)
+                .channel(NioSocketChannel.class)
+                .option(ChannelOption.TCP_NODELAY, true)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    protected void initChannel(SocketChannel socketChannel) throws Exception {
+                        socketChannel.pipeline()
+                                .addLast(new XDecoder())
+                                .addLast(new StringEncoder())
+                                .addLast(new NettyClientHandler());
+                    }
+                })
+                .remoteAddress(Config.server_ip, Config.tcp_port);
+    }
+
+    public void run(Object msg){
+        // 连接(复用同一个group和bootstrap)
+        future = bootstrap.connect();
+        log.info("客户端正在连接服务端...");
+        // 客户端断线重连逻辑
+        future.addListener((ChannelFutureListener) future1 -> {
+            if (future1.isSuccess()) {
+                MesClient.tcp_connect_flag = true;
+                MesClient.allow_connect_again = true;
+                log.info("连接Netty服务端成功");
+                future.channel().writeAndFlush(msg);
+            } else {
+                MesClient.tcp_connect_flag = false;
+                log.info("连接失败,10秒后重连");
+                // 用同一个group的线程调度重连,不再递归创建新group
+                group.schedule(() -> run(msg), 10, TimeUnit.SECONDS);
+            }
+        });
+        socketChannel = (SocketChannel) future.channel();
+    }
+
+    // 关闭资源
+    public void shutdown() {
+        if (future != null && future.channel() != null) {
+            future.channel().close();
+        }
+        group.shutdownGracefully();
+    }
+}

+ 91 - 0
src/com/mes/netty/NettyClientHandler.java

@@ -0,0 +1,91 @@
+package com.mes.netty;
+
+import com.mes.ui.MesClient;
+import com.mes.ui.MesRevice;
+import com.mes.util.HttpUtils;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class NettyClientHandler extends ChannelInboundHandlerAdapter {
+
+    public static final Logger log =  LoggerFactory.getLogger(NettyClientHandler.class);
+
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    public static byte[] responseByte;
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        log.info("mes connecting:{}", sdf.format(new Date()));
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        Channel channel = ctx.channel();
+        log.info("TCP连接断开, ip: {}", channel.remoteAddress());
+        MesClient.tcp_connect_flag = false;
+        channel.close();     // 关闭通道
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        log.info("==============================================================================");
+        log.info("| TCP返回报文内容: {}", msg);
+        String mes_msg = formatResult(msg.toString());
+        if(mes_msg.isEmpty()){ // error msg
+            log.info("| TCP返回报文内容格式错误, 弃用!");
+            log.info("==============================================================================");
+            return;
+        }
+
+        String msg_type = ProtocolParam.getMsgType(mes_msg);
+        log.info("| TCP返回报文类型: {}", msg_type);
+
+        String processMsgRet = MesMsgUtils.processMsg(mes_msg, msg_type);
+        log.info("| TCP返回报文结果: {}", processMsgRet);
+        log.info("==============================================================================");
+        switch(msg_type) {
+            case "AQDW": // 查询质量
+                MesRevice.checkQualityRevice(processMsgRet,mes_msg);
+                break;
+            case "MKSW":
+                MesRevice.startRevice(processMsgRet,mes_msg);
+                break;
+            case "MBDW":
+                MesRevice.bindRevice(processMsgRet,mes_msg);
+                break;
+            case "MJBW":
+                MesRevice.unbindRevice(processMsgRet,mes_msg);
+                break;
+            case "VLDW":
+                MesRevice.validateColdPlateRevice(processMsgRet,mes_msg);
+                break;
+            case "MQDW":
+                MesRevice.updateResultRevice(processMsgRet,mes_msg);
+                break;
+        }
+
+
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        ctx.close();
+    }
+
+    private String formatResult(String msg){
+//        log.info("length:{} content:{}", msg.length(), msg);
+        String msgType = msg.substring(0,2).trim();
+        if(msgType.equals("OK")){
+            return msg.substring(3,msg.length());
+        }else{
+            return "";
+        }
+    }
+
+}

+ 77 - 0
src/com/mes/netty/ProtocolParam.java

@@ -0,0 +1,77 @@
+package com.mes.netty;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// 固定格式报文各参数获取方法
+public class ProtocolParam {
+    public static final Logger log =  LoggerFactory.getLogger(ProtocolParam.class);
+    // bbbbfffffARWAQDWGWOP100 GY100000ID151245P00000106200123062900001      RSOKDA2023-09-07ZT10:16:58
+    public static Integer fixedLength = 96; // 固定长度
+
+    // 获取消息类型  所有报文都可使用
+    public static String getMsgType(String msg){
+//        log.info(msg);
+        if(msg.length() < 16){
+            return "";
+        }
+        return msg.substring(12,16);
+    }
+
+    // 获取工位号
+    public static String getOprno(String msg){
+        if(msg.length() < 24){
+            return "";
+        }
+        return msg.substring(18,24);
+    }
+
+    // 获取工艺号
+    public static String getCraft(String msg){
+        if(msg.length() < 32){
+            return "";
+        }
+        return msg.substring(26,32);
+    }
+
+    // 获取镭雕码或设备报警故障代码
+    public static String getSn(String msg){
+        if(msg.length() < 70){
+            return "";
+        }
+        return msg.substring(34,70);
+    }
+
+    public static String getLx(String msg){
+        if(msg.length() < 72){
+            return "";
+        }
+        return msg.substring(70,72);
+    }
+
+    // 获取结果
+    public static String getResult(String msg){
+        if(msg.length() < 74){
+            return "";
+        }
+        return msg.substring(72,74);
+    }
+
+    // 获取日期
+    public static String getDay(String msg){
+        if(msg.length() < 86){
+            return "";
+        }
+        return msg.substring(76,86);
+    }
+
+    // 获取时间
+    public static String getTime(String msg){
+        if(msg.length() < 96){
+            return "";
+        }
+        return msg.substring(88,96);
+    }
+
+}

+ 182 - 0
src/com/mes/netty/XDecoder.java

@@ -0,0 +1,182 @@
+package com.mes.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class XDecoder extends ByteToMessageDecoder {
+
+    public static final Logger log =  LoggerFactory.getLogger(XDecoder.class);
+    static final int PACKET_SIZE = 46; // 最短包长度
+    static final int PACKET_MAX_SIZE = 1000; // 最长包长度
+
+    // 用来临时保留没有处理过的请求报文
+    ByteBuf tempMsg = Unpooled.buffer();
+
+    @Override
+    protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+        // 连接断开时释放临时缓冲区,防止堆外内存泄漏
+        if (tempMsg != null && tempMsg.refCnt() > 0) {
+            tempMsg.release();
+        }
+    }
+
+    /**
+     * @param ctx
+     * @param in 请求的数据
+     * @param out 将粘在一起的报文拆分后的结果保留起来
+     * @throws Exception
+     */
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+//        log.info(Thread.currentThread() + "收到了一次数据包,长度是:" + in.readableBytes());
+//        String content = hexStringToAscii(ByteBufUtil.hexDump(in));
+//        log.info("接收包内容:" + hexStringToAscii(ByteBufUtil.hexDump(in)));
+
+        // 合并报文
+        ByteBuf message = null;
+        int tmpMsgSize = tempMsg.readableBytes();
+        // 如果暂存有上一次余下的请求报文,则合并
+        if (tmpMsgSize > 0) {
+            message = Unpooled.buffer();
+            message.writeBytes(tempMsg);
+            message.writeBytes(in);
+//            log.info("合并:上一数据包余下的长度为:" + tmpMsgSize + ",合并后长度为:" + message.readableBytes());
+//            log.info("合并后包内容:" + hexStringToAscii(ByteBufUtil.hexDump(message)));
+        } else {
+            message = in;
+        }
+
+//        log.info(Thread.currentThread() + "收到了一次数据包,长度是:" + message.readableBytes());
+        int size = 0;
+        String split = "bbbbfffffARW";
+
+        while (true){
+            size = message.readableBytes();
+            if(size >= PACKET_SIZE){
+                String retType = "NG:";
+                int qcsize = 0;
+                String str = hexStringToAscii(ByteBufUtil.hexDump(message));
+                String[] lists = str.split(split);
+
+                if(lists.length == 1 && size > PACKET_MAX_SIZE){ // 大于最大包长度
+                    byte[] request = new byte[size - PACKET_MAX_SIZE];
+                    // 每次从总的消息中读取n个字节的数据
+                    message.readBytes(request);
+                    // 将拆分后的结果放入out列表中,交由后面的业务逻辑去处理
+                    out.add(retType+hexStringToAscii(ByteBufUtil.hexDump(Unpooled.copiedBuffer(request))));
+                    break;
+                }
+
+                if(lists[0].length() > 0){
+                    byte[] request = new byte[lists[0].length()];
+                    // 每次从总的消息中读取n个字节的数据
+                    message.readBytes(request);
+                    // 将拆分后的结果放入out列表中,交由后面的业务逻辑去处理
+                    out.add(retType+hexStringToAscii(ByteBufUtil.hexDump(Unpooled.copiedBuffer(request))));
+                }else{
+                    if(lists.length > 1){
+                        String ss = lists[1];
+                        if(ss.length() <= 4){
+                            qcsize += ss.length();
+                        }else{
+                            String msgType = ProtocolParam.getMsgType(split+ss);
+                            if(!MesMsgUtils.isMsgTypeOk(msgType)){
+                                qcsize += split.length() + ss.length();
+                            }else{
+                                Integer ppsize = checkResultSize(ss,msgType,split);
+                                if(ppsize > 0){
+                                    qcsize = ppsize;
+                                    retType = "OK:";
+                                }else{
+                                    qcsize += split.length() + ss.length();
+                                }
+                            }
+                        }
+                    }
+                }
+
+
+                if(qcsize > 0){
+                    byte[] request = new byte[qcsize];
+                    // 每次从总的消息中读取n个字节的数据
+                    message.readBytes(request);
+                    // 将拆分后的结果放入out列表中,交由后面的业务逻辑去处理
+                    out.add(retType+hexStringToAscii(ByteBufUtil.hexDump(Unpooled.copiedBuffer(request))));
+                }
+            }else{
+                break;
+            }
+        }
+
+        // 多余的报文存起来
+        // 第一个报文: i+  暂存
+        // 第二个报文: 1 与第一次
+        size = message.readableBytes();
+        if (size != 0) {
+//            log.info("多余的数据长度:" + size);
+            // 剩下来的数据放到tempMsg暂存
+            tempMsg.clear();
+            tempMsg.writeBytes(message.readBytes(size));
+        }
+    }
+
+    private Integer checkResultSize(String str, String msg_type, String split){
+        String oldstr = str;
+        str = split+str;
+        int tpsize = 0;
+        log.info("checkResultSize: msg_type=" + msg_type + ", str.length()=" + str.length() + ", fixedLength=" + ProtocolParam.fixedLength);
+        switch(msg_type) {
+            case "SYNR": // 同步
+                if(str.length() >= MesMsgUtils.SYNR_LEN){
+                    tpsize = MesMsgUtils.SYNR_LEN;
+                }
+                break;
+            case "AXTW": // 心跳
+                if(str.length() >= MesMsgUtils.AXTW_LEN){
+                    tpsize = MesMsgUtils.AXTW_LEN;
+                }
+                break;
+            case "ACLW": // 重连
+                if(str.length() >= MesMsgUtils.ACLW_LEN){
+                    tpsize = MesMsgUtils.ACLW_LEN;
+                }
+                break;
+            case "MCJW":
+            case "AQDW":
+            case "MBDW":
+            case "MJBW":
+            case "MQDW":
+            case "MKSW":
+            case "MSBW":
+            case "MCSW":
+            case "AQRW":
+            case "VLDW":
+            default: // 默认新报文
+                if(str.length() >= ProtocolParam.fixedLength){ // 大于固定长度
+                    tpsize = ProtocolParam.fixedLength;
+                }
+                break;
+        }
+
+        return tpsize;
+    }
+
+    // 16字符串转Ascii
+    private String hexStringToAscii(String hexString) {
+        StringBuilder sbuilder = new StringBuilder();
+        for (int i = 0; i < hexString.length(); i += 2) {
+            String hexByte = hexString.substring(i, i + 2);
+            int byteValue = Integer.parseInt(hexByte, 16);
+            char c = (char) byteValue;
+            sbuilder.append(c);
+        }
+        return sbuilder.toString();
+    }
+}

+ 560 - 0
src/com/mes/test/PlcDebugTest.java

@@ -0,0 +1,560 @@
+package com.mes.test;
+
+import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Scanner;
+
+/**
+ * PLC调试测试类 - 用于测试拉铆枪点位读取和启停控制
+ * 
+ * 功能:
+ * 1. 测试拉铆枪的各个数据点位能否正常读取
+ * 2. 测试拉铆枪启停控制点位能否正常读写
+ * 
+ * 拉铆枪启停控制点位说明:
+ * - 远程开机点位: 3328 (Coil)
+ * - 远程关机点位: 3079 (Coil)
+ * - 控制模式: 1090 (0=本地, 1=远程)
+ * - 系统运行中: 3128 (Coil)
+ */
+public class PlcDebugTest {
+    
+    private static final Logger log = LoggerFactory.getLogger(PlcDebugTest.class);
+    
+    // PLC连接配置
+    private static final String PLC_A_IP = "192.168.0.6";
+    private static final String PLC_B_IP = "192.168.0.16";
+    private static final int UNIT_ID = 1;
+    
+    private static ModbusTcp plcA;
+    private static ModbusTcp plcB;
+    private static Scanner scanner;
+    
+    public static void main(String[] args) {
+        scanner = new Scanner(System.in);
+        
+        log.info("=== PLC调试测试工具启动 ===");
+        
+        try {
+            // 初始化PLC连接
+            initPlcConnections();
+            
+            // 显示主菜单
+            showMainMenu();
+            
+        } catch (Exception e) {
+            log.error("程序运行出错: " + e.getMessage(), e);
+        } finally {
+            closeConnections();
+            scanner.close();
+        }
+    }
+    
+    /**
+     * 初始化PLC连接
+     */
+    private static void initPlcConnections() {
+        try {
+            log.info("正在连接PLC A (" + PLC_A_IP + ")...");
+            plcA = new ModbusTcp(UNIT_ID, PLC_A_IP);
+            plcA.setConnectTimeout(1000);
+            plcA.setReceiveTimeout(1000);
+            log.info("PLC A连接成功");
+        } catch (Exception e) {
+            log.error("PLC A连接失败: " + e.getMessage());
+        }
+        
+        try {
+            log.info("正在连接PLC B (" + PLC_B_IP + ")...");
+            plcB = new ModbusTcp(UNIT_ID, PLC_B_IP);
+            plcB.setConnectTimeout(1000);
+            plcB.setReceiveTimeout(1000);
+            log.info("PLC B连接成功");
+        } catch (Exception e) {
+            log.error("PLC B连接失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 显示主菜单
+     */
+    private static void showMainMenu() {
+        while (true) {
+            System.out.println("\n========================================");
+            System.out.println("       PLC调试测试工具主菜单");
+            System.out.println("========================================");
+            System.out.println("1. 读取拉铆枪A当前数据");
+            System.out.println("2. 读取拉铆枪B当前数据");
+            System.out.println("3. 启动拉铆枪A心跳(模拟质量检查合格)");
+            System.out.println("4. 停止拉铆枪A心跳(模拟工作完成)");
+            System.out.println("5. 启动拉铆枪B心跳(模拟质量检查合格)");
+            System.out.println("6. 停止拉铆枪B心跳(模拟工作完成)");
+            System.out.println("7. 查看拉铆枪A心跳状态");
+            System.out.println("8. 查看拉铆枪B心跳状态");
+            System.out.println("0. 退出");
+            System.out.println("========================================");
+            System.out.print("请选择功能 (0-8): ");
+            
+            String choice = scanner.nextLine().trim();
+            
+            switch (choice) {
+                case "1":
+                    testRivetGunDataA();
+                    break;
+                case "2":
+                    testRivetGunDataB();
+                    break;
+                case "3":
+                    startHeartBeatA();
+                    break;
+                case "4":
+                    stopHeartBeatA();
+                    break;
+                case "5":
+                    startHeartBeatB();
+                    break;
+                case "6":
+                    stopHeartBeatB();
+                    break;
+                case "7":
+                    testHeartBeatControlA();
+                    break;
+                case "8":
+                    testHeartBeatControlB();
+                    break;
+                case "0":
+                    log.info("退出程序");
+                    return;
+                default:
+                    System.out.println("无效的选择,请重新输入!");
+            }
+        }
+    }
+    
+    /**
+     * 测试拉铆枪A的数据点位读取
+     */
+    private static void testRivetGunDataA() {
+        if (plcA == null) {
+            System.out.println("错误: PLC A未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 测试拉铆枪A数据点位 ===");
+        
+        try {
+            // 心跳连接
+            short heartBeatConn = plcA.readInt16(4160);
+            System.out.println("心跳连接 (地址4160): " + heartBeatConn);
+            
+            // 心跳间隔
+            short heartBeatInterval = plcA.readInt16(4170);
+            System.out.println("心跳间隔 (地址4170): " + heartBeatInterval + " (超时后设备禁止运行)");
+            
+            // 作业模式
+            short workMode = plcA.readInt16(1092);
+            System.out.println("作业模式 (地址1092): " + workMode);
+            
+            // 最大拉力 F-out
+            int fOut = plcA.readInt32(4112);
+            System.out.println("F-out最大拉力 (地址4112): " + fOut + " (实际值: " + (fOut/1000.0) + " kN)");
+            
+            // S-out对应位移
+            int sOut = plcA.readInt32(4120);
+            System.out.println("S-out对应位移 (地址4120): " + sOut + " (实际值: " + (sOut/1000.0) + " mm)");
+            
+            // F-min最小拉力
+            int fMin = plcA.readInt32(4116);
+            System.out.println("F-min最小拉力 (地址4116): " + fMin + " (实际值: " + (fMin/1000.0) + " kN)");
+            
+            // F-max最大拉力
+            int fMax = plcA.readInt32(4114);
+            System.out.println("F-max最大拉力 (地址4114): " + fMax + " (实际值: " + (fMax/1000.0) + " kN)");
+            
+            // S-min最小位移
+            int sMin = plcA.readInt32(4124);
+            System.out.println("S-min最小位移 (地址4124): " + sMin + " (实际值: " + (sMin/1000.0) + " mm)");
+            
+            // S-max最大位移
+            int sMax = plcA.readInt32(4126);
+            System.out.println("S-max最大位移 (地址4126): " + sMax + " (实际值: " + (sMax/1000.0) + " mm)");
+            
+            // 预设数量/任务数量
+            int taskNum = plcA.readInt32(4534);
+            System.out.println("预设数量 (地址4534): " + taskNum);
+            
+            // 已打数量/当前数量
+            short curNum = plcA.readInt16(66);
+            System.out.println("已打数量 (地址66): " + curNum);
+            
+            // 合格数量
+            short qualifiedNum = plcA.readInt16(68);
+            System.out.println("合格数量 (地址68): " + qualifiedNum);
+            
+            // 质量状态
+            short qualityStatus = plcA.readInt16(1072);
+            System.out.println("质量状态 (地址1072): " + qualityStatus);
+            
+            System.out.println("\n✓ 拉铆枪A数据点位读取成功!");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 读取失败: " + e.getMessage());
+            log.error("读取拉铆枪A数据失败", e);
+        }
+    }
+    
+    /**
+     * 测试拉铆枪B的数据点位读取
+     */
+    private static void testRivetGunDataB() {
+        if (plcB == null) {
+            System.out.println("错误: PLC B未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 测试拉铆枪B数据点位 ===");
+        
+        try {
+            // 心跳连接
+            short heartBeatConn = plcB.readInt16(4160);
+            System.out.println("心跳连接 (地址4160): " + heartBeatConn);
+            
+            // 心跳间隔
+            short heartBeatInterval = plcB.readInt16(4170);
+            System.out.println("心跳间隔 (地址4170): " + heartBeatInterval + " (超时后设备禁止运行)");
+            
+            // 作业模式
+            short workMode = plcB.readInt16(1092);
+            System.out.println("作业模式 (地址1092): " + workMode);
+            
+            // 最大拉力 F-out
+            int fOut = plcB.readInt32(4112);
+            System.out.println("F-out最大拉力 (地址4112): " + fOut + " (实际值: " + (fOut/1000.0) + " kN)");
+            
+            // S-out对应位移
+            int sOut = plcB.readInt32(4120);
+            System.out.println("S-out对应位移 (地址4120): " + sOut + " (实际值: " + (sOut/1000.0) + " mm)");
+            
+            // F-min最小拉力
+            int fMin = plcB.readInt32(4116);
+            System.out.println("F-min最小拉力 (地址4116): " + fMin + " (实际值: " + (fMin/1000.0) + " kN)");
+            
+            // F-max最大拉力
+            int fMax = plcB.readInt32(4114);
+            System.out.println("F-max最大拉力 (地址4114): " + fMax + " (实际值: " + (fMax/1000.0) + " kN)");
+            
+            // S-min最小位移
+            int sMin = plcB.readInt32(4124);
+            System.out.println("S-min最小位移 (地址4124): " + sMin + " (实际值: " + (sMin/1000.0) + " mm)");
+            
+            // S-max最大位移
+            int sMax = plcB.readInt32(4126);
+            System.out.println("S-max最大位移 (地址4126): " + sMax + " (实际值: " + (sMax/1000.0) + " mm)");
+            
+            // 预设数量/任务数量
+            int taskNum = plcB.readInt32(4534);
+            System.out.println("预设数量 (地址4534): " + taskNum);
+            
+            // 已打数量/当前数量
+            short curNum = plcB.readInt16(1136);
+            System.out.println("已打数量 (地址1136): " + curNum);
+            
+            // 合格数量
+            short qualifiedNum = plcB.readInt16(68);
+            System.out.println("合格数量 (地址68): " + qualifiedNum);
+            
+            // 质量状态
+            short qualityStatus = plcB.readInt16(1072);
+            System.out.println("质量状态 (地址1072): " + qualityStatus);
+            
+            System.out.println("\n✓ 拉铆枪B数据点位读取成功!");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 读取失败: " + e.getMessage());
+            log.error("读取拉铆枪B数据失败", e);
+        }
+    }
+    
+    // 心跳定时器
+    private static java.util.Timer heartBeatTimerA;
+    private static java.util.Timer heartBeatTimerB;
+    private static boolean heartBeatFlagA = false;
+    private static boolean heartBeatFlagB = false;
+    private static short heartBeatValueA = 1; // 心跳值,在1和2之间切换
+    private static short heartBeatValueB = 1; // 心跳值,在1和2之间切换
+    
+    /**
+     * 启动拉铆枪A心跳 - 模拟质量检查合格后启动
+     */
+    private static void startHeartBeatA() {
+        if (plcA == null) {
+            System.out.println("错误: PLC A未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 启动拉铆枪A心跳 ===");
+        System.out.println("警告: 此操作将启动心跳,拉铆枪A将被启用!");
+        System.out.print("是否继续? (y/n): ");
+        
+        String confirm = scanner.nextLine().trim().toLowerCase();
+        if (!confirm.equals("y")) {
+            System.out.println("已取消操作");
+            return;
+        }
+        
+        try {
+            // 停止旧的定时器
+            if (heartBeatTimerA != null) {
+                heartBeatTimerA.cancel();
+            }
+            
+            // 先设置心跳间隔时间(10代表1秒)
+            plcA.writeInt16(4170, (short) 10);
+            System.out.println("已设置心跳间隔为10(代表1秒)");
+            
+            heartBeatFlagA = true;
+            heartBeatValueA = 1; // 初始化心跳值
+            heartBeatTimerA = new java.util.Timer();
+            heartBeatTimerA.schedule(new java.util.TimerTask() {
+                public void run() {
+                    try {
+                        if (heartBeatFlagA) {
+                            // 向4160写入相邻不重复的数值(1, 2, 3循环)
+                            plcA.writeInt16(4160, heartBeatValueA);
+                            System.out.println("心跳A: 写入4160=" + heartBeatValueA);
+                            
+                            // 切换心跳值(1→2→3→1循环)
+                            heartBeatValueA = (short) (heartBeatValueA % 3 + 1);
+                        }
+                    } catch (Exception e) {
+                        log.error("心跳A执行失败: " + e.getMessage());
+                    }
+                }
+            }, 100, 500);
+            
+            System.out.println("✓ 拉铆枪A心跳已启动!");
+            System.out.println("提示: 心跳每500ms向地址4160写入1、2、3循环");
+            System.out.println("提示: 如果1秒内没有心跳,设备将禁止运行");
+            System.out.println("提示: 使用菜单4停止心跳");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 启动失败: " + e.getMessage());
+            log.error("启动拉铆枪A心跳失败", e);
+        }
+    }
+    
+    /**
+     * 停止拉铆枪A心跳 - 模拟工作完成后停止
+     */
+    private static void stopHeartBeatA() {
+        System.out.println("\n=== 停止拉铆枪A心跳 ===");
+        
+        try {
+            heartBeatFlagA = false;
+            if (heartBeatTimerA != null) {
+                heartBeatTimerA.cancel();
+                heartBeatTimerA = null;
+            }
+            
+            System.out.println("✓ 拉铆枪A心跳已停止!");
+            System.out.println("提示: 拉铆枪A现在应该自动停止工作");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 停止失败: " + e.getMessage());
+            log.error("停止拉铆枪A心跳失败", e);
+        }
+    }
+    
+    /**
+     * 启动拉铆枪B心跳 - 模拟质量检查合格后启动
+     */
+    private static void startHeartBeatB() {
+        if (plcB == null) {
+            System.out.println("错误: PLC B未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 启动拉铆枪B心跳 ===");
+        System.out.println("警告: 此操作将启动心跳,拉铆枪B将被启用!");
+        System.out.print("是否继续? (y/n): ");
+        
+        String confirm = scanner.nextLine().trim().toLowerCase();
+        if (!confirm.equals("y")) {
+            System.out.println("已取消操作");
+            return;
+        }
+        
+        try {
+            // 停止旧的定时器
+            if (heartBeatTimerB != null) {
+                heartBeatTimerB.cancel();
+            }
+            
+            // 先设置心跳间隔时间(10代表1秒)
+            plcB.writeInt16(4170, (short) 10);
+            System.out.println("已设置心跳间隔为10(代表1秒)");
+            
+            heartBeatFlagB = true;
+            heartBeatValueB = 1; // 初始化心跳值
+            heartBeatTimerB = new java.util.Timer();
+            heartBeatTimerB.schedule(new java.util.TimerTask() {
+                public void run() {
+                    try {
+                        if (heartBeatFlagB) {
+                            // 向4160写入相邻不重复的数值(1, 2, 3循环)
+                            plcB.writeInt16(4160, heartBeatValueB);
+                            System.out.println("心跳B: 写入4160=" + heartBeatValueB);
+                            
+                            // 切换心跳值(1→2→3→1循环)
+                            heartBeatValueB = (short) (heartBeatValueB % 3 + 1);
+                        }
+                    } catch (Exception e) {
+                        log.error("心跳B执行失败: " + e.getMessage());
+                    }
+                }
+            }, 100, 500);
+            
+            System.out.println("✓ 拉铆枪B心跳已启动!");
+            System.out.println("提示: 心跳每500ms向地址4160写入1、2、3循环");
+            System.out.println("提示: 如果1秒内没有心跳,设备将禁止运行");
+            System.out.println("提示: 使用菜单6停止心跳");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 启动失败: " + e.getMessage());
+            log.error("启动拉铆枪B心跳失败", e);
+        }
+    }
+    
+    /**
+     * 停止拉铆枪B心跳 - 模拟工作完成后停止
+     */
+    private static void stopHeartBeatB() {
+        System.out.println("\n=== 停止拉铆枪B心跳 ===");
+        
+        try {
+            heartBeatFlagB = false;
+            if (heartBeatTimerB != null) {
+                heartBeatTimerB.cancel();
+                heartBeatTimerB = null;
+            }
+            
+            System.out.println("✓ 拉铆枪B心跳已停止!");
+            System.out.println("提示: 拉铆枪B现在应该自动停止工作");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 停止失败: " + e.getMessage());
+            log.error("停止拉铆枪B心跳失败", e);
+        }
+    }
+    
+    /**
+     * 测试拉铆枪A心跳控制点位
+     */
+    private static void testHeartBeatControlA() {
+        if (plcA == null) {
+            System.out.println("错误: PLC A未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 查看拉铆枪A心跳状态 ===");
+        System.out.println("心跳控制原理:");
+        System.out.println("1. MES向地址4160写入相邻不重复的数值(如1, 2, 3...1, 2循环)");
+        System.out.println("2. MES向地址4170写入心跳间隔时间(秒)");
+        System.out.println("3. 如果到达时间没有检测到心跳连接,设备禁止运行");
+        
+        try {
+            System.out.println("\n当前状态:");
+            
+            // 读取心跳连接点位
+            short heartBeatConn = plcA.readInt16(4160);
+            System.out.println("心跳连接 (地址4160): " + heartBeatConn + " (MES写入的心跳值)");
+            
+            // 读取心跳间隔点位
+            short heartBeatInterval = plcA.readInt16(4170);
+            System.out.println("心跳间隔 (地址4170): " + heartBeatInterval + " 秒");
+            
+            // 判断心跳状态
+            if (heartBeatConn > 0) {
+                System.out.println("\n状态: ✓ 心跳正常,拉铆枪可以工作");
+            } else {
+                System.out.println("\n状态: ✗ 心跳未启动,拉铆枪禁止运行");
+            }
+            
+            System.out.println("\n提示: 使用菜单3启动心跳,菜单4停止心跳");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 读取失败: " + e.getMessage());
+            log.error("读取拉铆枪A心跳控制点位失败", e);
+        }
+    }
+    
+    /**
+     * 测试拉铆枪B心跳控制点位
+     */
+    private static void testHeartBeatControlB() {
+        if (plcB == null) {
+            System.out.println("错误: PLC B未连接!");
+            return;
+        }
+        
+        System.out.println("\n=== 查看拉铆枪B心跳状态 ===");
+        System.out.println("心跳控制原理:");
+        System.out.println("1. MES向地址4160写入相邻不重复的数值(如1, 2, 3...1, 2循环)");
+        System.out.println("2. MES向地址4170写入心跳间隔时间(秒)");
+        System.out.println("3. 如果到达时间没有检测到心跳连接,设备禁止运行");
+        
+        try {
+            System.out.println("\n当前状态:");
+            
+            // 读取心跳连接点位
+            short heartBeatConn = plcB.readInt16(4160);
+            System.out.println("心跳连接 (地址4160): " + heartBeatConn + " (MES写入的心跳值)");
+            
+            // 读取心跳间隔点位
+            short heartBeatInterval = plcB.readInt16(4170);
+            System.out.println("心跳间隔 (地址4170): " + heartBeatInterval + " 秒");
+            
+            // 判断心跳状态
+            if (heartBeatConn > 0) {
+                System.out.println("\n状态: ✓ 心跳正常,拉铆枪可以工作");
+            } else {
+                System.out.println("\n状态: ✗ 心跳未启动,拉铆枪禁止运行");
+            }
+            
+            System.out.println("\n提示: 使用菜单5启动心跳,菜单6停止心跳");
+            
+        } catch (Exception e) {
+            System.out.println("✗ 读取失败: " + e.getMessage());
+            log.error("读取拉铆枪B心跳控制点位失败", e);
+        }
+    }
+    
+    /**
+     * 关闭连接
+     */
+    private static void closeConnections() {
+        try {
+            // 停止心跳定时器
+            if (heartBeatTimerA != null) {
+                heartBeatTimerA.cancel();
+            }
+            if (heartBeatTimerB != null) {
+                heartBeatTimerB.cancel();
+            }
+            
+            if (plcA != null) {
+                plcA.close();
+                log.info("PLC A连接已关闭");
+            }
+            if (plcB != null) {
+                plcB.close();
+                log.info("PLC B连接已关闭");
+            }
+        } catch (Exception e) {
+            log.error("关闭连接失败", e);
+        }
+    }
+}
+

+ 58 - 0
src/com/mes/ui/BindMaterialResp.java

@@ -0,0 +1,58 @@
+package com.mes.ui;
+
+public class BindMaterialResp {
+    private String materialId;
+    private String materialTitle;
+    private String craft;
+    private String batchSn;
+    private String lastTimes;
+    private String type;
+
+    public String getMaterialId() {
+        return materialId;
+    }
+
+    public void setMaterialId(String materialId) {
+        this.materialId = materialId;
+    }
+
+    public String getMaterialTitle() {
+        return materialTitle;
+    }
+
+    public void setMaterialTitle(String materialTitle) {
+        this.materialTitle = materialTitle;
+    }
+
+    public String getCraft() {
+        return craft;
+    }
+
+    public void setCraft(String craft) {
+        this.craft = craft;
+    }
+
+    public String getBatchSn() {
+        return batchSn;
+    }
+
+    public void setBatchSn(String batchSn) {
+        this.batchSn = batchSn;
+    }
+
+    public String getLastTimes() {
+        return lastTimes;
+    }
+
+    public void setLastTimes(String lastTimes) {
+        this.lastTimes = lastTimes;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}

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

@@ -0,0 +1,329 @@
+package com.mes.ui;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.netty.NettyClient;
+import com.mes.util.Config;
+import com.mes.util.DateLocalUtils;
+import com.mes.util.JdbcUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+public class DataUtil {
+
+    public static final Logger log =  LoggerFactory.getLogger(DataUtil.class);
+
+
+    //    8.30
+    public static JSONObject upParamsA(String upparams) {
+        try{
+            String mes_server_ip = Config.server_ip;
+            String url = "http://"+mes_server_ip+":8980/js/a/mes/mesProductProd/batchsave";
+            String params = "__ajax=json&oprno="+Config.gw_1+"&lineSn="+Config.line_sn+"&params="+upparams;
+            log.info("params="+params);
+            String result = doPost(url,params);
+            log.info("result="+result);
+
+            if(result.equalsIgnoreCase("false")) {
+                return null;
+            }else {
+                return JSONObject.parseObject(result);
+            }
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    public static JSONObject upParamsB(String upparams) {
+        try{
+            String mes_server_ip = Config.server_ip;
+            String url = "http://"+mes_server_ip+":8980/js/a/mes/mesProductProd/batchsave";
+            String params = "__ajax=json&oprno="+Config.gw_2+"&lineSn="+Config.line_sn+"&params="+upparams;
+            log.info("params="+params);
+            String result = doPost(url,params);
+            log.info("result="+result);
+
+            if(result.equalsIgnoreCase("false")) {
+                return null;
+            }else {
+                return JSONObject.parseObject(result);
+            }
+        }catch (Exception e){
+            return null;
+        }
+    }
+    //    8.30//
+    public static Boolean synrTcp(NettyClient nettyClient,String mes_gw){
+        try{
+            //TCP连接后,直接先发同步报文
+            String start = "aaaabbbbbABW";
+            String msgType = "SYNR";
+            String gw = "GW"+DataUtil.rightPad(mes_gw, 6);
+            String da = "DA" + DateLocalUtils.getCurrentDate();
+            String zt = "ZT" + DateLocalUtils.getCurrentTimeHMS();
+            String synr_str = start + msgType + gw + da + zt;
+            //插入报文本地数据
+            String gy = "";
+            String sn = "";
+            JdbcUtils.insertData(gw, gy, synr_str, msgType, sn);
+
+            nettyClient.run(synr_str);
+
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean heartBeat(NettyClient nettyClient,String mes_gw){
+        try{
+            //TCP连接后,直接先发同步报文
+            String start = "aaaabbbbbABW";
+            String msgType = "AXTW";
+            String gw = "GW"+DataUtil.rightPad(mes_gw, 6);
+            String da = "DA" + DateLocalUtils.getCurrentDate();
+            String zt = "ZT" + DateLocalUtils.getCurrentTimeHMS();
+            String axtw_str = start + msgType + gw + da + zt;
+            //插入报文本地数据
+            String gy = "";
+            String sn = "";
+            JdbcUtils.insertData(gw, gy, axtw_str, msgType, sn);
+            nettyClient.future.channel().writeAndFlush(axtw_str);
+
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean checkQuality(NettyClient nettyClient, String sn, String user, String mes_gw){
+        try{
+            String msgType = "AQDW";
+            String gy = "100000";
+            Boolean aqdw_str = sendMessage(nettyClient,msgType,gy,"",sn,"",user,"00","", mes_gw);
+            return aqdw_str;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean startWork(NettyClient nettyClient,String sn,String user, String mes_gw){
+        try{
+            String msgType = "MKSW";
+            String gy = "";
+            Boolean aqdw_str = sendMessage(nettyClient,msgType,gy,"",sn,"",user,"00","", mes_gw);
+            return aqdw_str;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean bindUser(NettyClient nettyClient,String sn,String user, String mes_gw){
+        try{
+            String msgType = "MBDW";
+            String gy = "400001";
+            String param = rightPad(user, 36);
+            Boolean aqdw_str = sendMessage(nettyClient,msgType,gy,"",sn,"",user,"01",param, mes_gw);
+            return aqdw_str;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean bindWarehouse(NettyClient nettyClient,String sn,String wsn,String user,String craft, String mes_gw){
+        try{
+            String msgType = "MBDW";
+            String param = rightPad(wsn, 36);
+            String gwWithoutSuffix = mes_gw.replaceAll("[A-Z]$", "");
+            Boolean aqdw_str = sendMessage(nettyClient,msgType,craft,"",sn,"",user,"01",param, gwWithoutSuffix);
+            return aqdw_str;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean validateColdPlate(NettyClient nettyClient,String sn,String wsn,String user,String craft, String mes_gw){
+        try{
+            String msgType = "VLDW"; // 校验消息类型
+            String param = rightPad(wsn, 36);
+            Boolean validation_result = sendMessage(nettyClient,msgType,craft,"",sn,"",user,"01",param, mes_gw);
+            return validation_result;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean unBindWarehouse(NettyClient nettyClient,String sn,String wsn,String user,String craft, String mes_gw){
+        try{
+            String msgType = "MJBW";
+            String param = rightPad(wsn, 36);
+            Boolean aqdw_str = sendMessage(nettyClient,msgType,craft,"",sn,"",user,"01",param, mes_gw);
+            return aqdw_str;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean sendQuality(NettyClient nettyClient,String sn,String result,String user, String mes_gw){
+        try{
+            String msgType = "MQDW";
+            String craft = "100000";
+            String lx = "";
+            String paramNums = "00";
+            String params = "";
+
+            String gw = "GW"+rightPad(mes_gw, 6);
+            String start = "aaaabbbbbABW";
+            String gy = "GY" + rightPad(craft, 6);
+            String reslx = "LX" + rightPad(lx, 2);
+            String id = Config.line_sn + rightPad(sn, 36);
+            String rs = "RS"+ rightPad(result, 2);
+            String da = "DA" + DateLocalUtils.getCurrentDate();
+            String zt = "ZT" + DateLocalUtils.getCurrentTimeHMS();
+            String yh = "YH" + rightPad(user, 20);
+            String aqdw_str = start + msgType + gw + gy + reslx + id + rs + da + zt + yh + paramNums + params;
+            log.info("向submit_record表中插入更新质量报文: {}", aqdw_str);
+            JdbcUtils.insertData(gw, gy, aqdw_str, msgType, sn);
+            JdbcUtils.insertSubmitRecord(gw, sn, aqdw_str);
+            nettyClient.future.channel().writeAndFlush(aqdw_str);
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static Boolean bindLeakValue(String sn, String leakValue, String gw) {
+        return sendMessage(MesClient.nettyClient, "MBDW", "300001", "",
+                sn, "", MesClient.user20, "01", rightPad(leakValue, 36), gw);
+    }
+
+    public static Boolean sendMessage(NettyClient nettyClient,String msgType,String craft,String lx,String sn,String result,String user,String paramNums,String params, String mes_gw){
+        try{
+            String gw = "GW"+rightPad(mes_gw, 6);
+            String start = "aaaabbbbbABW";
+            String gy = "GY" + rightPad(craft, 6);
+            String reslx = "LX" + rightPad(lx, 2);
+            String id = Config.line_sn + rightPad(sn, 36);
+            String rs = "RS"+ rightPad(result, 2);
+            String da = "DA" + DateLocalUtils.getCurrentDate();
+            String zt = "ZT" + DateLocalUtils.getCurrentTimeHMS();
+            String yh = "YH" + rightPad(user, 20);
+            String aqdw_str = start + msgType + gw + gy + reslx + id + rs + da + zt + yh + paramNums + params;
+            log.info("向服务端发送TCP报文: {}", aqdw_str);
+            JdbcUtils.insertData(gw, gy, aqdw_str, msgType, sn);
+            nettyClient.future.channel().writeAndFlush(aqdw_str);
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    public static String rightPad(final String str, final int size) {
+        if (str == null) {
+            return null;
+        }
+
+        String strret = str;
+        if(str.length() > size){
+            strret = str.substring(0,size);
+        }
+        return String.format("%-"+size+"s", strret);
+    }
+
+    public static JSONObject getBindMaterail() {
+        try{
+
+            String url = "http://"+ Config.server_ip+":8980/js/a/mes/mesLineProcessMaterial/materials";
+            String params = "__ajax=json&oprno="+Config.gw_1.trim()+"&lineSn="+Config.line_sn.trim();
+            String result = doPost(url,params);
+
+            if(result.equalsIgnoreCase("false")) {
+                return null;
+            }else {
+                return JSONObject.parseObject(result);
+            }
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    public static JSONObject saveBindMaterail(String batchSn,String craft,String materialId,String type) {
+        try{
+            String url = "http://"+Config.server_ip+":8980/js/a/mes/mesMaterialPrebind/bind";
+            String params = "__ajax=json&oprno="+Config.gw_1.trim()+"&lineSn="+Config.line_sn.trim()+"&batchSn="+batchSn+"&craft="+craft+"&materialId="+materialId+"&type="+type;
+            String result = doPost(url,params);
+            if(result.equalsIgnoreCase("false")) {
+                return null;
+            }else {
+                return JSONObject.parseObject(result);
+            }
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    public static String doPost(String httpUrl, String param) {
+        HttpURLConnection connection = null;
+        InputStream is = null;
+        OutputStream os = null;
+        BufferedReader br = null;
+        String result = null;
+        try {
+            URL url = new URL(httpUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.setConnectTimeout(15000);
+            connection.setReadTimeout(60000);
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            connection.setRequestProperty("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
+            os = connection.getOutputStream();
+            os.write(param.getBytes());
+            if (connection.getResponseCode() == 200) {
+                is = connection.getInputStream();
+                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+                StringBuffer sbf = new StringBuffer();
+                String temp = null;
+                while ((temp = br.readLine()) != null) {
+                    sbf.append(temp);
+                    sbf.append("\r\n");
+                }
+                result = sbf.toString();
+            }
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (null != br) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != os) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != is) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            connection.disconnect();
+        }
+        return result;
+    }
+}

+ 85 - 0
src/com/mes/ui/LockUtil.java

@@ -0,0 +1,85 @@
+package com.mes.ui;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+
+public class LockUtil {
+    private FileChannel channel;
+
+    private FileLock lock;
+
+    private File file;
+
+    private static LockUtil instance = null;
+
+    /**
+     * 获取唯一进程限制的实例
+     * @Title: getInstance
+     * @return  唯一进程实例
+     */
+    public static LockUtil getInstance(){
+        if(instance == null){
+            instance = new LockUtil();
+        }
+        return instance;
+    }
+
+    /**
+     * 进程是否正在运行
+     * @Title: isAppActive
+     * @return  运行与否
+     */
+    public boolean isAppActive() {
+        File file = new File(".lock");
+        try {
+            channel = new RandomAccessFile(file, "rw").getChannel();
+            try {
+                lock = channel.tryLock();
+
+            } catch (OverlappingFileLockException e) {
+                closeLock();
+                return true;
+            }
+            if (lock == null) {
+                closeLock();
+                return true;
+            }
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                public void run() {
+                    closeLock();
+                    deleteFile();
+                }
+            });
+            return false;
+        } catch (Exception e) {
+            closeLock();
+            return true;
+        }
+    }
+
+    protected void deleteFile() {
+        try {
+            file.delete();
+        } catch (Exception e) {
+
+        }
+
+    }
+
+    private void closeLock() {
+        try {
+            lock.release();
+        } catch (Exception e) {
+
+        }
+
+        try {
+            channel.close();
+        } catch (Exception e) {
+
+        }
+    }
+}

+ 193 - 0
src/com/mes/ui/LoginFrame.java

@@ -0,0 +1,193 @@
+package com.mes.ui;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.component.MesWebView;
+import com.mes.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+
+public class LoginFrame extends JFrame {
+
+    public static final Logger log = LoggerFactory.getLogger(LoginFrame.class);
+    //登录模块组件
+    static JLabel userNameLabel = new JLabel("<html><body>用户名:</body></html>", JLabel.LEFT);//用户名
+    static JLabel userPasswordLabel = new JLabel("<html><body>密码:</body></html>", JLabel.LEFT);//用户名
+    public static JTextField userNameTxt;
+    public static JPasswordField userPasswordTxt;
+    static JButton loginButton = new JButton("用户密码登录");
+    static JButton scanLoginButton = new JButton("扫  码  登  录");
+
+    public LoginFrame() {
+        setTitle("MES系统客户端:OP150" + " - " + Config.gw_des_1);
+
+        ImageIcon bg = new ImageIcon(Objects.requireNonNull(MesClient.class.getResource("/background.png")));
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        JLabel imgLabel = new JLabel(bg);//将背景图放在标签里。
+        getLayeredPane().add(imgLabel, new Integer(Integer.MIN_VALUE));//注意这里是关键,将背景标签添加到frame的LayeredPane面板里。
+        imgLabel.setBounds(0, 0, bg.getIconWidth(), bg.getIconHeight());//设置背景标签的位置
+        Container contentPane = getContentPane();
+        contentPane.setLayout(null);//布局,很重要
+        JPanel welcomePanel = new JPanel();
+        welcomePanel.setLayout(null);
+        welcomePanel.setBounds(30, 330, 890, 300);
+        welcomePanel.setOpaque(false);//背景透明
+        contentPane.add(welcomePanel);
+
+        //登录页面
+        userNameLabel.setBounds(300, 100, 120, 40);
+        userNameLabel.setFont(new java.awt.Font("Dialog", Font.BOLD, 16));
+        userPasswordLabel.setBounds(300, 150, 120, 40);
+        userPasswordLabel.setFont(new java.awt.Font("Dialog", Font.BOLD, 16));
+        userNameTxt = new JTextField(20);
+        userNameTxt.setText("system");
+        userNameTxt.setBounds(400, 105, 150, 30);
+        userPasswordTxt = new JPasswordField(20);
+        userPasswordTxt.setBounds(400, 155, 150, 30);
+        userPasswordTxt.setText("Aa111111");
+
+        loginButton.setFont(new java.awt.Font("Dialog", Font.BOLD, 16));
+        loginButton.setBounds(300, 200, 255, 40);
+        loginButton.setIcon(new ImageIcon(Objects.requireNonNull(MesClient.class.getResource("/bg/user.png"))));
+
+        scanLoginButton.setFont(new java.awt.Font("Dialog", Font.BOLD, 16));
+        scanLoginButton.setBounds(300, 250, 255, 40);
+        scanLoginButton.setIcon(new ImageIcon(Objects.requireNonNull(MesClient.class.getResource("/bg/scan_barcode.png"))));
+
+        welcomePanel.add(userNameLabel);
+        welcomePanel.add(userPasswordLabel);
+        welcomePanel.add(userNameTxt);
+        welcomePanel.add(userPasswordTxt);
+        welcomePanel.add(loginButton);
+        welcomePanel.add(scanLoginButton);
+        loginButton.addActionListener(e -> login());
+        scanLoginButton.addActionListener(e -> scanLogin());
+
+        ((JPanel) contentPane).setOpaque(false); //注意这里,将内容面板设为透明。这样LayeredPane面板中的背景才能显示出来。
+        setResizable(false);//禁止最大化
+        setIconImage(Toolkit.getDefaultToolkit().getImage(MesClient.class.getResource("/bg/logo.png")));
+
+        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+        int width = 902;
+        int height = 678;
+        setBounds((d.width - width) / 2, (d.height - height) / 2 - (d.height - height) / 4, width, height);
+        // 正常模式
+        setVisible(true);
+    }
+
+    //登录
+    public static void login() {
+        String user_str = userNameTxt.getText();
+        String password_str = userPasswordTxt.getText();
+        if (user_str.equalsIgnoreCase("") || password_str.equalsIgnoreCase("")) {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame, "用户名或密码不能为空", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+        String username = Base64Utils.getBase64(user_str);
+        String password = Base64Utils.getBase64(password_str);
+        log.info("登录 -->  用户名: {}, 密码: {}", username, password);
+        String url = "http://" + Config.server_ip + ":8980/js/a/login?__ajax=json&username=" + username + "&password=" + password + "&validCode=&__sid=";
+        String loginResult = HttpUtils.sendRequest(url);
+        if (loginResult.equalsIgnoreCase("false")) {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame, "登录异常,请检查网络或联系网络管理员", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+        } else {
+            JSONObject retObj = JSONObject.parseObject(loginResult);
+            if (retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
+                //检查用户权限是否可登录界面
+                checkUserAuthority(retObj);
+            } else {
+                JOptionPane.showMessageDialog(MesClient.mesClientFrame, "登录失败,用户名或密码错误", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            }
+        }
+    }
+
+    //扫码登录
+    public static void scanLogin() {
+        String scanContent = JOptionPane.showInputDialog(null, "请扫码工牌二维码");
+        log.info("scanContent={}", scanContent);
+        if (scanContent != null && !scanContent.equalsIgnoreCase("")) {
+            String url = "http://" + Config.server_ip + ":8980/js/a/mes/mesLogin/login?__login=true&__ajax=json&username=" + scanContent;
+            String loginResult = HttpUtils.sendRequest(url);
+            log.info("loginResult={}", loginResult);
+            if (loginResult.equalsIgnoreCase("false")) {
+                JOptionPane.showMessageDialog(MesClient.mesClientFrame, "登录异常,请检查网络或联系网络管理员", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            } else {
+                JSONObject retObj = JSONObject.parseObject(loginResult);
+                if (retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
+                    //检查用户权限是否可登录界面
+                    checkUserAuthority(retObj);
+                } else {
+                    JOptionPane.showMessageDialog(MesClient.mesClientFrame, "登录失败,用户名或密码错误", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                }
+            }
+        } else {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame, "扫码内容错误", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+        }
+    }
+
+    //检查用户权限是否可登录界面
+    public static void checkUserAuthority(JSONObject retObj) {
+        //设置登录用户名
+        JSONObject userObj = JSONObject.parseObject(retObj.get("user").toString());
+        String user_id = userObj.getString("id");
+
+        //获取session id,判断权限
+        MesClient.sessionid = retObj.get("sessionid").toString();
+        if (MesClient.sessionid != null && !MesClient.sessionid.equalsIgnoreCase("")) {
+            //请求权限
+            String url_authority = "http://" + Config.server_ip + ":8980/js/a/mes/mesLineProcess/userAuth?__ajax=json&type=0&__sid=" + MesClient.sessionid + "&oprno=" + Config.gw_1 + "&lineSn=" + Config.line_sn;
+            String authorityResult = HttpUtils.sendRequest(url_authority);
+            JSONObject authorityObj = JSONObject.parseObject(authorityResult);
+            if (authorityObj.get("result") != null && authorityObj.get("result").toString().equalsIgnoreCase("true")) {
+                JSONObject authObjTmp = JSONObject.parseObject(authorityObj.get("data").toString());
+                MesClient.mes_auth = Integer.parseInt(authObjTmp.getString("auth"));
+                if (MesClient.mes_auth == 0) {
+                    //无权限登录
+                    JOptionPane.showMessageDialog(MesClient.mesClientFrame, "您无权登录该工位", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                } else if (MesClient.mes_auth == 1 || MesClient.mes_auth == 2) {
+                    // 获取等于所处时间-当前小时
+                    MesClient.userLoginHours = LocalDateTime.now().getHour();
+                    //初始化tcp连接,发送同步报文
+                    MesClient.initTcpConnection();
+                    //启动timer心跳包
+                    MesClient.startHeartBeatTimer();
+
+                    //1操作工人,2管理员
+                    //登录成功
+                    MesClient.user_menu.setText(user_id);
+                    MesClient.welcomeWin.setVisible(false);
+                    MesClient.mesClientFrame.setVisible(true);
+                    MesClient.getUser();
+
+
+                    String url1 = "http://" + Config.server_ip + ":8980/js/a/mes/mesProductRecord/work?" + "oprno=OP150" + "&lineSn=" + Config.line_sn;
+                    MesWebView jfxPanel = new MesWebView(url1);
+                    jfxPanel.setSize(990, 550);
+                    MesClient.indexPanelB.add(jfxPanel);
+
+
+                    String url2 = "http://" + Config.server_ip + ":8980/js/a/mes/mesProcessCheckRecord/ulist?ucode=" + user_id + "&oprno=OP150" + "&lineSn=" + Config.line_sn;
+                    MesWebView jfxPanel2 = new MesWebView(url2);
+                    jfxPanel2.setSize(990, 550);
+                    MesClient.indexPanelC.add(jfxPanel2);
+
+
+
+
+                    MesClient.initWarehouseData();
+//                    UpdateQualityTimer.start();  // 开启更新质量的定时任务
+//                    WorkTimer.start();
+                }
+
+            }
+
+        } else {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame, "登录失败,用户名或密码错误", "提示窗口", JOptionPane.INFORMATION_MESSAGE);
+        }
+    }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1619 - 0
src/com/mes/ui/MesClient.java


+ 261 - 0
src/com/mes/ui/MesRevice.java

@@ -0,0 +1,261 @@
+package com.mes.ui;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.netty.ProtocolParam;
+import com.mes.util.Config;
+import com.mes.util.ErrorMsg;
+import com.mes.util.HttpUtils;
+import com.mes.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MesRevice {
+    private static final Logger log = LoggerFactory.getLogger(MesRevice.class);
+
+    // 质量查询
+    public static void checkQualityRevice(String processMsgRet,String mes_msg){
+        try{
+            String oprno = ProtocolParam.getOprno(mes_msg).trim();
+            if (Config.gw_1.equalsIgnoreCase(oprno)) {
+                if(processMsgRet.equalsIgnoreCase("UD")) {
+                    MesClient.check_quality_result1 = true;  //质量合格,可以绑定加工
+                    MesClient.setMenuState_1("该工件可以加工", 0);
+                    if(MesClient.work_status1 == 0 ){
+                        String barcode = MesClient.getBarcode(MesClient.product_sn_1.getText());//处理36为码
+                        DataUtil.startWork(MesClient.nettyClient, barcode,MesClient.user20, Config.gw_1);
+
+                    MesClient.work_status1 = 1;
+                    MesClient.f_scan_data_bt_1.setEnabled(false);
+                    // 启用扫冷板码按钮
+                    MesClient.f_scan_data_bt_lb_1.setEnabled(true);
+                    // 扫码状态可以扫码防止重复扫码
+                    MesClient.scan_status_1 = true;
+                    // 注释掉:质量检测合格时不启动心跳,改为冷板校验通过时启动
+                    // MesClient.heartBeatFlag = true;
+                    
+                    // MES扫码成功合格后,给PLC地址8写1
+                        try {
+                            if (MesClient.plcA != null) {
+                                MesClient.plcA.writeInt16(8, (short) 1);
+                                log.info("A工位MES扫码合格,向地址8写入1");
+                                // 读取验证写入是否成功
+                                Short readValue8 = MesClient.plcA.readInt16(8);
+                                log.info("A工位地址8读取验证值:" + readValue8);
+                            }
+                        } catch (Exception e) {
+                            log.error("A工位向地址8写入1失败:" + e.getMessage());
+                        }
+                        
+                        // 工件码合格后自动弹出冷板码扫码框
+                        MesClient.scanBarcodeLb1();
+                    }
+                }else {
+                    MesClient.check_quality_result1 = false;
+                    String lmsg = ErrorMsg.getErrorMsg(processMsgRet, ProtocolParam.getLx(mes_msg));
+                    if("QD".equals(processMsgRet) && "20".equals(ProtocolParam.getLx(mes_msg))){
+                        JSONObject json = new JSONObject().fluentPut("sn", MesClient.product_sn_1.getText().trim());
+                        String response = HttpUtils.sendJsonPost(String.format("http://%s:8980/js/a/mes/mesProductRecord/checkOP200State", Config.server_ip), json.toJSONString(), 2000);
+                        JSONObject jsonObject = JSON.parseObject(response);
+                        log.info("{}检查OP200状态接口响应: {}", Config.gw_1, response);
+                        if (jsonObject != null && jsonObject.getBoolean("result")) {
+                            lmsg = "恒温静置还有" + jsonObject.getInteger("data") + "分钟";
+                        }
+                    }
+                    MesClient.setMenuState_1(lmsg,-1);
+                }
+            } else if(Config.gw_2.equalsIgnoreCase(oprno)){
+                if(processMsgRet.equalsIgnoreCase("UD")) {
+                    MesClient.check_quality_result2 = true;  //质量合格,可以绑定加工
+                    MesClient.setMenuState_2("该工件可以加工", 0);
+                    if(MesClient.work_status2 == 0 ){
+                        String barcode = MesClient.getBarcode(MesClient.product_sn_2.getText());//处理36为码
+                        DataUtil.startWork(MesClient.nettyClient, barcode,MesClient.user20, Config.gw_2);
+
+                    MesClient.work_status2 = 1;
+                    MesClient.f_scan_data_bt_2.setEnabled(false);
+                    // 启用扫冷板码按钮
+                    MesClient.f_scan_data_bt_lb_2.setEnabled(true);
+                    // 扫码状态可以扫码防止重复扫码
+                    MesClient.scan_status_2 = true;
+                    // 注释掉:质量检测合格时不启动心跳,改为冷板校验通过时启动
+                    // MesClient.heartBeatFlag2 = true;
+                    
+                    // MES扫码成功合格后,给PLC地址8写1
+                        try {
+                            if (MesClient.plcB != null) {
+                                MesClient.plcB.writeInt16(8, (short) 1);
+                                log.info("B工位MES扫码合格,向地址8写入1");
+                                // 读取验证写入是否成功
+                                Short readValue8 = MesClient.plcB.readInt16(8);
+                                log.info("B工位地址8读取验证值:" + readValue8);
+                            }
+                        } catch (Exception e) {
+                            log.error("B工位向地址8写入1失败:" + e.getMessage());
+                        }
+                        
+                        // 工件码合格后自动弹出冷板码扫码框
+                        MesClient.scanBarcodeLb2();
+                    }
+                }else {
+                    MesClient.check_quality_result2 = false;
+                    String lmsg = ErrorMsg.getErrorMsg(processMsgRet, ProtocolParam.getLx(mes_msg));
+                    if("QD".equals(processMsgRet) && "20".equals(ProtocolParam.getLx(mes_msg))){
+                        JSONObject json = new JSONObject().fluentPut("sn", MesClient.product_sn_2.getText().trim());
+                        String response = HttpUtils.sendJsonPost(String.format("http://%s:8980/js/a/mes/mesProductRecord/checkOP200State", Config.server_ip), json.toJSONString(), 2000);
+                        JSONObject jsonObject = JSON.parseObject(response);
+                        log.info("{}检查OP200状态接口响应: {}", Config.gw_2, response);
+                        if (jsonObject != null && jsonObject.getBoolean("result")) {
+                            lmsg = "恒温静置还有" + jsonObject.getInteger("data") + "分钟";
+                        }
+                    }
+                    MesClient.setMenuState_2(lmsg,-1);
+                }
+            }
+        }catch (Exception e){
+            log.error("质量查询报文回调出现错误", e);
+        }
+    }
+
+    // 开始回复处理
+    public static void startRevice(String processMsgRet,String mes_msg){
+//        try{
+//            String oprno = ProtocolParam.getOprno(mes_msg).trim();
+//            if (Config.gw_1.equalsIgnoreCase(oprno)) {
+//                if(processMsgRet.equalsIgnoreCase("OK")) {
+//                    MesClient.work_status1 = 1;
+//                    MesClient.f_scan_data_bt_1.setEnabled(false);
+////                    MesClient.finish_ok_bt_1.setEnabled(true);
+////                    MesClient.finish_ng_bt_1.setEnabled(true);
+//
+//
+//                }
+//            } else if(Config.gw_2.equalsIgnoreCase(oprno)){
+//                if(processMsgRet.equalsIgnoreCase("OK")) {
+//                    MesClient.work_status2 = 1;
+//                    MesClient.f_scan_data_bt_2.setEnabled(false);
+////                    MesClient.finish_ok_bt_2.setEnabled(true);
+////                    MesClient.finish_ng_bt_2.setEnabled(true);
+//
+//
+//                }
+//            }
+//        }catch (Exception e){
+//            e.printStackTrace();
+//        }
+    }
+
+    // 绑定
+    public static void bindRevice(String processMsgRet,String mes_msg){
+        try{
+            if(processMsgRet.equalsIgnoreCase("OK")) {
+
+            }else{
+
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    // 解绑
+    public static void unbindRevice(String processMsgRet,String mes_msg){
+        try{
+            if(processMsgRet.equalsIgnoreCase("OK")) {
+
+            }else{
+
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    // 上传质量
+    public static void updateResultRevice(String processMsgRet,String mes_msg){
+        try{
+            String oprno = ProtocolParam.getOprno(mes_msg).trim();
+            if(processMsgRet.equalsIgnoreCase("OK")) {
+                if (Config.gw_1.equalsIgnoreCase(oprno)) {
+                    MesClient.isSubmitting1 = false;
+                    MesClient.resetScanA();
+                    MesClient.setMenuState_1("结果提交成功,请扫下一件",0);
+                    // 自动提交成功后弹出工件码扫码框
+                    MesClient.scanBarcode1();
+                } else if(Config.gw_2.equalsIgnoreCase(oprno)){
+                    MesClient.isSubmitting2 = false;
+                    MesClient.resetScanB();
+                    MesClient.setMenuState_2("结果提交成功,请扫下一件",0);
+                    // 自动提交成功后弹出工件码扫码框
+                    MesClient.scanBarcode2();
+                }
+            }else{
+                if (Config.gw_1.equalsIgnoreCase(oprno)) {
+                    MesClient.isSubmitting1 = false;
+                    MesClient.setMenuState_1("结果提交失败,请重试",-1);
+                } else if(Config.gw_2.equalsIgnoreCase(oprno)){
+                    MesClient.isSubmitting2 = false;
+                    MesClient.setMenuState_2("结果提交失败,请重试",-1);
+                }
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    // 冷板校验响应处理
+    public static void validateColdPlateRevice(String processMsgRet,String mes_msg){
+        try{
+            String oprno = ProtocolParam.getOprno(mes_msg).trim();
+            // 从完整消息中提取结果码(2位)
+            String resultCode = ProtocolParam.getResult(mes_msg);
+            log.info("冷板校验结果码: " + resultCode + ", 工位: " + oprno);
+            
+            if (Config.gw_1.equalsIgnoreCase(oprno)) {
+                // 工位1的冷板校验
+                if(resultCode.equalsIgnoreCase("OK")) {
+                    // 校验通过,设置状态等待拉铆
+                    MesClient.coldPlateValidated_1 = true;
+                    MesClient.f_scan_data_bt_lb_1.setEnabled(false); // 禁用扫码按钮
+                    MesClient.btnUnbindLb_1.setEnabled(true); // 启用解绑按钮
+                    // 冷板校验通过后启动拉铆枪心跳
+                    MesClient.heartBeatFlag = true;
+                    log.info("工位A:冷板校验通过,启动拉铆枪心跳");
+                    MesClient.setMenuState_1("冷板校验通过,拉铆枪已启动", 0);
+                }else{
+                    // 校验失败,清空状态
+                    MesClient.coldPlateValidated_1 = false;
+                    MesClient.coldPlateCode_1 = "";
+                    MesClient.product_sn_lb_1.setText(""); // 清空冷板码显示
+                    String lmsg = ErrorMsg.getErrorMsg(resultCode, ProtocolParam.getLx(mes_msg));
+                    MesClient.setMenuState_1(lmsg, 1);
+                    // 校验失败后自动弹出扫码框重新扫描
+                    MesClient.scanBarcodeLb1();
+                }
+            } else if(Config.gw_2.equalsIgnoreCase(oprno)){
+                // 工位2的冷板校验
+                if(resultCode.equalsIgnoreCase("OK")) {
+                    // 校验通过,设置状态等待拉铆
+                    MesClient.coldPlateValidated_2 = true;
+                    MesClient.f_scan_data_bt_lb_2.setEnabled(false); // 禁用扫码按钮
+                    MesClient.btnUnbindLb_2.setEnabled(true); // 启用解绑按钮
+                    // 冷板校验通过后启动拉铆枪心跳
+                    MesClient.heartBeatFlag2 = true;
+                    log.info("工位B:冷板校验通过,启动拉铆枪心跳");
+                    MesClient.setMenuState_2("冷板校验通过,拉铆枪已启动", 0);
+                }else{
+                    // 校验失败,清空状态
+                    MesClient.coldPlateValidated_2 = false;
+                    MesClient.coldPlateCode_2 = "";
+                    MesClient.product_sn_lb_2.setText(""); // 清空冷板码显示
+                    String lmsg = ErrorMsg.getErrorMsg(resultCode, ProtocolParam.getLx(mes_msg));
+                    MesClient.setMenuState_2(lmsg, 1);
+                    // 校验失败后自动弹出扫码框重新扫描
+                    MesClient.scanBarcodeLb2();
+                }
+            }
+        }catch (Exception e){
+            log.error("冷板校验响应处理异常", e);
+        }
+    }
+}

+ 67 - 0
src/com/mes/ui/OprnoUtil.java

@@ -0,0 +1,67 @@
+package com.mes.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class OprnoUtil {
+    public static String[] xtoprnos = new String[]{
+            "OP040","OP050","OP060","OP070","OP080","OP090","OP100",
+            "OP110","OP120","OP130","OP140","OP150","OP170","OP190",
+            "OP200","OP210","OP220","OP230","OP240","OP250","OP260",
+            "OP270","OP280","OP290","OP300","OP310","OP320","OP340",
+            "OP350","OP360","OP370","OP370","OP380","OP390","OP400","OP410","OP420"
+    };
+    public static String[] xtoprnodes = new String[]{
+            "镭雕二维码","前边梁压铆,单部件拉铆","框架CMT焊接","人工补焊",
+            "焊道检查","焊道打磨","","框架气密","框架反面CNC加工+去毛刺","框架反面,吹铝屑+清洁",
+            "框架涂胶","液冷板安装+水冷板点焊","液冷板FSW","匙孔补焊+匙孔补焊打磨",
+            "总成正面CNC+去毛刺","总成正面,吹铝屑+清洁","总成正面装配","套筒涂胶","封堵片焊接",
+            "左右边梁封堵","半成品气密","VHB胶带粘贴+冷板泡棉粘贴","冷板背面涂胶",
+            "底护板清洗","安装底护板","成品气密","液冷板气密","总成检具检验","箱体封堵",
+            "总成清洁","粘贴云母片","CCD","终检","GP12正面","后梁底涂镭雕","GP12反面","包装"
+    };
+    public static String[] lboprnos = new String[]{
+
+    };
+    public static String[] lboprnodes = new String[]{
+
+    };
+    public static String getGwDes(String lineSn,String oprno){
+        String des = "";
+        oprno = formatOprno(oprno);
+        if(lineSn.equals("XT")){
+            int i = 0;
+            for (String gw:xtoprnos){
+                if(gw.equals(oprno)){
+                    des = xtoprnodes[i];
+                    break;
+                }
+                i++;
+            }
+        }else if(lineSn.equals("LB")){
+            int i = 0;
+            for (String gw:lboprnos){
+                if(gw.equals(oprno)){
+                    des = lboprnodes[i];
+                    break;
+                }
+                i++;
+            }
+        }
+        return des;
+    }
+
+    public static String formatOprno(String oprno){
+        List<String> lists = new ArrayList<>();
+
+        if(oprno.length() == 6){
+            String ysoprno = oprno.substring(0,5).trim();
+            if(!lists.contains(ysoprno)){
+                oprno = ysoprno;
+            }
+        }
+
+        return  oprno;
+    }
+}

+ 157 - 0
src/com/mes/ui/SettingDialog.java

@@ -0,0 +1,157 @@
+package com.mes.ui;
+
+import com.mes.util.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+/**
+ * 设置对话框,用于配置工件前缀等参数
+ */
+public class SettingDialog extends JDialog {
+    private static final Logger log = LoggerFactory.getLogger(SettingDialog.class);
+
+    private JTextField prefixField;
+    private JTextField coldplatePrefixField;
+    private boolean saved = false;
+
+    public SettingDialog(Frame owner) {
+        super(owner, "系统设置", true);
+        initUI();
+    }
+
+    private void initUI() {
+        setSize(500, 260);
+        setLocationRelativeTo(getOwner());
+        setResizable(false);
+
+        JPanel mainPanel = new JPanel();
+        mainPanel.setLayout(new BorderLayout(10, 10));
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+
+        // 中间面板 - 配置项
+        JPanel centerPanel = new JPanel(new GridLayout(2, 2, 10, 10));
+
+        JLabel prefixLabel = new JLabel("工件前缀(用逗号分隔):");
+        prefixLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        centerPanel.add(prefixLabel);
+
+        // 读取当前配置
+        String currentPrefix = String.join(",", Config.workpiece_prefix);
+        prefixField = new JTextField(currentPrefix);
+        prefixField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        centerPanel.add(prefixField);
+
+        JLabel coldplatePrefixLabel = new JLabel("冷板前缀(用逗号分隔):");
+        coldplatePrefixLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        centerPanel.add(coldplatePrefixLabel);
+
+        String currentColdplatePrefix = String.join(",", Config.coldplate_prefix);
+        coldplatePrefixField = new JTextField(currentColdplatePrefix);
+        coldplatePrefixField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        centerPanel.add(coldplatePrefixField);
+
+        mainPanel.add(centerPanel, BorderLayout.CENTER);
+
+        // 底部按钮面板
+        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10));
+
+        JButton saveButton = new JButton("保存");
+        saveButton.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        saveButton.addActionListener(e -> saveConfig());
+        buttonPanel.add(saveButton);
+
+        JButton cancelButton = new JButton("取消");
+        cancelButton.setFont(new Font("微软雅黑", Font.PLAIN, 16));
+        cancelButton.addActionListener(e -> dispose());
+        buttonPanel.add(cancelButton);
+
+        mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+
+        setContentPane(mainPanel);
+    }
+
+    private void saveConfig() {
+        String prefix = prefixField.getText().trim();
+        if (prefix.isEmpty()) {
+            JOptionPane.showMessageDialog(this,
+                "工件前缀不能为空",
+                "输入错误",
+                JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        String coldplatePrefix = coldplatePrefixField.getText().trim();
+        if (coldplatePrefix.isEmpty()) {
+            JOptionPane.showMessageDialog(this,
+                "冷板前缀不能为空",
+                "输入错误",
+                JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        // 验证格式
+        if (!checkPrefixFormat(prefix, "工件") || !checkPrefixFormat(coldplatePrefix, "冷板")) {
+            return;
+        }
+
+        try {
+            // 以当前生效的完整配置为基础(外部文件优先,否则取jar内置),避免丢失其他配置项
+            Properties prop = Config.loadRawProperties();
+
+            // 更新配置
+            prop.setProperty("mes.workpiece_prefix", prefix);
+            prop.setProperty("mes.coldplate_prefix", coldplatePrefix);
+
+            // 写入jar同级的外部配置文件
+            File configFile = Config.getExternalConfigFile();
+            File parent = configFile.getParentFile();
+            if (parent != null && !parent.exists() && !parent.mkdirs()) {
+                throw new IOException("无法创建配置目录: " + parent.getAbsolutePath());
+            }
+            try (OutputStreamWriter osw = new OutputStreamWriter(
+                    new FileOutputStream(configFile), StandardCharsets.UTF_8)) {
+                prop.store(osw, "Updated by SettingDialog");
+            }
+
+            saved = true;
+            JOptionPane.showMessageDialog(this,
+                "配置已保存到:\n" + configFile.getAbsolutePath() + "\n请重启程序使配置生效",
+                "保存成功",
+                JOptionPane.INFORMATION_MESSAGE);
+
+            log.info("前缀配置已更新: 工件={}, 冷板={}", prefix, coldplatePrefix);
+            dispose();
+
+        } catch (Exception ex) {
+            log.error("保存配置失败", ex);
+            JOptionPane.showMessageDialog(this,
+                "保存配置失败: " + ex.getMessage(),
+                "错误",
+                JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    // 校验逗号分隔的前缀格式,每段不能为空
+    private boolean checkPrefixFormat(String value, String name) {
+        for (String p : value.split(",")) {
+            if (p.trim().isEmpty()) {
+                JOptionPane.showMessageDialog(this,
+                    name + "前缀格式错误,请用逗号分隔有效前缀",
+                    "输入错误",
+                    JOptionPane.ERROR_MESSAGE);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isSaved() {
+        return saved;
+    }
+}

+ 48 - 0
src/com/mes/ui/TableCellEditorButton.java

@@ -0,0 +1,48 @@
+package com.mes.ui;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class TableCellEditorButton extends DefaultCellEditor {
+
+    public static final Logger log =  LoggerFactory.getLogger(TableCellEditorButton.class);
+    private JButton btn;
+    public TableCellEditorButton() {
+        super(new JTextField());
+        //设置点击一次就激活,否则默认好像是点击2次激活。
+        this.setClickCountToStart(1);
+        btn = new JButton("扫码");
+        btn.addActionListener(new ActionListener() {
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                log.info("按钮事件触发----");
+                int selectedRow = MesClient.table.getSelectedRow();//获得选中行的索引
+//                MesClient.rowData[selectedRow][1] = (new Date()).getTime();
+//                MesClient.table.repaint(); //重绘
+
+//                MesClient.scan_type = selectedRow + 4;
+                BindMaterialResp bindMaterialResp = new BindMaterialResp();
+                bindMaterialResp.setMaterialTitle(MesClient.rowData[selectedRow][0] + "");
+                bindMaterialResp.setBatchSn(MesClient.rowData[selectedRow][1] + "");
+                bindMaterialResp.setLastTimes(MesClient.rowData[selectedRow][2] + "");
+                bindMaterialResp.setCraft(MesClient.rowData[selectedRow][4] + "");
+                bindMaterialResp.setMaterialId(MesClient.rowData[selectedRow][5] + "");
+                bindMaterialResp.setType(MesClient.rowData[selectedRow][6] + "");
+
+                MesClient.scanBatchSn(bindMaterialResp);
+            }
+        });
+
+    }
+    @Override
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+
+        return btn;
+    }
+}

+ 14 - 0
src/com/mes/ui/TableCellRendererButton.java

@@ -0,0 +1,14 @@
+package com.mes.ui;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+
+public class TableCellRendererButton implements TableCellRenderer {
+    @Override
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+                                                   int row, int column) {
+        JButton button = new JButton("扫码");
+        return button;
+    }
+}

+ 17 - 0
src/com/mes/util/Base64Utils.java

@@ -0,0 +1,17 @@
+package com.mes.util;
+
+import com.mes.ui.MesClient;
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Base64Utils {
+    public static final Logger log =  LoggerFactory.getLogger(Base64Utils.class);
+	public static String getBase64(String str) {
+		// 待编码的二进制数据
+        byte[] binaryData = str.getBytes();
+        // 使用Base64.encodeBase64String进行编码
+        String encodedString = Base64.encodeBase64String(binaryData);
+        return encodedString;
+	}
+}

+ 97 - 0
src/com/mes/util/Config.java

@@ -0,0 +1,97 @@
+package com.mes.util;
+
+import com.mes.ui.OprnoUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+
+public class Config {
+    private static final Logger log = LoggerFactory.getLogger(Config.class);
+
+    public static final String line_sn;
+    public static final String gw_1;
+    public static final String gw_des_1;
+    public static final String gw_2;
+    public static final String gw_des_2;
+
+    public static final String server_ip;
+    public static final Integer tcp_port;
+    public static final Integer heart_beat_cycle;
+    public static final Integer heart_icon_cycle = 1;
+    public static final Boolean plc_timeout_enabled;
+    public static final String[] workpiece_prefix;
+    public static final String[] coldplate_prefix;
+
+
+
+    private Config() {}
+
+    /**
+     * 外部配置文件位置:jar(或class输出目录)同级的 config/config.properties
+     * 打包运行时可直接修改此文件,无需重新打包
+     */
+    public static File getExternalConfigFile() {
+        try {
+            File codeSource = new File(Config.class.getProtectionDomain()
+                    .getCodeSource().getLocation().toURI());
+            File baseDir = codeSource.isFile() ? codeSource.getParentFile() : codeSource;
+            return new File(baseDir, "config/config.properties");
+        } catch (Exception e) {
+            // 取不到运行位置时退回到工作目录
+            return new File("config/config.properties");
+        }
+    }
+
+    /**
+     * 读取当前生效的完整配置:外部文件优先,缺失时用jar内置配置
+     */
+    public static Properties loadRawProperties() {
+        Properties prop = new Properties();
+        File external = getExternalConfigFile();
+        if (external.isFile()) {
+            try (Reader r = new InputStreamReader(new FileInputStream(external), StandardCharsets.UTF_8)) {
+                prop.load(r);
+                return prop;
+            } catch (Exception e) {
+                log.error("读取外部配置失败,改用内置配置: " + external.getAbsolutePath(), e);
+            }
+        }
+        try (InputStream is = Config.class.getClassLoader().getResourceAsStream("config/config.properties")) {
+            if (is == null) {
+                log.error("内置配置文件 config/config.properties 不存在");
+                return prop;
+            }
+            prop.load(new InputStreamReader(is, StandardCharsets.UTF_8));
+        } catch (Exception e) {
+            log.error("读取内置配置失败", e);
+        }
+        return prop;
+    }
+
+    static {
+        Properties prop = loadRawProperties();
+
+        line_sn = prop.getProperty("mes.line_sn");
+        gw_1 = prop.getProperty("mes.gw_1");
+        gw_des_1 = OprnoUtil.getGwDes(line_sn, gw_1);
+        gw_2 = prop.getProperty("mes.gw_2");
+        gw_des_2 = OprnoUtil.getGwDes(line_sn, gw_2);
+        server_ip = prop.getProperty("mes.server_ip");
+        tcp_port = Integer.parseInt(prop.getProperty("mes.tcp_port"));
+        heart_beat_cycle = Integer.parseInt(prop.getProperty("mes.heart_beat_cycle"));
+        plc_timeout_enabled = Boolean.parseBoolean(prop.getProperty("mes.plc_timeout_enabled"));
+        workpiece_prefix = prop.getProperty("mes.workpiece_prefix", "KA99").split(",");
+        coldplate_prefix = prop.getProperty("mes.coldplate_prefix", "KB004").split(",");
+
+
+    }
+
+}

+ 93 - 0
src/com/mes/util/DataUtils.java

@@ -0,0 +1,93 @@
+package com.mes.util;
+
+public class DataUtils {
+    // 将16进制字符串转化为byte数组
+    public static byte[] hexStringToByteArray(String s) {
+        s = s.replace(" ", "");
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    public static int getBit(int data, int index) {
+        return (data >> index) & 1;
+    }
+
+    public static int bytesToInt(byte[] data) {
+        int a = 0;
+        for (int i = 0; i < data.length; i++) {
+            a = a | (data[i] & 0xFF) << (8 * i);
+        }
+        return a;
+    }
+
+    // 将一个byte值转换成16进制形式的字符串
+    public static String byteToHexString(byte b) {
+        // 得到高4位和低4位的数字
+        int high = (b & 0xF0) >>> 4;  // 高4位
+        int low = b & 0x0F;           // 低4位
+
+        // 将高4位和低4位转换为16进制字符
+        return hexChar(high) + String.valueOf(hexChar(low));
+    }
+
+    public static String CRC16(String input) {
+        byte[] bytes = hexStringToByteArray(input);
+
+        // 初始化CRC寄存器
+        int crc = 0xFFFF;
+
+        for (byte b : bytes) {
+            // 更新CRC寄存器
+            crc ^= (b & 0xFF);
+            for (int i = 0; i < 8; i++) {
+                if ((crc & 0x0001) != 0) {
+                    crc >>= 1;
+                    crc ^= 0xA001;
+                } else {
+                    crc >>= 1;
+                }
+            }
+        }
+
+        // 获取CRC16结果的高位和低位
+        byte high = (byte) ((crc >> 8) & 0xFF);
+        byte low = (byte) (crc & 0xFF);
+
+        // 将CRC16结果添加到原始字符串后面
+        return input + " " + byteToHexString(low) + " " + byteToHexString(high);
+    }
+
+    // 把int数据转化为Word,一个Word有两字节, 且低位在前、高位在后
+    public static String intToWord(int n) {
+        byte b1 = (byte) (n & 0xFF);
+        byte b2 = (byte) ((n >> 8) & 0xFF);
+        String s1 = byteToHexString(b1);
+        String s2 = byteToHexString(b2);
+        return s1 + " " + s2;
+    }
+
+
+
+
+
+
+
+
+    // 辅助方法:根据数字返回对应的16进制字符
+    private static char hexChar(int n) {
+        if (n >= 0 && n <= 9) {
+            return (char) ('0' + n);
+        } else if (n >= 10 && n <= 15) {
+            return (char) ('A' + (n - 10));
+        }
+        throw new IllegalArgumentException("转换为十六进制字符的值无效: " + n);
+    }
+
+
+
+}

+ 34 - 0
src/com/mes/util/DateLocalUtils.java

@@ -0,0 +1,34 @@
+package com.mes.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class DateLocalUtils {
+	public static SimpleDateFormat DATA_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//璁剧疆鏃ユ湡鏍煎紡
+	public static SimpleDateFormat DATA_FORMAT1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+	
+	
+	public static String getCurrentTime() {
+		String currentTime = DATA_FORMAT.format(new Date());
+		return currentTime;
+	}
+	
+	public static String getCurrentTime1() {
+		String currentTime = DATA_FORMAT1.format(new Date());
+		return currentTime;
+	}
+	
+	//获取当前时间,年月日
+	public static SimpleDateFormat DATA_FORMAT2 = new SimpleDateFormat("yyyy-MM-dd");
+	public static String getCurrentDate() {
+		String currentTime = DATA_FORMAT2.format(new Date());
+		return currentTime;
+	}
+	
+	//获取当前时间,时分秒
+	public static SimpleDateFormat DATA_FORMAT3 = new SimpleDateFormat("HH:mm:ss");
+	public static String getCurrentTimeHMS() {
+		String currentTime = DATA_FORMAT3.format(new Date());
+		return currentTime;
+	}
+}

+ 47 - 0
src/com/mes/util/ErrorMsg.java

@@ -0,0 +1,47 @@
+package com.mes.util;
+
+public class ErrorMsg {
+    public static String getErrorMsg(String processMsgRet,String lx){
+        String lmsg = "该工件本工位不可加工";
+        try{
+            if(processMsgRet.equalsIgnoreCase("OK")||processMsgRet.equalsIgnoreCase("NG")) {
+                lmsg = "该工件本工位已加工,结果:"+processMsgRet;
+            }else if(processMsgRet.equalsIgnoreCase("NE")) {
+                lmsg = "该工件未录入系统";
+            }else if(processMsgRet.equalsIgnoreCase("NN")) {
+                lmsg = "该工件跳过该工位";
+            }else if(processMsgRet.equalsIgnoreCase("QN")) {
+                lmsg = "该工件OP"+ lx+"0加工NG";
+            }else if(processMsgRet.equalsIgnoreCase("QD")) {
+                lmsg = "该工件OP"+ lx+"0未加工";
+            }else if(processMsgRet.equalsIgnoreCase("NF")) {
+                lmsg = "该工件已合格下线";
+            }else if(processMsgRet.equalsIgnoreCase("NR")) {
+                lmsg = "该工件离线返修中";
+            }else if(processMsgRet.equalsIgnoreCase("NB")) {
+                lmsg = "该工件已报废";
+            }else if(processMsgRet.equalsIgnoreCase("ND")) {
+                lmsg = "该工件NG待处理";
+            }else if(processMsgRet.equalsIgnoreCase("FN")) {
+                lmsg = "首件检查工件不合格停机";
+            }else if(processMsgRet.equalsIgnoreCase("GN")) {
+                lmsg = "更换配件首件检查不合格停机";
+            }else if(processMsgRet.equalsIgnoreCase("DJ")) {
+                lmsg = "未进行开班点检";
+            }else if(processMsgRet.equalsIgnoreCase("BM")) {
+                lmsg = "未绑定物料";
+            }else if(processMsgRet.equalsIgnoreCase("PL")) {
+                lmsg = "配件寿命不足";
+            }else if(processMsgRet.equalsIgnoreCase("QM")) {
+                lmsg = "两次气密必须间隔15分钟";
+            }else if(processMsgRet.equalsIgnoreCase("GS")) {
+                lmsg = "工件码格式不正确";
+            }else if(processMsgRet.equalsIgnoreCase("CF")) {
+                lmsg = "工件码重复";
+            }else if(processMsgRet.equalsIgnoreCase("MX")) {
+                lmsg = "超出最大测试次数,请返修";
+            }
+        }catch (Exception e){ }
+        return lmsg;
+    }
+}

+ 0 - 0
src/com/mes/util/HttpUtils.java


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.