package com.mes.ui; import com.github.s7connector.api.DaveArea; import com.github.s7connector.api.factory.S7ConnectorFactory; import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; import com.mes.util.JdbcUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.math.BigInteger; import java.text.DecimalFormat; public class S7Util { private static final Logger log = LoggerFactory.getLogger(S7Util.class); private static final int DB9051 = 9051; private static final int DB9051_LEN = 38; private static final int DB9052 = 9052; /** 连接失败后的重试间隔 */ private static final long RECONNECT_COOLDOWN_MS = 5000L; private static volatile long nextReconnectAt = 0L; private static volatile boolean connected = false; /** 实测本设备 slot=2 可通;成功后优先复用 */ private static volatile int lastSuccessSlot = 2; /** * 监控线程入口:读 DB9051 一次,刷新界面并执行业务状态机 */ public static void pollDeviceCycle() { try { if (!ensureConnected()) { SwingUtilities.invokeLater(() -> MesClient.updatePlcStatus(false)); return; } SwingUtilities.invokeLater(() -> MesClient.updatePlcStatus(true)); byte[] db9051; try { db9051 = MesClient.s7Connector.read(DaveArea.DB, DB9051, DB9051_LEN, 0); } catch (Exception e) { markBroken("读DB9051", e); SwingUtilities.invokeLater(() -> MesClient.updatePlcStatus(false)); return; } if (db9051 == null) { return; } updateRealtimeDisplay(db9051); getDeviceState(db9051); } catch (Exception e) { log.error("[PLC异常] pollDeviceCycle", e); markBroken("pollDeviceCycle", e); SwingUtilities.invokeLater(() -> MesClient.updatePlcStatus(false)); } } public static synchronized boolean isConnected() { return connected && MesClient.s7Connector != null; } public static synchronized void disconnect() { connected = false; if (MesClient.s7Connector != null) { try { MesClient.s7Connector.close(); } catch (Exception ignored) { } MesClient.s7Connector = null; } } public static synchronized boolean connect() { disconnect(); // Unexpected function code 常见于 rack/slot 不匹配: // 旧版默认 slot=2;S7-1200 常用 slot=1。按历史成功值优先,再依次尝试。 int[] slots; if (lastSuccessSlot == 1) { slots = new int[]{1, 2}; } else if (lastSuccessSlot == 2) { slots = new int[]{2, 1}; } else { slots = new int[]{2, 1}; } Exception lastError = null; for (int slot : slots) { try { log.info("[PLC连接] 尝试 {} rack=0 slot={} port=102 timeout=5000", MesClient.plcUrl, slot); MesClient.s7Connector = S7ConnectorFactory .buildTCPConnector() .withHost(MesClient.plcUrl) .withPort(102) .withRack(0) .withSlot(slot) .withTimeout(5000) .build(); byte[] probe = MesClient.s7Connector.read(DaveArea.DB, DB9051, DB9051_LEN, 0); if (probe == null || probe.length < DB9051_LEN) { throw new IllegalStateException("探测读取DB9051返回空数据"); } connected = true; lastSuccessSlot = slot; nextReconnectAt = 0L; log.info("[PLC连接] 连接成功 {} rack=0 slot={}", MesClient.plcUrl, slot); return true; } catch (Exception e) { lastError = e; log.error("[PLC连接] rack=0 slot={} 失败: {}", slot, rootCause(e)); disconnect(); try { Thread.sleep(800); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; } } } log.error("[PLC连接] 全部 slot 尝试失败 {}: {}", MesClient.plcUrl, lastError == null ? "" : rootCause(lastError)); nextReconnectAt = System.currentTimeMillis() + RECONNECT_COOLDOWN_MS; return false; } /** * 确保可用连接:半开连接探测失败时强制断开再重连。 */ public static synchronized boolean ensureConnected() { if (connected && MesClient.s7Connector != null) { try { // 心跳探测只读1字节,减轻通讯负担 byte[] probe = MesClient.s7Connector.read(DaveArea.DB, DB9051, 1, 0); if (probe != null && probe.length >= 1) { return true; } log.info("[PLC连接] 探测读取异常,准备重连"); } catch (Exception e) { log.info("[PLC连接] 连接已失效,准备重连: {}", rootCause(e)); } disconnect(); } if (System.currentTimeMillis() < nextReconnectAt) { return false; } return connect(); } private static synchronized void markBroken(String action, Exception e) { log.info("[PLC] {} 失败,释放连接: {}", action, rootCause(e)); disconnect(); nextReconnectAt = System.currentTimeMillis() + RECONNECT_COOLDOWN_MS; } private static String rootCause(Throwable e) { if (e == null) { return ""; } Throwable cur = e; while (cur.getCause() != null && cur.getCause() != cur) { cur = cur.getCause(); } if (cur == e) { return e.getClass().getSimpleName() + ": " + e.getMessage(); } return e.getClass().getSimpleName() + "(" + e.getMessage() + ") <- " + cur.getClass().getSimpleName() + ": " + cur.getMessage(); } // 监听状态 public static void getDeviceState(byte[] db9051){ try { if (MesClient.s7Connector == null || db9051 == null) { return; } if (MesClient.tjFlag == 3) { if (MesClient.tjStatus == 0) { MesClient.tjStatus = 1; log.info("[流程] 焊接完成,复位允许启动并提交MES总结果 OK,工件={}", MesClient.product_sn.getText()); sendAllowStart(false); Boolean ret = DataUtil.sendQuality( MesClient.nettyClient, MesClient.product_sn.getText(), "OK", MesClient.user20); if (ret) { log.info("[MES写] MQDW 提交成功,工件={}", MesClient.product_sn.getText()); MesClient.resetScanA(); MesClient.scan_type = 1; MesClient.scanBarcode(); MesClient.setMenuStatus("结果提交成功,请扫下一件", 0); } else { log.warn("[MES写] MQDW 提交失败,工件={},等待重试", MesClient.product_sn.getText()); MesClient.tjStatus = 0; MesClient.setMenuStatus(MesClient.tjFlagTextErr, -1); } } } else if (isWaitingProcess()) { if (isWeldingComplete(db9051)) { log.info("[流程] 检测到焊接完成 DB9051.DBX36.0=true,tjFlag: {} -> 3", MesClient.tjFlag); MesClient.tjFlag = 3; MesClient.tjStatus = 0; MesClient.status_menu.setText(MesClient.tjFlagText3); } } else if (MesClient.tjFlag == 0) { if (getAllowStart()) { log.info("[流程] 未扫码状态检测到允许启动仍为true,执行复位"); sendAllowStart(false); } } }catch (Exception e){ log.error("[流程异常] getDeviceState", e); } } /** @deprecated 请使用 pollDeviceCycle */ public static void getDeviceState(){ byte[] db9051 = readDb9051Raw(true); if (db9051 != null) { getDeviceState(db9051); } } /** * 实时刷新 DB9051 主轴转速、进给速度、压力值到界面 */ public static void updateRealtimeDisplay(byte[] data) { try { if (data == null || MesClient.spindleSpeedLabel == null) { return; } final String spindle = formatS7Val(subBytes(data, 0, 4)); final String feed = formatS7Val(subBytes(data, 4, 4)); final String pressure = formatS7Val(subBytes(data, 12, 4)); saveFswParamIfProcessing(spindle, feed, pressure); SwingUtilities.invokeLater(() -> MesClient.updatePlcDisplay(spindle, feed, pressure)); } catch (Exception e) { log.error("[PLC异常] updateRealtimeDisplay", e); } } /** @deprecated 请使用 pollDeviceCycle */ public static void updateRealtimeDisplay() { byte[] data = readDb9051Raw(true); updateRealtimeDisplay(data); } private static byte[] readDb9051Raw(boolean withLog) { try { if (!ensureConnected()) { if (withLog) { log.warn("[PLC读] DB9051 失败:S7未连接"); } return null; } byte[] data = MesClient.s7Connector.read(DaveArea.DB, DB9051, DB9051_LEN, 0); if (withLog) { log.info(formatDb9051Log(data)); } return data; } catch (Exception e) { log.error("[PLC读] DB9051 异常", e); markBroken("读DB9051", e); return null; } } static String formatDb9051Log(byte[] data) { if (data == null) { return "[PLC读] DB9051 数据为空"; } return String.format( "[PLC读] DB9051 | 主轴转速=%s(HEX:%s) 进给速度=%s(HEX:%s) 压力值=%s(HEX:%s) | " + "焊接长度1=%s 2=%s 3=%s 4=%s 5=%s | 焊接完成(DBX36.0)=%s 出站OK(DBB37)=%d | tjFlag=%d work_status=%d", formatS7Val(subBytes(data, 0, 4)), bytesToHex(subBytes(data, 0, 4)), formatS7Val(subBytes(data, 4, 4)), bytesToHex(subBytes(data, 4, 4)), formatS7Val(subBytes(data, 12, 4)), bytesToHex(subBytes(data, 12, 4)), formatS7Val(subBytes(data, 16, 4)), formatS7Val(subBytes(data, 20, 4)), formatS7Val(subBytes(data, 24, 4)), formatS7Val(subBytes(data, 28, 4)), formatS7Val(subBytes(data, 32, 4)), isWeldingComplete(data), data[37] & 0xFF, MesClient.tjFlag, MesClient.work_status ); } /** 等待加工中(质量合格、尚未收到焊接完成),不含 tjFlag=3 提交阶段 */ private static boolean isWaitingProcess() { return MesClient.tjFlag == 1; } private static boolean isMeaningfulParam(String value) { if (value == null || value.trim().isEmpty() || "--".equals(value.trim())) { return false; } try { float v = Float.parseFloat(value.trim()); return !Float.isNaN(v) && !Float.isInfinite(v) && Math.abs(v) > 1e-4f; } catch (Exception e) { return false; } } private static void saveFswParamIfProcessing(String spindleSpeed, String feedRate, String pressure) { if (!isWaitingProcess()) { return; } String sn = MesClient.product_sn.getText(); if (sn == null || sn.trim().isEmpty()) { return; } // 设备未启动前三个参数多为0,等任一非0再开始存,避免无用数据 if (!MesClient.paramRecordingStarted) { if (isMeaningfulParam(spindleSpeed) || isMeaningfulParam(feedRate) || isMeaningfulParam(pressure)) { MesClient.paramRecordingStarted = true; log.info("[过程参数] 检测到有效数据,开始缓存 sn={}", sn.trim()); } else { return; } } MesClient.getUser(); boolean saved = JdbcUtils.insertFswParam( MesClient.mes_gw, MesClient.mes_line_sn, sn.trim(), spindleSpeed, feedRate, pressure, MesClient.user20.trim() ); if (saved) { log.debug("[过程参数] 本地缓存 sn={} 主轴={} 进给={} 压力={}", sn.trim(), spindleSpeed, feedRate, pressure); } } /** DB9051.DBX36.0 焊接完成(BOOL) */ private static boolean isWeldingComplete(byte[] data) { if (data.length <= 36) { return false; } return (data[36] & (1 << 0)) != 0; } private static byte[] subBytes(byte[] src, int offset, int length) { byte[] dest = new byte[length]; System.arraycopy(src, offset, dest, 0, length); return dest; } //屏蔽mes交互 如果设备禁止启动 则启动设备 public static void getDeviceShield(){ try { if (!getAllowStart()) { log.info("[MES屏蔽] 强制发送允许启动 DB9052.DBX0.0=true"); sendAllowStart(true); } }catch (Exception e){ log.error("[MES屏蔽异常] getDeviceShield", e); } } // 监听急停 public static void checkStop(){ try { if (!ensureConnected()) { return; } byte[] bytes = MesClient.s7Connector.read(DaveArea.DB, DB9052, 1, 0); log.info(formatDb9052Log(bytes[0], "读-急停检测")); int jtstatus = getBit(bytes[0], 7); log.info("[PLC读] DB9052.DBX0.7 急停状态={}", jtstatus); if(jtstatus == 0){ switchEnable(0); } if(MesClient.tjFlag == 2 && jtstatus == 0){ MesClient.tjResult = "NG"; MesClient.finish_ok_bt.setEnabled(true); MesClient.finish_ng_bt.setEnabled(true); } }catch (Exception e){ log.error("[PLC异常] checkStop", e); markBroken("checkStop", e); } } public static int getBit(byte b, int n) { String binaryString = Integer.toBinaryString(b & 0xFF); binaryString = String.format("%8s", binaryString).replace(' ', '0'); return binaryString.charAt(n) == '1' ? 1 : 0; } private static String formatDb9052Log(byte b, String action) { boolean allowStart = (b & (1 << 0)) != 0; return String.format("[PLC%s] DB9052.DBB0=0x%02X DBX0.0(允许启动)=%s DBX0.7(急停)=%d", action, b & 0xFF, allowStart, getBit(b, 7)); } // 转大端排序 private static byte[] xtob( byte[] bytes){ byte[] reversedBytes = new byte[bytes.length]; System.arraycopy(bytes, 0, reversedBytes, 0, bytes.length); for (int i = 0; i < bytes.length / 2; i++) { byte temp = reversedBytes[i]; reversedBytes[i] = reversedBytes[reversedBytes.length - 1 - i]; reversedBytes[reversedBytes.length - 1 - i] = temp; } return reversedBytes; } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b & 0xFF)); } return sb.toString(); } // 格式化 S7 REAL(大端 IEEE754,不做字节反转) private static float parseS7Real(byte[] val) { if (val == null || val.length < 4) { return 0f; } int bits = ((val[0] & 0xFF) << 24) | ((val[1] & 0xFF) << 16) | ((val[2] & 0xFF) << 8) | (val[3] & 0xFF); return Float.intBitsToFloat(bits); } private static String formatS7Val(byte[] val){ float floatValue = parseS7Real(val); if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) { return "0.0"; } DecimalFormat df = new DecimalFormat("#.#"); df.setMaximumFractionDigits(1); return df.format(floatValue); } // 禁止/启用启动 public static Boolean switchEnable(int state){ try{ log.info("[PLC写] switchEnable state={} -> DB9052.DBX0.0={}", state, state == 1); sendAllowStart(state == 1); return true; }catch (Exception e){ log.error("[PLC异常] switchEnable", e); return false; } } // 获取 DB9052.DBX0.0 允许启动状态 public static boolean getAllowStart() { try { if (!ensureConnected()) { return false; } byte[] data = MesClient.s7Connector.read(DaveArea.DB, DB9052, 1, 0); log.info(formatDb9052Log(data[0], "读-允许启动")); return (data[0] & (1 << 0)) != 0; } catch (Exception e) { log.error("[PLC读] DB9052 允许启动 异常", e); markBroken("读允许启动", e); return false; } } // 获取允许运行状态 public static Integer getSwitchEnable(){ try{ if (!ensureConnected()) { return 0; } byte[] data = MesClient.s7Connector.read(DaveArea.DB, DB9052, 1, 0); log.info(formatDb9052Log(data[0], "读-整字节")); return new BigInteger(xtob(data)).intValue(); }catch (Exception e){ log.error("[PLC读] DB9052 整字节 异常", e); markBroken("读DB9052", e); return 0; } } /** DB9052.DBX0.0 允许启动信号:true=允许,false=禁止/复位 */ public static boolean sendAllowStart(boolean allow) { log.info("[PLC写] 请求设置 DB9052.DBX0.0 允许启动={}", allow); return writeBit(DB9052, 0, 0, allow); } /** * 兼容旧接口:true=禁止启动,false=允许启动 */ public static boolean sendStartSignal(boolean forbidStart) { return sendAllowStart(!forbidStart); } public static boolean writeBit(int dbNumber, int byteIndex, int bitIndex, boolean value) { try { if (!ensureConnected()) { log.warn("[PLC写] DB{}.DBX{}.{} 失败:S7未连接", dbNumber, byteIndex, bitIndex); return false; } byte[] data = MesClient.s7Connector.read(DaveArea.DB, dbNumber, 1, byteIndex); byte oldByte = data[0]; boolean oldBit = (oldByte & (1 << bitIndex)) != 0; byte b = oldByte; if (value) { b = (byte)(b | (1 << bitIndex)); } else { b = (byte)(b & ~(1 << bitIndex)); } byte[] toWrite = new byte[]{b}; MesClient.s7Connector.write(DaveArea.DB, dbNumber, byteIndex, toWrite); log.info("[PLC写] DB{}.DBX{}.{} {} -> {} | DBB{}: 0x{} -> 0x{}", dbNumber, byteIndex, bitIndex, oldBit, value, byteIndex, String.format("%02X", oldByte & 0xFF), String.format("%02X", b & 0xFF)); if (dbNumber == DB9052 && byteIndex == 0 && bitIndex == 0) { log.info("[PLC写] DB9052.DBX0.0 允许启动信号已设为 {}", value); } return true; } catch (Exception e) { log.error("[PLC写] DB{}.DBX{}.{} 异常", dbNumber, byteIndex, bitIndex, e); markBroken("写位", e); return false; } } // 获取运行的程序名 public static String getProgramName(S7PLC s7PLC){ String programName = ""; try{ programName = s7PLC.readProgramName(); log.info("[PLC读] 当前程序名={}", programName); }catch (Exception e){ log.error("[PLC读] 程序名异常", e); programName = ""; } return programName; } }