|
|
@@ -10,6 +10,7 @@ import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
/**
|
|
|
* 框架气密工位 S7 读写封装(DB200)。
|
|
|
+ * 所有公开方法 synchronized,供轮询与调试页共用同一连接。
|
|
|
*/
|
|
|
public class QmS7Client {
|
|
|
|
|
|
@@ -20,7 +21,7 @@ public class QmS7Client {
|
|
|
this.config = config;
|
|
|
}
|
|
|
|
|
|
- public void updateHost(String host) {
|
|
|
+ public synchronized void updateHost(String host) {
|
|
|
if (host != null && !host.equals(config.host)) {
|
|
|
config.host = host.trim();
|
|
|
close();
|
|
|
@@ -34,13 +35,18 @@ public class QmS7Client {
|
|
|
if (config.host == null || config.host.isEmpty() || config.host.contains("xxx")) {
|
|
|
throw new IllegalStateException("未配置有效的 mes.qm.plc.host");
|
|
|
}
|
|
|
- connector = S7ConnectorFactory
|
|
|
- .buildTCPConnector()
|
|
|
- .withHost(config.host)
|
|
|
- .withRack(config.rack)
|
|
|
- .withSlot(config.slot)
|
|
|
- .withTimeout(3000)
|
|
|
- .build();
|
|
|
+ try {
|
|
|
+ connector = S7ConnectorFactory
|
|
|
+ .buildTCPConnector()
|
|
|
+ .withHost(config.host)
|
|
|
+ .withRack(config.rack)
|
|
|
+ .withSlot(config.slot)
|
|
|
+ .withTimeout(3000)
|
|
|
+ .build();
|
|
|
+ } catch (Exception e) {
|
|
|
+ connector = null;
|
|
|
+ throw new IllegalStateException(formatConnectError(e), e);
|
|
|
+ }
|
|
|
System.out.println("框架气密PLC已连接: " + config.host + " DB" + config.db);
|
|
|
}
|
|
|
|
|
|
@@ -54,13 +60,13 @@ public class QmS7Client {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public boolean readBit(int byteOffset, int bit) throws Exception {
|
|
|
+ public synchronized boolean readBit(int byteOffset, int bit) throws Exception {
|
|
|
ensureConnected();
|
|
|
byte[] data = connector.read(DaveArea.DB, config.db, 1, byteOffset);
|
|
|
return getBit(data[0], bit);
|
|
|
}
|
|
|
|
|
|
- public void writeBit(int byteOffset, int bit, boolean value) throws Exception {
|
|
|
+ public synchronized void writeBit(int byteOffset, int bit, boolean value) throws Exception {
|
|
|
ensureConnected();
|
|
|
byte[] data = connector.read(DaveArea.DB, config.db, 1, byteOffset);
|
|
|
data[0] = setBit(data[0], bit, value);
|
|
|
@@ -70,7 +76,7 @@ public class QmS7Client {
|
|
|
/**
|
|
|
* 校验通过:写允许进站=true,同时清扫码反馈=false(同字节一次写入)。
|
|
|
*/
|
|
|
- public void allowEntryAndClearScanFeedback() throws Exception {
|
|
|
+ public synchronized void allowEntryAndClearScanFeedback() throws Exception {
|
|
|
ensureConnected();
|
|
|
if (config.allowEntryByte == config.scanFeedbackByte) {
|
|
|
byte[] data = connector.read(DaveArea.DB, config.db, 1, config.allowEntryByte);
|
|
|
@@ -83,53 +89,128 @@ public class QmS7Client {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public void clearScanFeedback() throws Exception {
|
|
|
+ public synchronized void clearScanFeedback() throws Exception {
|
|
|
writeBit(config.scanFeedbackByte, config.scanFeedbackBit, false);
|
|
|
}
|
|
|
|
|
|
/** 上传成功后复位允许进站=0(不碰检测完成/扫码反馈) */
|
|
|
- public void clearAllowEntry() throws Exception {
|
|
|
+ public synchronized void clearAllowEntry() throws Exception {
|
|
|
writeBit(config.allowEntryByte, config.allowEntryBit, false);
|
|
|
}
|
|
|
|
|
|
/** @deprecated 检测完成由 PLC 厂家清,MES 不应调用 */
|
|
|
@Deprecated
|
|
|
- public void clearComplete() throws Exception {
|
|
|
+ public synchronized void clearComplete() throws Exception {
|
|
|
throw new UnsupportedOperationException("检测完成由 PLC 厂家清,MES 不写");
|
|
|
}
|
|
|
|
|
|
- public String readFrameCode() throws Exception {
|
|
|
+ public synchronized String readFrameCode() throws Exception {
|
|
|
ensureConnected();
|
|
|
int readLen = config.frameCodeLen + 2;
|
|
|
byte[] data = connector.read(DaveArea.DB, config.db, readLen, config.frameCodeOffset);
|
|
|
+ return parseSiemensString(data, config.frameCodeLen);
|
|
|
+ }
|
|
|
+
|
|
|
+ public synchronized float readFloat(int offset) throws Exception {
|
|
|
+ ensureConnected();
|
|
|
+ byte[] data = connector.read(DaveArea.DB, config.db, 4, offset);
|
|
|
+ return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getFloat();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调试页一次读齐 DB200 点位,缩短占锁时间、减少与轮询交错。
|
|
|
+ * 覆盖偏移 0~59(含 Real、判定、框架码、58.x 三位)。
|
|
|
+ */
|
|
|
+ public synchronized Db200Snapshot readDb200Snapshot() throws Exception {
|
|
|
+ ensureConnected();
|
|
|
+ byte[] raw = connector.read(DaveArea.DB, config.db, 60, 0);
|
|
|
+ Db200Snapshot s = new Db200Snapshot();
|
|
|
+ s.inflationTime = getFloat(raw, 0);
|
|
|
+ s.holdTime = getFloat(raw, 4);
|
|
|
+ s.testTime = getFloat(raw, 8);
|
|
|
+ s.inflationPressure = getFloat(raw, 12);
|
|
|
+ s.stablePressure = getFloat(raw, 16);
|
|
|
+ s.leak = getFloat(raw, 20);
|
|
|
+ s.judgePass = getBit(raw[24], 0);
|
|
|
+ int strLen = Math.min(config.frameCodeLen + 2, raw.length - 26);
|
|
|
+ byte[] strBytes = new byte[strLen];
|
|
|
+ System.arraycopy(raw, 26, strBytes, 0, strLen);
|
|
|
+ s.frameCode = parseSiemensString(strBytes, config.frameCodeLen);
|
|
|
+ s.complete = getBit(raw[58], config.completeBit);
|
|
|
+ s.allowEntry = getBit(raw[58], config.allowEntryBit);
|
|
|
+ s.scanFeedback = getBit(raw[58], config.scanFeedbackBit);
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String formatConnectError(Throwable e) {
|
|
|
+ Throwable t = e;
|
|
|
+ while (t != null) {
|
|
|
+ String msg = t.getMessage();
|
|
|
+ if (msg != null) {
|
|
|
+ String m = msg.trim();
|
|
|
+ if (m.isEmpty() || "constructor".equalsIgnoreCase(m)) {
|
|
|
+ return "PLC连接失败(连接数可能已满,请关闭西门子工具等多余连接)";
|
|
|
+ }
|
|
|
+ if (m.toLowerCase().contains("constructor")) {
|
|
|
+ return "PLC连接失败(连接数可能已满): " + m;
|
|
|
+ }
|
|
|
+ if (m.toLowerCase().contains("unexpected function code")) {
|
|
|
+ return "PLC通讯错乱(请勿同时多工具读写,已尝试重连): " + m;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ t = t.getCause();
|
|
|
+ }
|
|
|
+ return e == null ? "PLC未知错误" : String.valueOf(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean isProtocolGlitch(Throwable e) {
|
|
|
+ Throwable t = e;
|
|
|
+ while (t != null) {
|
|
|
+ String msg = t.getMessage();
|
|
|
+ if (msg != null) {
|
|
|
+ String lower = msg.toLowerCase();
|
|
|
+ if (lower.contains("unexpected function code") || "constructor".equalsIgnoreCase(msg.trim())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ t = t.getCause();
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static float getFloat(byte[] raw, int offset) {
|
|
|
+ return ByteBuffer.wrap(raw, offset, 4).order(ByteOrder.BIG_ENDIAN).getFloat();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String parseSiemensString(byte[] data, int maxLen) {
|
|
|
+ if (data == null || data.length < 2) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
int curLen = data[1] & 0xFF;
|
|
|
if (curLen < 0) {
|
|
|
curLen = 0;
|
|
|
}
|
|
|
- if (curLen > config.frameCodeLen) {
|
|
|
- curLen = config.frameCodeLen;
|
|
|
+ if (curLen > maxLen) {
|
|
|
+ curLen = maxLen;
|
|
|
+ }
|
|
|
+ if (curLen > data.length - 2) {
|
|
|
+ curLen = data.length - 2;
|
|
|
}
|
|
|
String sn = new String(data, 2, curLen, StandardCharsets.US_ASCII).trim();
|
|
|
- if (sn.isEmpty()) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- for (int i = 2; i < data.length; i++) {
|
|
|
- char c = (char) (data[i] & 0xFF);
|
|
|
- if (c == 0) {
|
|
|
- break;
|
|
|
- }
|
|
|
- if (c >= 32 && c < 127) {
|
|
|
- sb.append(c);
|
|
|
- }
|
|
|
+ if (!sn.isEmpty()) {
|
|
|
+ return sn;
|
|
|
+ }
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 2; i < data.length; i++) {
|
|
|
+ char c = (char) (data[i] & 0xFF);
|
|
|
+ if (c == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (c >= 32 && c < 127) {
|
|
|
+ sb.append(c);
|
|
|
}
|
|
|
- sn = sb.toString().trim();
|
|
|
}
|
|
|
- return sn;
|
|
|
- }
|
|
|
-
|
|
|
- public float readFloat(int offset) throws Exception {
|
|
|
- ensureConnected();
|
|
|
- byte[] data = connector.read(DaveArea.DB, config.db, 4, offset);
|
|
|
- return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getFloat();
|
|
|
+ return sb.toString().trim();
|
|
|
}
|
|
|
|
|
|
public static boolean getBit(byte b, int n) {
|
|
|
@@ -142,4 +223,18 @@ public class QmS7Client {
|
|
|
}
|
|
|
return (byte) (b & ~(1 << n));
|
|
|
}
|
|
|
+
|
|
|
+ public static class Db200Snapshot {
|
|
|
+ public float inflationTime;
|
|
|
+ public float holdTime;
|
|
|
+ public float testTime;
|
|
|
+ public float inflationPressure;
|
|
|
+ public float stablePressure;
|
|
|
+ public float leak;
|
|
|
+ public boolean judgePass;
|
|
|
+ public String frameCode;
|
|
|
+ public boolean complete;
|
|
|
+ public boolean allowEntry;
|
|
|
+ public boolean scanFeedback;
|
|
|
+ }
|
|
|
}
|