Ver Fonte

兼容安徽英太气密仪

hou há 1 semana atrás
pai
commit
e5c4064faf

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

@@ -78,21 +78,6 @@ public class ProtocolParam {
         // 5位结果:xxxCC;4位 RSXX 紧跟 DA 时 five 末位是 D,code 不会命中
         if(code.equals("QN") || code.equals("QD") || code.equals("UD")
                 || code.equals("NE") || code.equals("NN") || code.equals("OK")
-                || code.equals("NG") || code.equals("MT")){
-            return five;
-        }
-        return null;
-    }
-
-    private static String getFiveCharResultBlock(String msg){
-        if(msg == null || msg.length() < 75){
-            return null;
-        }
-        String five = msg.substring(70, 75);
-        String code = five.substring(3, 5);
-        // 5位结果:xxxCC;4位 RSXX 紧跟 DA 时 five 末位是 D,code 不会命中
-        if(code.equals("QN") || code.equals("QD") || code.equals("UD")
-                || code.equals("NE") || code.equals("NN") || code.equals("OK")
                 || code.equals("NG") || code.equals("MT") || code.equals("BM")){
             return five;
         }

+ 129 - 131
src/com/mes/ui/MesClient.java

@@ -5,6 +5,7 @@ import com.mes.component.MesRadio;
 import com.mes.component.MesWebView;
 import com.mes.netty.NettyClient;
 import com.mes.util.*;
+import com.mes.util.CommonQmDevice;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -129,7 +130,12 @@ public class MesClient extends JFrame {
 
     public static String tjFlagTextErr = "结果上传MES失败,请重试";
 
+    public static CommonQmDevice commonQmDevice;
     public static SerialPortUtils serialPortUtils;
+    public static Integer mesTjFlag = 0;
+    public static Integer workstop = 0;
+    public static String lastQmPressure = "";
+    public static String lastQmLeak = "";
     public static JTextField printSn2;
     public static JTextField printTitle2;
     public static JTextField printValue2;
@@ -167,7 +173,8 @@ public class MesClient extends JFrame {
 
                     getTask();
 
-                    serialPortUtils=new SerialPortUtils(configParam.getSerialPort(),getEffectiveOprno(),configParam.getOprnoTitle(),configParam.getDevice());
+                    commonQmDevice = new CommonQmDevice(configParam);
+                    serialPortUtils = commonQmDevice.serialPortUtils;
 
                //    startDateTimer();
 
@@ -346,6 +353,10 @@ public class MesClient extends JFrame {
         product_sn.setEditable(true);
         tjFlag = 0;
         mesStartFlag = 0;
+        mesTjFlag = 0;
+        workstop = 0;
+        lastQmPressure = "";
+        lastQmLeak = "";
         curTaskName.setText("");
 
 //        f_scan_data_bt_1.setIcon(new ImageIcon(MesClient.class.getResource("/bg/scan_barcode.png")));
@@ -665,7 +676,7 @@ public class MesClient extends JFrame {
 
     public static void scanBarcode() {
         if (work_status == 1) {
-            MesClient.serialPortUtils.start();
+            startQmDevice();
             return;
         }
     }
@@ -727,7 +738,7 @@ public class MesClient extends JFrame {
 //        }
 
         if(checkProductSn(scanBarcode.trim())){
-            serialPortUtils.changeProgram(); // 切换程序
+            changeQmProgram(); // 切换程序
             if(MesClient.mes_enable){
                 getUser();
                 // 查询工件质量
@@ -737,15 +748,7 @@ public class MesClient extends JFrame {
                     return;
                 }
             }else{
-                work_status = 1;
-                MesClient.tjFlag = 1;
-                MesClient.setMenuStatus("扫码成功,等待测试",0);
-                product_sn.setEditable(false);
-                if(MesClient.configParam.getStartMode().equals("自动启动")){
-                    MesClient.serialPortUtils.start();
-                }
-                MesClient.result.setText("等待结果");
-                MesClient.result.setForeground(Color.GRAY);
+                beginTestAfterScan();
             }
         }
 //        f_scan_data_bt_1.setIcon(null);
@@ -753,6 +756,99 @@ public class MesClient extends JFrame {
 
     }
 
+    /** 扫码/质量检查通过后进入测试状态 */
+    public static void beginTestAfterScan() {
+        work_status = 1;
+        MesClient.tjFlag = 1;
+        MesClient.mesStartFlag = 0;
+        MesClient.mesTjFlag = 0;
+        MesClient.workstop = 0;
+        MesClient.lastQmPressure = "";
+        MesClient.lastQmLeak = "";
+        MesClient.check_quality_result = true;
+        MesClient.setMenuStatus("该工件可以测试,等待测试", 0);
+        product_sn.setEditable(false);
+        if (MesClient.configParam.getStartMode().equals("自动启动")) {
+            startQmDevice();
+        }
+        MesClient.result.setText("等待结果");
+        MesClient.result.setForeground(Color.GRAY);
+    }
+
+    public static void startQmDevice() {
+        if (commonQmDevice != null) {
+            commonQmDevice.start();
+        } else if (serialPortUtils != null) {
+            serialPortUtils.start();
+        } else {
+            MesClient.setMenuStatus("设备未初始化", -1);
+        }
+    }
+
+    public static void changeQmProgram() {
+        if (commonQmDevice != null) {
+            commonQmDevice.changeProgram();
+        } else if (serialPortUtils != null) {
+            serialPortUtils.changeProgram();
+        }
+    }
+
+    /**
+     * 本地存库后提交MES,成功则复位扫码。
+     */
+    public static boolean afterTestSaveAndUpload(TestParam testParam) {
+        try {
+            String testDate = testParam.getCreateTime();
+            if (testDate == null || testDate.isEmpty()) {
+                testDate = DateLocalUtils.getCurrentTime();
+                testParam.setCreateTime(testDate);
+            }
+            if (testParam.getOprno() == null || testParam.getOprno().isEmpty()) {
+                testParam.setOprno(getEffectiveOprno());
+            }
+            if (testParam.getLineSn() == null || testParam.getLineSn().isEmpty()) {
+                testParam.setLineSn(getEffectiveLineSn());
+            }
+            JdbcUtils.insertTestRecord(testParam);
+
+            if (mes_enable) {
+                JSONObject retObj = DataUtil.qmResultData(testParam);
+                if (retObj != null && retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
+                    MesClient.resetScanA();
+                    MesClient.setMenuStatus("测试结果上传成功,请扫下一件", 0);
+                    try { JdbcUtils.getTestData(); } catch (Exception ignore) {}
+                    if (MesClient.configParam.getPrintLabel().equals("是") && "OK".equals(testParam.getResult())) {
+                        PrintUtil.printLabel(testParam.getSn(),
+                                (testParam.getParam1() != null ? testParam.getParam1() : "") + (testParam.getParam2() != null ? testParam.getParam2() : ""),
+                                (testParam.getParam3() != null ? testParam.getParam3() : "") + (testParam.getParam4() != null ? testParam.getParam4() : ""),
+                                testDate);
+                    }
+                    return true;
+                } else {
+                    MesClient.mesStartFlag = 0;
+                    MesClient.mesTjFlag = 0;
+                    MesClient.workstop = 0;
+                    MesClient.setMenuStatus("测试结果上传失败,请重试", -1);
+                    return false;
+                }
+            } else {
+                MesClient.resetScanA();
+                try { JdbcUtils.getTestData(); } catch (Exception ignore) {}
+                if (MesClient.configParam.getPrintLabel().equals("是") && "OK".equals(testParam.getResult())) {
+                    PrintUtil.printLabel(testParam.getSn(),
+                            (testParam.getParam1() != null ? testParam.getParam1() : "") + (testParam.getParam2() != null ? testParam.getParam2() : ""),
+                            (testParam.getParam3() != null ? testParam.getParam3() : "") + (testParam.getParam4() != null ? testParam.getParam4() : ""),
+                            testDate);
+                }
+                return true;
+            }
+        } catch (Exception e) {
+            log.error("保存上传测试结果失败", e);
+            MesClient.setMenuStatus("测试结果保存失败,请重试", -1);
+            return false;
+        }
+    }
+
     public static void scanBarcodeOld() {
         if(work_status == 1){
 //            MesClient.serialPortUtils.sendData("START");
@@ -812,7 +908,7 @@ public class MesClient extends JFrame {
             MesClient.setMenuStatus("扫码成功,等待测试",0);
 
             if(MesClient.configParam.getStartMode().equals("自动启动")){
-                MesClient.serialPortUtils.start();
+                startQmDevice();
             }
             f_scan_data_bt_1.setIcon(null);
             f_scan_data_bt_1.setText("启动");
@@ -1333,7 +1429,7 @@ public class MesClient extends JFrame {
         startMode.setBounds(140, 71, 200, 30);
         indexPanelD.add(startMode);
         
-        JLabel lblNewLabel_3_1_1 = new JLabel("串口号");
+        JLabel lblNewLabel_3_1_1 = new JLabel("串口号/IP");
         lblNewLabel_3_1_1.setHorizontalAlignment(SwingConstants.RIGHT);
         lblNewLabel_3_1_1.setFont(new Font("微软雅黑", Font.PLAIN, 16));
         lblNewLabel_3_1_1.setBounds(10, 123, 120, 30);
@@ -1345,7 +1441,7 @@ public class MesClient extends JFrame {
         serialPort.setBounds(140, 123, 200, 30);
         indexPanelD.add(serialPort);
         
-        JLabel lblNewLabel_3_1_1_1 = new JLabel("波特率");
+        JLabel lblNewLabel_3_1_1_1 = new JLabel("波特率/端口");
         lblNewLabel_3_1_1_1.setHorizontalAlignment(SwingConstants.RIGHT);
         lblNewLabel_3_1_1_1.setFont(new Font("微软雅黑", Font.PLAIN, 16));
         lblNewLabel_3_1_1_1.setBounds(10, 176, 120, 30);
@@ -1566,8 +1662,16 @@ public class MesClient extends JFrame {
 //                    }
 
                     formatConfigData();
-                    serialPortUtils.closePort();
-                    serialPortUtils = new SerialPortUtils(configParam.getSerialPort(),getEffectiveOprno(),configParam.getOprnoTitle(),configParam.getDevice());
+                    if (commonQmDevice != null) {
+                        commonQmDevice.reconnect();
+                        serialPortUtils = commonQmDevice.serialPortUtils;
+                    } else {
+                        if (serialPortUtils != null) {
+                            serialPortUtils.closePort();
+                        }
+                        commonQmDevice = new CommonQmDevice(configParam);
+                        serialPortUtils = commonQmDevice.serialPortUtils;
+                    }
                 }
 
             }
@@ -1622,7 +1726,7 @@ public class MesClient extends JFrame {
         indexPanelD.add(lblNewLabel_3_1_1_1_1_1_1_1_1_1_1_1_1_1_1_2);
         
         device = new JComboBox();
-        device.setModel(new DefaultComboBoxModel(new String[] {"innomatec", "ateq"}));
+        device.setModel(new DefaultComboBoxModel(new String[] {"innomatec", "ateq", "INNTAI"}));
         device.setFont(new Font("微软雅黑", Font.PLAIN, 16));
 //        device.setBounds(536, 445, 200, 30);
 //        device.setBounds(140, 390, 200, 30);
@@ -2019,113 +2123,6 @@ public class MesClient extends JFrame {
         MesClient.status_menu.setText(msg);
     }
 
-    public static void getMaterailData(){
-        try{
-            JSONObject retObj = DataUtil.getBindMaterail();
-            if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                List<BindMaterialResp> arrs = retObj.getList("data",BindMaterialResp.class);
-                int i = 0;
-                bindRowData = new Object[arrs.size()][7];
-                for (BindMaterialResp bindMaterialResp:arrs){
-                    bindRowData[i][0] = bindMaterialResp.getMaterialTitle();
-                    bindRowData[i][1] = bindMaterialResp.getBatchSn();
-                    bindRowData[i][2] = bindMaterialResp.getLastTimes();
-                    bindRowData[i][3] = "";
-                    bindRowData[i][4] = bindMaterialResp.getCraft();
-                    bindRowData[i][5] = bindMaterialResp.getMaterialId();
-                    bindRowData[i][6] = bindMaterialResp.getType();
-                    i++;
-                }
-                bindBatchPanel();
-            }
-        }catch (Exception e){
-            log.info(e.getMessage());
-        }
-    }
-
-    public static void updateMaterailData(){
-        try{
-            if(bindTable == null || bindRowData == null){
-                return;
-            }
-            JSONObject retObj = DataUtil.getBindMaterail();
-            if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                List<BindMaterialResp> arrs = retObj.getList("data",BindMaterialResp.class);
-
-                int i = 0;
-                for (BindMaterialResp bindMaterialResp:arrs){
-                    if(i >= bindRowData.length){
-                        break;
-                    }
-                    bindRowData[i][0] = bindMaterialResp.getMaterialTitle();
-                    bindRowData[i][1] = bindMaterialResp.getBatchSn();
-                    bindRowData[i][2] = bindMaterialResp.getLastTimes();
-                    bindRowData[i][3] = "";
-                    bindRowData[i][4] = bindMaterialResp.getCraft();
-                    bindRowData[i][5] = bindMaterialResp.getMaterialId();
-                    bindRowData[i][6] = bindMaterialResp.getType();
-                    i++;
-                }
-
-                MesClient.bindTable.repaint();
-            }
-        }catch (Exception e){
-            log.info(e.getMessage());
-        }
-    }
-
-    // 绑定物料批次码
-    public static void scanBatchSn(BindMaterialResp bindMaterialResp) {
-        String scanBarcodeTitle = "请扫物料:"+bindMaterialResp.getMaterialTitle();
-        String scanBarcode = JOptionPane.showInputDialog(null, scanBarcodeTitle);
-        if(scanBarcode!=null&&!scanBarcode.equalsIgnoreCase("")) {
-
-            JSONObject retObj = DataUtil.saveBindMaterail(scanBarcode,bindMaterialResp.getCraft(),bindMaterialResp.getMaterialId(),bindMaterialResp.getType());
-            if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
-                MesClient.setMenuStatus("扫物料:"+bindMaterialResp.getMaterialTitle()+"成功",0);
-                updateMaterailData();
-            }else{
-                if(retObj == null || retObj.get("result")==null){
-                    MesClient.setMenuStatus("请求失败,请重试",-1);
-                }else{
-                    if(retObj.get("result").toString().equalsIgnoreCase("false")){
-                        MesClient.setMenuStatus(retObj.getString("message"),-1);
-                    }
-                }
-            }
-        }
-    }
-
-    public static void bindBatchPanel(){
-        JPanel indexPanelBB = new JPanel();
-        JPanel panel = new JPanel();
-        panel.setBounds(0, 0, 990, 550);
-        indexPanelBB.add(panel);
-
-        panel.setLayout(new GridLayout(0, 1, 0, 0));
-        bindTable = new JTable(bindRowData, bindColumnNames){
-            public boolean isCellEditable(int row, int column) {
-                if(column == 3){
-                    return true;
-                }
-                return false;
-            }
-        };
-        bindTable.setRowHeight(40);
-        bindTable.setEnabled(true);
-        bindTable.setFont(new Font("微软雅黑", Font.PLAIN, 14));
-
-        bindTable.getColumnModel().getColumn(3).setCellRenderer(new TableCellRendererButton());
-        bindTable.getColumnModel().getColumn(3).setCellEditor(new TableCellEditorButton());
-
-        JScrollPane scrollPane = new JScrollPane(bindTable);
-        panel.add(scrollPane);
-
-        JScrollPane indexScrollPaneB = new JScrollPane(indexPanelBB);
-        indexPanelBB.setLayout(null);
-        tabbedPane.addTab("绑定物料", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), indexScrollPaneB, null);
-    }
-
     public static void getTask(){
         taskParamList = JdbcUtils.getTask();
         System.out.println("taskParamList:"+taskParamList.size());
@@ -2268,7 +2265,7 @@ public class MesClient extends JFrame {
 
     public static void updateMaterailData(){
         try{
-            if(materialRowData == null){
+            if(materialTable == null || materialRowData == null){
                 return;
             }
             JSONObject retObj = DataUtil.getBindMaterail();
@@ -2280,6 +2277,9 @@ public class MesClient extends JFrame {
 
                 int i = 0;
                 for (BindMaterialResp bindMaterialResp:arrs){
+                    if(i >= materialRowData.length){
+                        break;
+                    }
                     materialRowData[i][0] = bindMaterialResp.getMaterialTitle();
                     materialRowData[i][1] = bindMaterialResp.getBatchSn();
                     materialRowData[i][2] = bindMaterialResp.getLastTimes();
@@ -2290,9 +2290,7 @@ public class MesClient extends JFrame {
                     i++;
                 }
 
-                if(materialTable != null){
-                    materialTable.repaint();
-                }
+                materialTable.repaint();
             }
         }catch (Exception e){
             log.info(e.getMessage());
@@ -2305,11 +2303,11 @@ public class MesClient extends JFrame {
         if(scanBarcode!=null&&!scanBarcode.equalsIgnoreCase("")) {
 
             JSONObject retObj = DataUtil.saveBindMaterail(scanBarcode,bindMaterialResp.getCraft(),bindMaterialResp.getMaterialId(),bindMaterialResp.getType());
-            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+            if(retObj != null && retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
                 MesClient.setMenuStatus("扫物料:"+bindMaterialResp.getMaterialTitle()+"成功",0);
                 updateMaterailData();
             }else{
-                if(retObj.get("result")==null){
+                if(retObj == null || retObj.get("result")==null){
                     MesClient.setMenuStatus("请求失败,请重试",-1);
                 }else{
                     if(retObj.get("result").toString().equalsIgnoreCase("false")){

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

@@ -13,16 +13,7 @@ public class MesRevice {
     public static void checkQualityRevice(String processMsgRet,String mes_msg){
         try{
             if(processMsgRet.equalsIgnoreCase("UD")) {
-                MesClient.check_quality_result = true;//质量合格,可以绑定加工
-                MesClient.work_status = 1;
-                MesClient.tjFlag = 1;
-                MesClient.setMenuStatus("该工件可以测试,等待测试",0);
-                MesClient.product_sn.setEditable(false);
-                if(MesClient.configParam.getStartMode().equals("自动启动")){
-                    MesClient.serialPortUtils.start();
-                }
-                MesClient.result.setText("等待结果");
-                MesClient.result.setForeground(Color.GRAY);
+                MesClient.beginTestAfterScan();
             }else {
                 MesClient.check_quality_result = false;
                 String lmsg = ErrorMsg.getErrorMsg(processMsgRet, ProtocolParam.getLx(mes_msg));

+ 157 - 0
src/com/mes/util/CommonQmDevice.java

@@ -0,0 +1,157 @@
+package com.mes.util;
+
+import com.mes.ui.ConfigParam;
+import com.mes.ui.MesClient;
+import com.mes.util.ytnetty.YtNettyClient;
+import com.mes.util.ytnetty.YtUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * 气密仪统一入口:innomatec/ateq 走串口,INNTAI(中皖英泰)走网口 TCP。
+ */
+public class CommonQmDevice {
+    public static final Logger log = LoggerFactory.getLogger(CommonQmDevice.class);
+    public String device;
+    public SerialPortUtils serialPortUtils;
+    public YtNettyClient ytClient;
+    public boolean isOnline;
+    ConfigParam configParam;
+    public Timer workTimer;
+
+    public CommonQmDevice(ConfigParam configParam) {
+        this.configParam = configParam;
+        this.device = configParam.getDevice();
+        connect();
+    }
+
+    public static boolean isInntai(String device) {
+        if (device == null) {
+            return false;
+        }
+        return "INNTAI".equalsIgnoreCase(device.trim()) || "中皖英泰".equals(device.trim());
+    }
+
+    public boolean isInntai() {
+        return isInntai(device);
+    }
+
+    private void connect() {
+        this.device = configParam.getDevice();
+        WorkTimer.stop();
+        if (isInntai()) {
+            try {
+                if (serialPortUtils != null) {
+                    serialPortUtils.closePort();
+                    serialPortUtils = null;
+                }
+                MesClient.serialPortUtils = null;
+                String ip = configParam.getSerialPort();
+                String port = configParam.getBaudRate();
+                ytClient = new YtNettyClient();
+                ytClient.run(ip, Integer.parseInt(port));
+                workTimerTask();
+                log.info("INNTAI气密仪TCP连接 {}:{}", ip, port);
+            } catch (Exception e) {
+                log.error("INNTAI气密仪连接失败", e);
+            }
+        } else {
+            serialPortUtils = new SerialPortUtils(configParam.getSerialPort(),
+                    MesClient.getEffectiveOprno(),
+                    configParam.getOprnoTitle(),
+                    configParam.getDevice());
+            MesClient.serialPortUtils = serialPortUtils;
+        }
+    }
+
+    public boolean isOnline() {
+        isOnline = false;
+        if (isInntai()) {
+            isOnline = YtNettyClient.getTcpOnline() == 1;
+        } else if (serialPortUtils != null) {
+            isOnline = serialPortUtils.isOpen;
+        }
+        return isOnline;
+    }
+
+    public void reconnect() {
+        this.device = configParam.getDevice();
+        if (workTimer != null) {
+            workTimer.cancel();
+            workTimer = null;
+        }
+        connect();
+    }
+
+    public void workTimerTask() {
+        if (workTimer != null) {
+            workTimer.cancel();
+        }
+        workTimer = new Timer();
+        workTimer.schedule(new TimerTask() {
+            public void run() {
+                try {
+                    if (MesClient.work_status == 1 && YtNettyClient.future != null
+                            && YtNettyClient.future.channel() != null) {
+                        YtNettyClient.future.channel().writeAndFlush(YtUtil.getSendByteBuf(YtUtil.getCurDataCommon()));
+                    }
+                } catch (Exception e) {
+                    log.error("INNTAI实时查询失败", e);
+                }
+            }
+        }, 500, 500);
+    }
+
+    public boolean start() {
+        try {
+            if (isInntai()) {
+                if (YtNettyClient.future == null || YtNettyClient.future.channel() == null
+                        || YtNettyClient.getTcpOnline() != 1) {
+                    MesClient.setMenuStatus("INNTAI气密仪未连接,请检查IP/端口后重试", 1);
+                    return false;
+                }
+                log.info("INNTAI点击启动");
+                YtNettyClient.future.channel().writeAndFlush(YtUtil.getSendByteBuf(YtUtil.getStartCommon()));
+            } else {
+                if (serialPortUtils == null) {
+                    MesClient.setMenuStatus("成品气密仪连接失败,请检查设备后重试", 1);
+                    return false;
+                }
+                serialPortUtils.start();
+            }
+        } catch (Exception e) {
+            log.error("气密仪启动失败", e);
+            return false;
+        }
+        return true;
+    }
+
+    public boolean changeProgram() {
+        try {
+            if (isInntai()) {
+                if (YtNettyClient.future == null || YtNettyClient.future.channel() == null
+                        || YtNettyClient.getTcpOnline() != 1) {
+                    MesClient.setMenuStatus("INNTAI气密仪未连接,请检查IP/端口后重试", 1);
+                    return false;
+                }
+                String programNo = MesClient.configParam.getProgramNo();
+                log.info("INNTAI切换程序 {}", programNo);
+                YtNettyClient.future.channel().writeAndFlush(
+                        YtUtil.getSendByteBuf(YtUtil.getSelectProgramCommon(programNo)));
+            } else {
+                if (serialPortUtils == null) {
+                    MesClient.setMenuStatus("成品气密仪连接失败,请检查设备后重试", 1);
+                    return false;
+                }
+                serialPortUtils.changeProgram();
+            }
+        } catch (Exception e) {
+            log.error("气密仪切换程序失败", e);
+            return false;
+        }
+        return true;
+    }
+}

+ 63 - 0
src/com/mes/util/ytnetty/QmCurUtil.java

@@ -0,0 +1,63 @@
+package com.mes.util.ytnetty;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QmCurUtil {
+    public static final Logger log =  LoggerFactory.getLogger(QmCurUtil.class);
+
+    public final String step; // 0=待机 4=充气 1=排气 65535=结束
+    public final String temperature; // 温度等扩展字段
+    public final String pressureValue;                // 压力值
+    public final String leakValue;                    // 泄漏值
+
+    public QmCurUtil(String msg) {
+        // 新协议:RS,65535,0,0,4.7,0.0,0.0,0.0,0.0, (整包 hex 实为 ASCII CSV)
+        String ascii = new String(YtUtil.hexStringToByteArray(msg));
+        ascii = ascii.replaceAll("RS", "");
+        if (ascii.indexOf(',') >= 0) {
+            String[] parts = ascii.split(",", -1);
+            this.step = part(parts, 0);
+            this.pressureValue = part(parts, 1);
+            this.leakValue = part(parts, 2);
+            this.temperature = part(parts, 3);
+            return;
+        }
+        // 旧协议:定长二进制字段
+        this.step = new String(YtUtil.hexStringToByteArray(msg.substring(4, 6)));
+        this.temperature = new String(YtUtil.hexStringToByteArray(msg.substring(36, 42)));
+        this.leakValue = new String(YtUtil.hexStringToByteArray(msg.substring(28, 34)));
+        this.pressureValue = new String(YtUtil.hexStringToByteArray(msg.substring(20, 26)));
+    }
+
+    private static String part(String[] parts, int index) {
+        if (parts == null || index < 0 || index >= parts.length) {
+            return "";
+        }
+        return parts[index] == null ? "" : parts[index].trim();
+    }
+
+    /**
+     * 开启本轮:仅充气/保压/测试等中间步。 0=待机  4=充气 7=平衡 8=测试 10=排气
+     * 不含 0/1/9/65535,避免上一轮结束后设备停在结束态,扫新码被立刻当成新一轮并提交旧结果。
+     */
+    public boolean isRunningStep() {
+        return step != null && !step.isEmpty() && !isEndStep();
+    }
+
+    /** 读最终结果的结束步(含新协议 65535) */
+    public boolean isEndStep() {
+//        return "65535".equals(step) || "1".equals(step) || "9".equals(step) || "0".equals(step);
+        return "10".equals(step);
+    }
+
+    @Override
+    public String toString() {
+        return "QmCurUtil{" +
+                "step='" + step + '\'' +
+                ", temperature='" + temperature + '\'' +
+                ", pressureValue='" + pressureValue + '\'' +
+                ", leakValue='" + leakValue + '\'' +
+                '}';
+    }
+}

+ 258 - 0
src/com/mes/util/ytnetty/QmResultUtil.java

@@ -0,0 +1,258 @@
+package com.mes.util.ytnetty;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 最终结果 4353 / CS。
+ * 旧实现按 hex 下标截取,字段一错位 OK/NG、压强、泄漏全歪;
+ * 现先 hex→ASCII,再按定长或 CSV 解析。
+ */
+public class QmResultUtil {
+    public static final Logger log = LoggerFactory.getLogger(QmResultUtil.class);
+
+    public final String num;
+    public final String testDate;
+    public final String sn;
+    public final String resultType;
+    public final String result;
+    public final String programNumber;
+    public final String pressureValue;
+    public final String leakValue;
+    public final String pressureUnit;
+    public final String leakUnit;
+
+    public QmResultUtil(String msg) {
+        String ascii = new String(YtUtil.hexStringToByteArray(msg));
+        log.info("QmResultUtil ascii={}", ascii);
+
+        if (ascii.indexOf(',') >= 0) {
+            Parsed p = parseCsv(ascii);
+            this.num = p.num;
+            this.testDate = p.testDate;
+            this.sn = p.sn;
+            this.resultType = p.resultType;
+            this.result = p.result;
+            this.programNumber = p.programNumber;
+            this.pressureValue = p.pressureValue;
+            this.leakValue = p.leakValue;
+            this.pressureUnit = p.pressureUnit;
+            this.leakUnit = p.leakUnit;
+            return;
+        }
+
+        Parsed p = parseFixed(ascii);
+        this.num = p.num;
+        this.testDate = p.testDate;
+        this.sn = p.sn;
+        this.resultType = p.resultType;
+        this.result = p.result;
+        this.programNumber = p.programNumber;
+        this.pressureValue = p.pressureValue;
+        this.leakValue = p.leakValue;
+        this.pressureUnit = p.pressureUnit;
+        this.leakUnit = p.leakUnit;
+    }
+
+    /**
+     * 定长:CS + num(5) + date(14) + reserved(3) + sn(30)
+     * + resultType(1) + leak(6) + leakUnit(1) + result(1) + program(2) + pressure(8)
+     * 例:CS0035620260706160102000...(sn)...0001.55310002013.2800 → result='1'=OK
+     */
+    private Parsed parseFixed(String ascii) {
+        Parsed p = new Parsed();
+        if (ascii == null || ascii.length() < 73) {
+            log.warn("CS定长包过短 len={}", ascii == null ? 0 : ascii.length());
+            p.result = "NG";
+            p.pressureUnit = "kPa";
+            return p;
+        }
+
+        p.num = safeSub(ascii, 2, 7).trim();
+        p.testDate = safeSub(ascii, 7, 21).trim();
+        p.sn = safeSub(ascii, 24, 54).trim();
+        p.resultType = safeSub(ascii, 54, 55);
+        String leakRaw = safeSub(ascii, 55, 61).trim();
+        p.leakUnit = formatLeakUnit(safeSub(ascii, 61, 62));
+        p.result = formatResult(safeSub(ascii, 62, 63));
+        p.programNumber = safeSub(ascii, 63, 65).trim();
+        p.pressureValue = toNumberString(safeSub(ascii, 65, 73));
+        p.pressureUnit = "kPa";
+
+        String leak = toNumberString(leakRaw);
+        if ("1".equals(p.resultType) && leak.length() > 0 && !leak.startsWith("-")) {
+            leak = "-" + leak;
+        }
+        p.leakValue = leak;
+        return p;
+    }
+
+    /**
+     * CSV:与实时 RS 一样整包为逗号分隔。
+     * 优先识别字面 OK/NG;否则按 CS,num,date,sn,resultType,leak,unit,result,program,pressure 取值。
+     */
+    private Parsed parseCsv(String ascii) {
+        Parsed p = new Parsed();
+        String[] parts = ascii.split(",", -1);
+        p.pressureUnit = "kPa";
+
+        for (String part : parts) {
+            if (part == null) {
+                continue;
+            }
+            String t = part.trim();
+            if ("OK".equalsIgnoreCase(t) || "NG".equalsIgnoreCase(t)) {
+                p.result = t.toUpperCase();
+                break;
+            }
+        }
+
+        p.num = part(parts, 1);
+        p.testDate = part(parts, 2);
+        p.sn = part(parts, 3);
+        p.resultType = part(parts, 4);
+        if (p.leakValue == null || p.leakValue.isEmpty()) {
+            p.leakValue = toNumberString(part(parts, 5));
+        }
+        String unitCode = part(parts, 6);
+        p.leakUnit = formatLeakUnit(unitCode);
+        if (p.result == null || p.result.isEmpty()) {
+            p.result = formatResult(part(parts, 7));
+        }
+        p.programNumber = part(parts, 8);
+        p.pressureValue = toNumberString(part(parts, 9));
+        // 若压力在更前(类似 RS: 第5/6 段是压强/泄漏)
+        if (p.pressureValue.isEmpty() && parts.length >= 6) {
+            String maybePressure = toNumberString(part(parts, 4));
+            String maybeLeak = toNumberString(part(parts, 5));
+            if (!maybePressure.isEmpty()) {
+                p.pressureValue = maybePressure;
+            }
+            if (!maybeLeak.isEmpty()) {
+                p.leakValue = maybeLeak;
+            }
+        }
+        if ("1".equals(p.resultType) && p.leakValue.length() > 0 && !p.leakValue.startsWith("-")) {
+            p.leakValue = "-" + p.leakValue;
+        }
+        if (p.result == null || p.result.isEmpty()) {
+            p.result = "NG";
+        }
+        return p;
+    }
+
+    private static String part(String[] parts, int index) {
+        if (parts == null || index < 0 || index >= parts.length || parts[index] == null) {
+            return "";
+        }
+        return parts[index].trim();
+    }
+
+    private static String safeSub(String s, int start, int end) {
+        if (s == null || start >= s.length()) {
+            return "";
+        }
+        if (end > s.length()) {
+            end = s.length();
+        }
+        if (start < 0) {
+            start = 0;
+        }
+        if (start >= end) {
+            return "";
+        }
+        return s.substring(start, end);
+    }
+
+    private static String toNumberString(String raw) {
+        if (raw == null) {
+            return "";
+        }
+        String t = raw.trim();
+        if (t.isEmpty()) {
+            return "";
+        }
+        try {
+            return Double.valueOf(t) + "";
+        } catch (Exception e) {
+            return t;
+        }
+    }
+
+    public String formatLeakUnit(String leakUnit) {
+        if (leakUnit == null) {
+            return "";
+        }
+        switch (leakUnit.trim()) {
+            case "0":
+                return "P";
+            case "1":
+                return "Pa/s";
+            case "2":
+                return "mL/s";
+            case "3":
+                return "mL/min";
+            case "4":
+                return "cc/min";
+            default:
+                // 已是单位文本则原样返回
+                if (leakUnit.contains("Pa") || leakUnit.contains("mL") || leakUnit.contains("cc") || "P".equals(leakUnit.trim())) {
+                    return leakUnit.trim();
+                }
+                return "";
+        }
+    }
+
+    public String formatResult(String result) {
+        if (result == null || result.trim().isEmpty()) {
+            return "NG";
+        }
+        String r = result.trim();
+        if ("OK".equalsIgnoreCase(r) || "NG".equalsIgnoreCase(r)) {
+            return r.toUpperCase();
+        }
+        switch (r) {
+            case "1":
+                return "OK";
+            case "2": // +NG
+            case "3": // 大漏
+            case "4": // 正向超量程
+            case "5": // -NG
+            case "6": // 负向超量程
+            case "7": // 压力不足
+            case "9": // 压力超限
+                return "NG";
+            default:
+                return "NG";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "QmResultUtil{" +
+                "num='" + num + '\'' +
+                ", testDate='" + testDate + '\'' +
+                ", sn='" + sn + '\'' +
+                ", resultType='" + resultType + '\'' +
+                ", result='" + result + '\'' +
+                ", programNumber='" + programNumber + '\'' +
+                ", pressureValue='" + pressureValue + '\'' +
+                ", leakValue='" + leakValue + '\'' +
+                ", pressureUnit='" + pressureUnit + '\'' +
+                ", leakUnit='" + leakUnit + '\'' +
+                '}';
+    }
+
+    private static class Parsed {
+        String num = "";
+        String testDate = "";
+        String sn = "";
+        String resultType = "";
+        String result = "";
+        String programNumber = "";
+        String pressureValue = "";
+        String leakValue = "";
+        String pressureUnit = "";
+        String leakUnit = "";
+    }
+}

+ 111 - 0
src/com/mes/util/ytnetty/YtDecoderServer.java

@@ -0,0 +1,111 @@
+package com.mes.util.ytnetty;
+
+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.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class YtDecoderServer extends ByteToMessageDecoder {
+
+    private static final byte START_CHAR = (byte) 0x02; // 假设的起始字符
+    private static final byte END_CHAR = (byte) 0x03; // 假设的结束字符
+    private final ByteBuf msgin = Unpooled.buffer();
+    private final Charset charset = Charset.forName("UTF-8");
+
+    private static final Logger logger = LoggerFactory.getLogger(YtDecoderServer.class);
+
+
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        logger.info(Thread.currentThread() + "收到了一次数据包,长度是:" + in.readableBytes());
+        String content = ByteBufUtil.hexDump(in).toUpperCase();
+        logger.info("接收包内容:" + in.toString(charset));
+        logger.info("接收包内容2:" +   content);
+
+        String content2 = hexStringToAscii(ByteBufUtil.hexDump(in)).toLowerCase();
+
+        while (in.isReadable()) {
+            byte b = in.readByte();
+            if (b == START_CHAR) {
+                // 当遇到起始字符时,重置消息StringBuilder
+                msgin.clear();
+            } else if (b == END_CHAR) {
+                // 当遇到结束字符时,将消息加入到解码列表中
+                String content3 = ByteBufUtil.hexDump(msgin).toUpperCase();
+                logger.info("接收信息:" + msgin.toString(charset));
+                logger.info("接收信息2:" + content3);
+//                out.add(content3);
+                out.add(content3);
+            } else {
+                // 正常的消息字符,追加到StringBuilder
+                msgin.writeByte(b);
+            }
+        }
+    }
+
+    /**
+     * 计算16进制字符串的BCC校验码
+     * @param hexData 16进制字符串
+     * @return BCC校验码
+     */
+    public static byte calculateBCC(String hexData) {
+        byte bcc = 0; // 初始化BCC校验码为0
+        for (int i = 0; i < hexData.length(); i += 2) { // 每次处理两个字符(1个字节)
+            String byteStr = hexData.substring(i, i + 2); // 获取当前16进制字节
+            int byteValue = Integer.parseInt(byteStr, 16); // 将16进制字符串转换为整数
+            bcc ^= byteValue; // 执行异或操作
+        }
+        return bcc; // 返回计算后的BCC校验码
+    }
+
+    // 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();
+    }
+
+    public static String hexStringToString(String str) {
+        StringBuilder name = new StringBuilder();
+        String[] split = str.split("\\\\x");
+        name.append(split[0]);
+        for (int i = 1; i < split.length; i++) {
+            if (!split[i].isEmpty() && split.length > i + 2 && split[i].length() == 2 && split[i + 1].length() == 2 && split[i+2].length() >= 2) {
+                String name16 = split[i] + split[i + 1] + split[i + 2].substring(0, 2);
+                // name16 = name16.replace(" ", "");
+                byte[] baKeyword = new byte[name16.length() / 2];
+                for (int j = 0; j < baKeyword.length; j++) {
+                    try {
+                        baKeyword[j] = (byte) (0xff & Integer.parseInt(name16.substring(j * 2, j * 2 + 2), 16));
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+                try {
+                    name16 = new String(baKeyword, StandardCharsets.UTF_8);
+                } catch (Exception e1) {
+                    e1.printStackTrace();
+                }
+                name.append(name16);
+                name.append(split[i + 2].substring(2));
+                i = i + 2;
+            }else {
+                name.append("\\\\x" + split[i]);
+            }
+        }
+        return name.toString();
+    }
+
+}

+ 60 - 0
src/com/mes/util/ytnetty/YtEncoderServer.java

@@ -0,0 +1,60 @@
+package com.mes.util.ytnetty;
+
+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.MessageToByteEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class YtEncoderServer extends MessageToByteEncoder<String> {
+
+    private static final String START_CHAR = "02"; // 假设的起始字符
+    private static final String END_CHAR = "03"; // 假设的结束字符
+    private static final Logger logger = LoggerFactory.getLogger(YtEncoderServer.class);
+
+    @Override
+    protected void encode(ChannelHandlerContext channelHandlerContext, String message, ByteBuf out) throws Exception {
+        //String message 16禁止字符串
+        String contstr = START_CHAR + message + END_CHAR;
+        System.out.println("contstr:"+contstr);
+        String bcc = String.format("%02x",calculateBCC(contstr));
+
+        String allstr = contstr+bcc;
+        System.out.println("allstr:"+allstr);
+
+        ByteBuf bccbf = Unpooled.wrappedBuffer(allstr.getBytes("UTF-8"));
+        out.writeBytes(bccbf);
+
+//        String content2 = hexStringToAscii(ByteBufUtil.hexDump(buf));
+        bccbf.release(); // 释放ByteBuf资源
+    }
+
+    // 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();
+    }
+
+    /**
+     * 计算16进制字符串的BCC校验码
+     * @param hexData 16进制字符串
+     * @return BCC校验码
+     */
+    public static byte calculateBCC(String hexData) {
+        byte bcc = 0; // 初始化BCC校验码为0
+        for (int i = 0; i < hexData.length(); i += 2) { // 每次处理两个字符(1个字节)
+            String byteStr = hexData.substring(i, i + 2); // 获取当前16进制字节
+            int byteValue = Integer.parseInt(byteStr, 16); // 将16进制字符串转换为整数
+            bcc ^= byteValue; // 执行异或操作
+        }
+        return bcc; // 返回计算后的BCC校验码
+    }
+}

+ 79 - 0
src/com/mes/util/ytnetty/YtNettyClient.java

@@ -0,0 +1,79 @@
+package com.mes.util.ytnetty;
+
+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.timeout.IdleStateHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+public class YtNettyClient {
+
+    public static final Logger log =  LoggerFactory.getLogger(YtNettyClientHandler.class);
+    public static SocketChannel socketChannel;
+    public static ChannelFuture future;
+    //配置线程组
+    public static EventLoopGroup group = new NioEventLoopGroup();
+    //创建服务启动器
+    public static Bootstrap bootstrap = new Bootstrap();
+
+    public static String ip;
+    public static int port;
+    public static int tcpOnline = 0; // 0不在线 1=在线
+    private static volatile boolean inited = false;
+
+    public static void run(String inip, int inport) {
+        try{
+            ip = inip;
+            port = inport;
+            if (!inited) {
+                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("decoder", new YtDecoderServer())
+                                        .addLast("encoder",new YtEncoderServer())
+                                        .addLast(new IdleStateHandler(60, 90, 120, TimeUnit.SECONDS))
+                                        .addLast(new YtNettyClientHandler());
+                            }
+                        });
+                inited = true;
+            }
+            bootstrap.remoteAddress(ip,port);
+            doConnect();
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+
+    }
+
+    public static void doConnect() throws InterruptedException {
+        tcpOnline = 0;
+        //连接
+        future = bootstrap.connect();
+        log.info("客户端正在连接服务端...");
+        //客户端断线重连逻辑
+        future.addListener((ChannelFutureListener) future1 -> {
+            if (future1.isSuccess()) {
+                log.info("客户端连接服务端成功...");
+                tcpOnline = 1;
+            } else {
+                future1.channel().eventLoop().schedule(() -> run(ip,port), 10, TimeUnit.SECONDS);
+            }
+        });
+        socketChannel = (SocketChannel) future.channel();
+    }
+
+    public static int getTcpOnline(){
+        return tcpOnline;
+    }
+}
+
+
+

+ 245 - 0
src/com/mes/util/ytnetty/YtNettyClientHandler.java

@@ -0,0 +1,245 @@
+package com.mes.util.ytnetty;
+
+import com.mes.ui.MesClient;
+import com.mes.util.DateLocalUtils;
+import com.mes.util.TestParam;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class YtNettyClientHandler extends ChannelInboundHandlerAdapter {
+
+    public static final Logger log = LoggerFactory.getLogger(YtNettyClientHandler.class);
+
+    /** 泄露值合格区间(含边界) */
+    private static final double LEAK_OK_MIN = -10.0;
+    private static final double LEAK_OK_MAX = 2.5;
+
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    public static byte[] responseByte;
+
+    private int timeoutNum = 0;
+    private int timeoutNumMax = 3;
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        log.info("mes connecting:" + sdf.format(new Date()));
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        Channel channel = ctx.channel();
+        log.error("close tcp, ip:" + channel.remoteAddress());
+        YtNettyClient.tcpOnline = 0;
+
+        System.out.println("3s 之后尝试重新连接服务器...");
+        Thread.sleep(3 * 1000);
+        YtNettyClient.doConnect();
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        IdleState state = ((IdleStateEvent) evt).state();
+        if (state.equals(IdleState.READER_IDLE)) {
+            System.out.println("客户端read超时" + (timeoutNum++) + "次");
+
+            if (timeoutNum > timeoutNumMax) {
+                System.out.println("超时次数上限,关闭这个不活跃的连接");
+                ctx.channel().close();
+            }
+        }
+        if (state.equals(IdleState.ALL_IDLE)) {
+            sendHeartbeatPacket(ctx);
+        }
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
+        timeoutNum = 0;
+        log.info("message:" + message);
+        String msg = message + "";
+        String code = YtUtil.getCode(msg);
+        log.info("code:" + code);
+        log.info("msg:" + msg);
+        try {
+            if (MesClient.configParam != null && !isInntaiDevice(MesClient.configParam.getDevice())) {
+                return;
+            }
+            switch (code) {
+                case "4353": // 最终结果 CS
+                    if (MesClient.work_status != 1 || MesClient.mesTjFlag == 1) {
+                        log.info("忽略4353:未在测试中或本轮已提交");
+                        break;
+                    }
+                    MesClient.mesTjFlag = 1;
+                    String sn = MesClient.product_sn.getText().trim();
+                    QmResultUtil qmResultUtil = new QmResultUtil(msg);
+                    log.info("INNTAI最终结果 {}", qmResultUtil);
+
+                    MesClient.param1.setText(qmResultUtil.pressureValue);
+                    MesClient.param2.setText(qmResultUtil.leakValue);
+
+                    if ("OK".equals(qmResultUtil.result)) {
+                        MesClient.result.setText("OK");
+                        MesClient.result.setForeground(Color.GREEN);
+                    } else {
+                        MesClient.result.setText("NG");
+                        MesClient.result.setForeground(Color.RED);
+                    }
+
+                    TestParam testParam = new TestParam();
+                    testParam.setSn(sn);
+                    testParam.setOprno(MesClient.getEffectiveOprno());
+                    testParam.setOprnoTitle(MesClient.configParam != null ? MesClient.configParam.getOprnoTitle() : "");
+                    testParam.setLineSn(MesClient.getEffectiveLineSn());
+                    testParam.setUcode(MesClient.user_menu != null ? MesClient.user_menu.getText().trim() : "");
+                    testParam.setParam1(qmResultUtil.pressureValue);
+                    testParam.setParam2(qmResultUtil.pressureUnit);
+                    testParam.setParam3(qmResultUtil.leakValue);
+                    testParam.setParam4(qmResultUtil.leakUnit);
+                    testParam.setParam5(MesClient.configParam != null && MesClient.configParam.getProgramNo() != null
+                            ? MesClient.configParam.getProgramNo() : "");
+                    testParam.setResult(qmResultUtil.result);
+                    testParam.setDeviceType(MesClient.configParam != null ? MesClient.configParam.getDevice() : "INNTAI");
+                    testParam.setRemark("");
+                    testParam.setCreateTime(DateLocalUtils.getCurrentTime());
+                    MesClient.afterTestSaveAndUpload(testParam);
+                    break;
+                case "5253": // 实时结果
+                    if (MesClient.work_status != 1 || MesClient.workstop == 1) {
+                        break;
+                    }
+                    QmCurUtil qmCurUtil = new QmCurUtil(msg);
+                    log.info("qmCurUtil:" + qmCurUtil.toString());
+                    // 仅中间步骤开启本轮
+                    if (qmCurUtil.isRunningStep()) {
+                        if (MesClient.mesStartFlag == 0 || MesClient.mesTjFlag == 1) {
+                            MesClient.mesStartFlag = 1;
+                            MesClient.mesTjFlag = 0;
+                            log.info("本轮测试已开始,step={}", qmCurUtil.step);
+                        }
+                        // 测试中不显示OK/NG
+                        if (MesClient.result != null
+                                && !"等待结果".equals(MesClient.result.getText())) {
+                            MesClient.result.setText("等待结果");
+                            MesClient.result.setForeground(Color.GRAY);
+                        }
+                    }
+                    MesClient.setMenuStatus("当前步骤:" + qmCurUtil.step, 0);
+                    // 实时只刷压强/泄漏数值,不刷OK/NG
+                    MesClient.param1.setText(qmCurUtil.pressureValue);
+                    MesClient.param2.setText(qmCurUtil.leakValue);
+                    if (qmCurUtil.pressureValue != null && !qmCurUtil.pressureValue.trim().isEmpty()) {
+                        MesClient.lastQmPressure = qmCurUtil.pressureValue.trim();
+                    }
+                    if (qmCurUtil.leakValue != null && !qmCurUtil.leakValue.trim().isEmpty()) {
+                        MesClient.lastQmLeak = qmCurUtil.leakValue.trim();
+                    }
+
+                    // 气密仪结束步:才判定并显示OK/NG、提交
+                    if (MesClient.mesStartFlag == 1 && MesClient.mesTjFlag == 0 && qmCurUtil.isEndStep()) {
+//                        finishByLeakRange();
+                        MesClient.workstop = 1;
+                        ctx.writeAndFlush(YtUtil.getSendByteBuf(YtUtil.getResultCommon()));
+                    }
+                    break;
+                case "5453":
+                case "4F4B":
+                case "4552":
+                    break;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 测试结束:按泄露值 [-4, 8] 判定 OK/NG,显示并提交。
+     * 不再使用气密仪 4353 自带判定结果。
+     */
+    private void finishByLeakRange() {
+        String sn = MesClient.product_sn.getText().trim();
+        if (sn.isEmpty()) {
+            log.info("结束判定跳过:工件码为空");
+            return;
+        }
+        String pressureValue = MesClient.lastQmPressure != null ? MesClient.lastQmPressure.trim() : "";
+        String leakValue = MesClient.lastQmLeak != null ? MesClient.lastQmLeak.trim() : "";
+        if (leakValue.isEmpty()) {
+            log.info("结束判定跳过:泄露值尚未收到");
+            return;
+        }
+
+        MesClient.mesTjFlag = 1;
+
+        boolean ok = isLeakOk(leakValue);
+        String lastRet = ok ? "OK" : "NG";
+        log.info("测试结束判定:leak={}, 区间[{}, {}], result={}",
+                leakValue, LEAK_OK_MIN, LEAK_OK_MAX, lastRet);
+
+        String pressureUnit = "kPa";
+        MesClient.param1.setText(pressureValue + pressureUnit);
+        MesClient.param2.setText(leakValue);
+        if (ok) {
+            MesClient.result.setText("OK");
+            MesClient.result.setForeground(Color.GREEN);
+        } else {
+            MesClient.result.setText("NG");
+            MesClient.result.setForeground(Color.RED);
+        }
+
+        TestParam testParam = new TestParam();
+        testParam.setSn(sn);
+        testParam.setParam1(pressureValue);
+        testParam.setParam2(pressureUnit);
+        testParam.setParam3(leakValue);
+        testParam.setParam4("");
+        testParam.setResult(lastRet);
+        testParam.setRemark(ok ? "" : "泄露值超限(" + LEAK_OK_MIN + "~" + LEAK_OK_MAX + ")");
+        testParam.setOprno(MesClient.getEffectiveOprno());
+        testParam.setOprnoTitle(MesClient.configParam != null ? MesClient.configParam.getOprnoTitle() : "");
+        testParam.setLineSn(MesClient.getEffectiveLineSn());
+        testParam.setUcode(MesClient.user_menu != null ? MesClient.user_menu.getText().trim() : "");
+        testParam.setParam5(MesClient.configParam != null && MesClient.configParam.getProgramNo() != null
+                ? MesClient.configParam.getProgramNo() : "");
+        testParam.setDeviceType(MesClient.configParam != null ? MesClient.configParam.getDevice() : "INNTAI");
+        testParam.setCreateTime(DateLocalUtils.getCurrentTime());
+        MesClient.afterTestSaveAndUpload(testParam);
+    }
+
+    /** 泄露值在 [-4, 8](含边界)为 OK,否则 NG */
+    static boolean isLeakOk(String leakValue) {
+        try {
+            double v = Double.parseDouble(leakValue.trim());
+            return v >= LEAK_OK_MIN && v <= LEAK_OK_MAX;
+        } catch (Exception e) {
+            log.warn("泄露值无法解析为数字: {}", leakValue);
+            return false;
+        }
+    }
+
+    static boolean isInntaiDevice(String device) {
+        if (device == null) {
+            return false;
+        }
+        String d = device.trim();
+        return "INNTAI".equalsIgnoreCase(d) || "中皖英泰".equals(d);
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        System.out.println("exceptionCaught");
+    }
+
+    private void sendHeartbeatPacket(ChannelHandlerContext ctx) {
+        ctx.writeAndFlush(YtUtil.getSendByteBuf(YtUtil.getStateCommon()));
+    }
+}

+ 123 - 0
src/com/mes/util/ytnetty/YtUtil.java

@@ -0,0 +1,123 @@
+package com.mes.util.ytnetty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class YtUtil {
+    public static final Logger log =  LoggerFactory.getLogger(YtUtil.class);
+
+    // 获取功能码
+    public static String getCode(String msg){
+        return msg.substring(0,4);
+    }
+
+    /**
+     * 计算16进制字符串的BCC校验码
+     * @param hexData 16进制字符串
+     * @return BCC校验码
+     */
+    public static byte calculateBCC(String hexData) {
+        byte bcc = 0; // 初始化BCC校验码为0
+        for (int i = 0; i < hexData.length(); i += 2) { // 每次处理两个字符(1个字节)
+            String byteStr = hexData.substring(i, i + 2); // 获取当前16进制字节
+            int byteValue = Integer.parseInt(byteStr, 16); // 将16进制字符串转换为整数
+            bcc ^= byteValue; // 执行异或操作
+        }
+        return bcc; // 返回计算后的BCC校验码
+    }
+
+    public static String formatCommon(String msg){
+        String start = "02";
+        String end = "03";
+        String content = start+msg+end;
+        String bcc = String.format("%02x",calculateBCC(content));
+        System.out.println(content + bcc);
+        return content + bcc;
+    }
+
+    // 启动指令
+    public static String getStartCommon(){
+        return formatCommon("5354");
+    }
+
+    // 停止/复位指令
+    public static String getStopCommon(){
+        return formatCommon("5254");
+    }
+
+    // 锁机指令
+    public static String getLockCommon(){
+        return formatCommon("4C4D31");
+    }
+
+    // 解锁指令
+    public static String getUnLockCommon(){
+        return formatCommon("4C4D30");
+    }
+
+    // 选频指令
+    public static String getSelectProgramCommon(String programNo){
+        String no = String.format("%03d", Integer.parseInt(programNo));
+        return formatCommon("4348"+asciiToHex(no));
+    }
+
+    // 读取当前通道
+    public static String getCurProgramCommon(){
+        return formatCommon("5250");
+    }
+
+    // 读取实时
+    public static String getCurDataCommon(){
+        return formatCommon("5253");
+    }
+
+    // 设备状态
+    public static String getStateCommon(){
+        return formatCommon("5453");
+    }
+
+
+    // 传输结果
+    public static String getResultCommon(){
+        return formatCommon("4353");
+    }
+
+    public static String asciiToHex(String input){
+        StringBuilder hexValues = new StringBuilder();
+        for (char c : input.toCharArray()) {
+            int asciiValue = (int) c; // 获取ASCII值
+            String hexValue = Integer.toHexString(asciiValue); // 转换为16进制字符串
+            if (hexValues.length() > 0) { // 如果不是第一个字符,添加空格分隔
+                hexValues.append("");
+            }
+            hexValues.append(hexValue); // 添加到StringBuilder中
+        }
+        return hexValues.toString();
+    }
+
+    public static byte[] hexStringToByteArray(String s) {
+        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;
+    }
+
+    /**
+     * 将字符串消息转为ByteBuf
+     * 16进制字符串转二进制
+     * @param hex 字符串消息
+     */
+    public static ByteBuf getSendByteBuf(String hex) {
+        byte[] bytes = new byte[hex.length() / 2];
+        for (int i = 0; i < hex.length(); i += 2) {
+            int value = Integer.parseInt(hex.substring(i, i + 2), 16);
+            bytes[i/2] = (byte)(value & 0xFF);
+        }
+        return Unpooled.wrappedBuffer(bytes);
+    }
+}

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

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

+ 1 - 1
src/resources/config/config.properties

@@ -1,4 +1,4 @@
-mes.gw=OP280A
+mes.gw=OP022A
 mes.server_ip=127.0.0.1
 #mes.server_ip=192.168.16.99
 mes.tcp_port=3000