diff --git a/src/main/java/a8k/OS.java b/src/main/java/a8k/OS.java index 5624af6..787c53e 100644 --- a/src/main/java/a8k/OS.java +++ b/src/main/java/a8k/OS.java @@ -37,10 +37,10 @@ public class OS { } public static void forceSleep(Integer mills) { - Long start = System.currentTimeMillis(); + Long start = getMonotonicClockTimestamp(); long end = start + mills; - while (System.currentTimeMillis() < end) { - int left = (int) (end - System.currentTimeMillis()); + while (getMonotonicClockTimestamp() < end) { + int left = (int) (end - getMonotonicClockTimestamp()); threadSleep(left); } } @@ -134,6 +134,14 @@ public class OS { return System.currentTimeMillis(); } + public static long getMonotonicClockTimestamp() { + return System.nanoTime() / 1_000_000; // 转换为毫秒 + } + + public static boolean monotonicClockHasElapsed(long startTimestamp, long durationMillis) { + return (getMonotonicClockTimestamp() - startTimestamp) >= durationMillis; + } + public static void main(String[] args) { // System.out.println("LOCAL IP: " + OS.getLocalIp()); // System.out.println("LOCAL IP: " + OS.readSN()); diff --git a/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusConnection.java b/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusConnection.java index bc9e71c..64ffc94 100644 --- a/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusConnection.java +++ b/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusConnection.java @@ -50,9 +50,9 @@ public class A8kCanBusConnection extends WebSocketClient { public A8kPacket getReceipt(long overtime) { - long end = System.currentTimeMillis() + overtime; + long end = OS.getMonotonicClockTimestamp() + overtime; A8kPacket packet = null; - while (System.currentTimeMillis() < end) { + while (OS.getMonotonicClockTimestamp() < end) { try { packet = receiptQueue.poll(2, TimeUnit.MILLISECONDS); } catch (InterruptedException ignored) { diff --git a/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusService.java b/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusService.java index 0ae21ef..9d5cb35 100644 --- a/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusService.java +++ b/src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusService.java @@ -115,7 +115,7 @@ public class A8kCanBusService { } public void waitForMod(MId mid, Integer acitionOvertime) throws AppException { - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); CmdId action = connection.getLastTxCmd(mid); do { ModuleStatus statu = moduleGetStatus(mid); @@ -125,7 +125,7 @@ public class A8kCanBusService { log.error("{} waitting for action {} , catch error {}, defail ecode {}", mid, action, moduleGetError(mid), moduleGetDetailError(mid)); throw AppException.of(new AEHardwareError(moduleGetError(mid), mid, action)); } - long now = System.currentTimeMillis(); + long now = OS.getMonotonicClockTimestamp(); if (now - startedAt > acitionOvertime) { log.error("{} waitting for action {} overtime({})", mid, action, acitionOvertime); moduleStopNoException(mid); diff --git a/src/main/java/a8k/app/channel/iflytophald/driver/CodeScanerDriver.java b/src/main/java/a8k/app/channel/iflytophald/driver/CodeScanerDriver.java index c5f83b7..e06e7ce 100644 --- a/src/main/java/a8k/app/channel/iflytophald/driver/CodeScanerDriver.java +++ b/src/main/java/a8k/app/channel/iflytophald/driver/CodeScanerDriver.java @@ -70,11 +70,11 @@ public class CodeScanerDriver { } private String codeScanerWaittingForResult(MId mid, Integer acitionOvertime) throws AppException { - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); do { if (codeScanerResultIsReady(mid)) break; - long now = System.currentTimeMillis(); + long now = OS.getMonotonicClockTimestamp(); if (now - startedAt > acitionOvertime) { codeScanerStopScan(mid); return null; diff --git a/src/main/java/a8k/app/channel/iflytophald/driver/MiniServoDriver.java b/src/main/java/a8k/app/channel/iflytophald/driver/MiniServoDriver.java index 553143b..8a5ad34 100644 --- a/src/main/java/a8k/app/channel/iflytophald/driver/MiniServoDriver.java +++ b/src/main/java/a8k/app/channel/iflytophald/driver/MiniServoDriver.java @@ -160,13 +160,13 @@ public class MiniServoDriver { } public void miniServoWaitIsNotMove(MiniServoMId id, int acitionOvertime) throws AppException { - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); do { var isMove = canBus.moduleGetReg(id.mid, RegIndex.kreg_mini_servo_is_move); if (isMove != 0) { break; } - long now = System.currentTimeMillis(); + long now = OS.getMonotonicClockTimestamp(); if (now - startedAt > acitionOvertime) { throw AppException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, id.mid, null)); } diff --git a/src/main/java/a8k/app/channel/iflytophald/driver/OptModuleDriver.java b/src/main/java/a8k/app/channel/iflytophald/driver/OptModuleDriver.java index ee8d96a..0800083 100644 --- a/src/main/java/a8k/app/channel/iflytophald/driver/OptModuleDriver.java +++ b/src/main/java/a8k/app/channel/iflytophald/driver/OptModuleDriver.java @@ -39,7 +39,7 @@ public class OptModuleDriver { void waittingForOptFinish(Integer overtime) throws AppException { - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); do { var packet = A8kPacketFactory.buildCMDPacket(MId.OptMod.toInt(), CmdId.module_get_status.toInt(), ZList.of()); @@ -59,7 +59,7 @@ public class OptModuleDriver { throw AppException.of(new AEHardwareError(canBus.moduleGetError(MId.OptMod), MId.OptMod, CmdId.a8k_opt_v2_t_start_scan)); } - if (System.currentTimeMillis() - startedAt > overtime) { + if (OS.getMonotonicClockTimestamp() - startedAt > overtime) { throw AppException.of(new AEHardwareError(A8kEcode.LOW_ERROR_ACTION_OVERTIME, MId.OptMod, CmdId.a8k_opt_v2_t_start_scan)); } OS.forceSleep(100); @@ -72,20 +72,20 @@ public class OptModuleDriver { log.info("OptTStartScan scanDirection:{} lasterGain:{} scanGain:{}", scanDirection, lasterGain, scanGain); canBus.callcmd(MId.OptMod, CmdId.a8k_opt_v2_t_start_scan, scanDirection.getInteger(), lasterGain, scanGain); //TODO: 2024-10-26 zhaohe,修改底层,光学结束上报一条完成事件 - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); OS.forceSleep(100); waittingForOptFinish(10 * 1000); - log.info("opt t scan use {}s", (System.currentTimeMillis() - startedAt) / 1000.0); + log.info("opt t scan use {}s", (OS.getMonotonicClockTimestamp() - startedAt) / 1000.0); } public void optFStartScan(OptScanDirection scanDirection, Integer lasterGain, Integer scanGain) throws AppException { log.info("OptFStartScan scanDirection:{} lasterGain:{} scanGain:{}", scanDirection, lasterGain, scanGain); canBus.callcmd(MId.OptMod, CmdId.a8k_opt_v2_f_start_scan, scanDirection.getInteger(), lasterGain, scanGain); //TODO: 2024-10-26 zhaohe,修改底层,光学结束上报一条完成事件 - long startedAt = System.currentTimeMillis(); + long startedAt = OS.getMonotonicClockTimestamp(); OS.forceSleep(100); waittingForOptFinish(10 * 1000); - log.info("opt f scan use {}s", (System.currentTimeMillis() - startedAt) / 1000.0); + log.info("opt f scan use {}s", (OS.getMonotonicClockTimestamp() - startedAt) / 1000.0); } public Integer[] optReadRaw() throws AppException { diff --git a/src/main/java/a8k/app/channel/net/BiLisDoubleTrackTcpClient.java b/src/main/java/a8k/app/channel/net/BiLisDoubleTrackTcpClient.java index 275ca24..b1a3333 100644 --- a/src/main/java/a8k/app/channel/net/BiLisDoubleTrackTcpClient.java +++ b/src/main/java/a8k/app/channel/net/BiLisDoubleTrackTcpClient.java @@ -28,14 +28,20 @@ public class BiLisDoubleTrackTcpClient extends ChannelInboundHandlerAdapter { void onDataReceived(byte[] data); } - private final EventLoopGroup group = NettyUtils.buildEventLoopGroup("BiLisDoubleTrackTcpClient", 1); - private final Bootstrap bootstrap = new Bootstrap(); - private String serverIp; - private Integer port; - private volatile Channel channel; - private volatile boolean isShuttingDown = false; - private final ByteBuf rxBuffer = Unpooled.buffer(rxBufferSize); // 接收数据缓冲区,初始大小为1024字节 - private OnDataReceivedListener onDataReceivedListener; + @FunctionalInterface + public interface OnLostConnectionListener { + void onLostConnection(); + } + + private final EventLoopGroup group = NettyUtils.buildEventLoopGroup("BiLisDoubleTrackTcpClient", 1); + private final Bootstrap bootstrap = new Bootstrap(); + private String serverIp; + private Integer port; + private volatile Channel channel; + private volatile boolean isShuttingDown = false; + private final ByteBuf rxBuffer = Unpooled.buffer(rxBufferSize); // 接收数据缓冲区,初始大小为1024字节 + private OnDataReceivedListener onDataReceivedListener; + private OnLostConnectionListener onLostConnectionListener; // // External API @@ -115,6 +121,10 @@ public class BiLisDoubleTrackTcpClient extends ChannelInboundHandlerAdapter { this.onDataReceivedListener = listener; } + public synchronized void regOnLostConnectionListener(OnLostConnectionListener listener) { + this.onLostConnectionListener = listener; + } + public synchronized boolean isConnected() { return channel != null && channel.isActive(); } diff --git a/src/main/java/a8k/app/controler/api/v1/app/setting/LISSettingController.java b/src/main/java/a8k/app/controler/api/v1/app/setting/LISSettingController.java index 6491e3d..c92abb0 100644 --- a/src/main/java/a8k/app/controler/api/v1/app/setting/LISSettingController.java +++ b/src/main/java/a8k/app/controler/api/v1/app/setting/LISSettingController.java @@ -57,66 +57,4 @@ public class LISSettingController { return ApiRet.success(); } - - @Operation(summary = "设置LIS类型") - @PostMapping("/setLISType") - public ApiRet setLISType(LISDirectionTypeEnum val) { - appSettingsMgrService.setLISType(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LIS协议") - @PostMapping("/setLISProtocol") - public ApiRet setLISProtocol(LISProtocolEnum val) { - appSettingsMgrService.setLISProtocol(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LIS接口") - @PostMapping("/setLIFIf") - public ApiRet setLIFIf(LISIFType val) { - appSettingsMgrService.setLIFIf(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LIS是否自动上报报告") - @PostMapping("/setLISAutoExport") - public ApiRet setLISAutoExport(Boolean val) { - appSettingsMgrService.setLISAutoExport(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LIS串口波特率") - @PostMapping("/setLISSerialBaudrate") - public ApiRet setLISSerialBaudrate(LISSerialBaudrateType val) { - appSettingsMgrService.setLISSerialBaudrate(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LISIP") - @PostMapping("/setLISNetIp") - public ApiRet setLISNetIp(String val) { - appSettingsMgrService.setLISNetIp(val); - return ApiRet.success(); - - } - - - @Operation(summary = "设置LIS端口") - @PostMapping("/setLISNetPort") - public ApiRet setLISNetPort(Integer val) { - appSettingsMgrService.setLISNetPort(val); - return ApiRet.success(); - - } } diff --git a/src/main/java/a8k/app/controler/api/v1/app/ws/AppWebSocketEndpointMgr.java b/src/main/java/a8k/app/controler/api/v1/app/ws/AppWebSocketEndpointMgr.java index afba9ce..71814e4 100644 --- a/src/main/java/a8k/app/controler/api/v1/app/ws/AppWebSocketEndpointMgr.java +++ b/src/main/java/a8k/app/controler/api/v1/app/ws/AppWebSocketEndpointMgr.java @@ -39,7 +39,7 @@ public class AppWebSocketEndpointMgr { public String messageType = "Report"; public String dataType; public Object data; - public Integer timestamp = (int) (System.currentTimeMillis() / 1000); + public Long timestamp = (System.currentTimeMillis()); public Report(String dataType, Object object) { this.dataType = dataType; @@ -183,32 +183,33 @@ public class AppWebSocketEndpointMgr { reportState("DeviceContext", deviceContext); } - @Scheduled(fixedDelay = 100) + @Scheduled(fixedDelay = 50) public void quickReportStateSchedule() { quickReportStateSchedule(false); } public void quickReportStateSchedule(boolean force) { - + reportState("ConsumablesState", consumablesMgrService.getStateVersion(), force, + consumablesMgrService::getState); reportState("EngineerModeState", engineerModeStateMgrService.getVersion(), force, engineerModeStateMgrService::getState); - reportState("TubeHolderState", tubeStateMgrService.getVersion(), force, tubeStateMgrService::getTubeHolderState); - reportState("EmergencyPosState", tubeStateMgrService.getVersion(), force, tubeStateMgrService::getEmergencyPosRunState); - - reportState("OptScanModuleState", optScanModuleStateMgr.getStateVersion(), force, - optScanModuleStateMgr::getOptScanModule); - reportState("DeviceWorkState", deviceWorkStateMgrService.getVersion(), force, deviceWorkStateMgrService::getDeviceWorkState); + reportState("OptScanModuleState", optScanModuleStateMgr.getStateVersion(), force, + optScanModuleStateMgr::getOptScanModule); reportState("TubeHolderSetting", tubeHolderSettingMgrService.getTubeHolderSettingVersion(), force, tubeHolderSettingMgrService::getTubeHolderSettings); + reportState("AppFlagStateList", appFlagStateMgrService.getStateVersion(), force, appFlagStateMgrService::getFlagState); - reportState("ConsumablesState", consumablesMgrService.getStateVersion(), force, - consumablesMgrService::getState); reportState("MessageBoxState", frontEndMessageBoxAndEventMgr.getMessageBoxStateVersion(), force, frontEndMessageBoxAndEventMgr::getMessageBoxState); + + + reportState("TubeHolderState", tubeStateMgrService.getVersion(), force, tubeStateMgrService::getTubeHolderState); + reportState("EmergencyPosState", tubeStateMgrService.getVersion(), force, tubeStateMgrService::getEmergencyPosRunState); + reportState("PreReactionPosGroupState-CG1", "PreReactionPosGroupState", preReactionStateMgr.getPreReactionGridGroupVersion(ConsumableGroup.CG1), force, () -> preReactionStateMgr.getPreReactionGridGroupState(ConsumableGroup.CG1)); reportState("PreReactionPosGroupState-CG2", "PreReactionPosGroupState", preReactionStateMgr.getPreReactionGridGroupVersion(ConsumableGroup.CG2), force, diff --git a/src/main/java/a8k/app/factory/BiLisDoubleTrackFrameFactory.java b/src/main/java/a8k/app/factory/BiLisDoubleTrackFrameFactory.java index 1c4f93a..d674a85 100644 --- a/src/main/java/a8k/app/factory/BiLisDoubleTrackFrameFactory.java +++ b/src/main/java/a8k/app/factory/BiLisDoubleTrackFrameFactory.java @@ -6,6 +6,9 @@ import a8k.app.type.lisprotocol.BiLisDoubleTrackFrame; import a8k.app.type.lisprotocol.BiLisDoubleTrackFrameBuildContext; import a8k.app.type.lisprotocol.BiLisFrameParseErrorCode; import a8k.app.type.lisprotocol.BiLisFrameParseException; +import a8k.app.type.lisprotocol.subframe.OFrame; +import a8k.app.type.lisprotocol.subframe.QFrame; +import a8k.app.type.lisprotocol.subframe.RFrame; import a8k.app.utils.ByteArrayUtils; import a8k.app.utils.ZDateUtils; import io.netty.buffer.ByteBuf; @@ -21,8 +24,8 @@ public class BiLisDoubleTrackFrameFactory { BiLisDoubleTrackFrameBuildContext buildContext = new BiLisDoubleTrackFrameBuildContext(); - BiLisDoubleTrackFrame frame = new BiLisDoubleTrackFrame(); - BiLisDoubleTrackFrame.RFrame rFrame = new BiLisDoubleTrackFrame.RFrame(); + BiLisDoubleTrackFrame frame = new BiLisDoubleTrackFrame(); + RFrame rFrame = new RFrame(); Double rawResultVal = reactionRecord.results.get(subProjIndex).result; var converter = reactionRecord.results.get(subProjIndex).resultConverters.get(0); @@ -35,7 +38,6 @@ public class BiLisDoubleTrackFrameFactory { case FECES -> "3"; // 尿 }; - rFrame.type = "R"; // 信息类型 R-1 rFrame.deviceName = AppConstant.deviceTypeName; rFrame.sampleId = reactionRecord.sampleUserid;//样本ID,条形码 R-3 @@ -81,8 +83,8 @@ public class BiLisDoubleTrackFrameFactory { static public BiLisDoubleTrackFrameBuildContext createQFrameBytes(String deviceName, String barcode) { BiLisDoubleTrackFrameBuildContext context = new BiLisDoubleTrackFrameBuildContext(); - BiLisDoubleTrackFrame frame = new BiLisDoubleTrackFrame(); - BiLisDoubleTrackFrame.QFrame qFrame = new BiLisDoubleTrackFrame.QFrame(); + BiLisDoubleTrackFrame frame = new BiLisDoubleTrackFrame(); + QFrame qFrame = new QFrame(); frame.frameType = BiLisDoubleTrackFrame.STX; qFrame.type = "Q"; // 请求类型 qFrame.deviceName = deviceName; @@ -94,37 +96,40 @@ public class BiLisDoubleTrackFrameFactory { return context; } + static public int minRxFrameLength() { + return 1/*STX*/ + 1/*FN*/ + 1/*ETX*/ + 2/*C1,C2*/ + 2; + } - static public BiLisDoubleTrackFrame createOFrame(byte[] rxData) throws BiLisFrameParseException { + static public void checkRxFrame(byte[] rxData) throws BiLisFrameParseException { /* * Frame Structure: * STX,FN,Content,ETX,C1,C2,CR,LF */ - int minLength = 1/*STX*/ + 1/*FN*/ + 1/*ETX*/ + 2/*C1,C2*/ + 2/*CR,LF*/; - if (rxData.length <= minLength) { + if (rxData.length <= minRxFrameLength()) { log.warn("Received frame is too short: {}", rxData.length); throw new BiLisFrameParseException(BiLisFrameParseErrorCode.FrameLengthTooShort, "rx packet length is too short: " + rxData.length); } byte stx = rxData[0]; char fn = (char) rxData[1]; byte etx = rxData[rxData.length - 5]; - byte c1 = rxData[rxData.length - 4]; - byte c2 = rxData[rxData.length - 3]; + char c1 = (char) rxData[rxData.length - 4]; + char c2 = (char) rxData[rxData.length - 3]; byte cr = rxData[rxData.length - 2]; byte lf = rxData[rxData.length - 1]; - String cmdContent = new String(rxData, 2, rxData.length - minLength); // 从第3个字节开始到倒数第5个字节之前 + String cmdContent = new String(rxData, 2, rxData.length - minRxFrameLength()); // 从第3个字节开始到倒数第5个字节之前 if (stx != BiLisDoubleTrackFrame.STX) { log.warn("Received frame does not start with STX: {}", stx); throw new BiLisFrameParseException(BiLisFrameParseErrorCode.NotSupportedCommand, "rx not support packet type: " + stx); } - if (cr != BiLisDoubleTrackFrame.LF || - lf != BiLisDoubleTrackFrame.CR) { + if (lf != BiLisDoubleTrackFrame.LF || + cr != BiLisDoubleTrackFrame.CR) { log.warn("received frame with invalid end: LF={}, CR={}", rxData[rxData.length - 1], rxData[rxData.length - 2]); - throw new BiLisFrameParseException(BiLisFrameParseErrorCode.InvalidFrameFormat, "rx not end with LF and CR: " + rxData[rxData.length - 1] + ", " + rxData[rxData.length - 2]); + throw new BiLisFrameParseException(BiLisFrameParseErrorCode.InvalidFrameFormat, String.format( + "rx packet end is invalid: LF=%x CR=%x", rxData[rxData.length - 1], rxData[rxData.length - 2])); } if (fn != BiLisDoubleTrackFrame.FN) { @@ -138,8 +143,9 @@ public class BiLisDoubleTrackFrameFactory { } char[] checkcode = computeChecksum(cmdContent); - if (c1 != checkcode[0] || c2 != checkcode[1]) { - log.warn("Received frame has invalid checksum: C1={}, C2={}", c1, c2); + + if (!isCheckcodeEq(c1,c2,checkcode)) { + log.warn("Received frame has invalid checksum: {}, {} !={} {}", c1, c2, checkcode[0], checkcode[1]); throw new BiLisFrameParseException(BiLisFrameParseErrorCode.ChecksumError, "rx checksum error: " + c1 + ", " + c2); } @@ -148,15 +154,16 @@ public class BiLisDoubleTrackFrameFactory { log.warn("Received frame content is too short: {}", cmdContent.length()); throw new BiLisFrameParseException(BiLisFrameParseErrorCode.FrameLengthTooShort, "rx packet content is too short: " + cmdContent.length()); } - BiLisDoubleTrackFrame frame = new BiLisDoubleTrackFrame(); - frame.frameType = BiLisDoubleTrackFrame.STX; + } - if (cmdContent.charAt(0) == 'O') { - BiLisDoubleTrackFrame.OFrame oframe = new BiLisDoubleTrackFrame.OFrame(); - frame.frameContent = oframe; + static public OFrame createOFrame(byte[] rxData) throws BiLisFrameParseException { + checkRxFrame(rxData); - String[] parts = cmdContent.split("\\|"); + String cmdContent = new String(rxData, 2, rxData.length - minRxFrameLength()); // 从第3个字节开始到倒数第5个字节之前 + if (cmdContent.charAt(0) == 'O') { + OFrame oframe = new OFrame(); + String[] parts = cmdContent.split("\\|"); oframe.type = strArraySafeGet(parts, 0);//O1 oframe.deviceName = strArraySafeGet(parts, 1);//O2 oframe.sampleId = strArraySafeGet(parts, 2);//O3 @@ -169,7 +176,7 @@ public class BiLisDoubleTrackFrameFactory { } oframe.sampleType = strArraySafeGet(parts, 5).isEmpty() ? null : Integer.parseInt(parts[5]);//O6 oframe.samplingDate = strArraySafeGet(parts, 7);//O8 - return frame; + return oframe; } else { throw new BiLisFrameParseException(BiLisFrameParseErrorCode.NotSupportedCommand, "rx not support packet type: " + cmdContent.charAt(0)); } @@ -186,8 +193,8 @@ public class BiLisDoubleTrackFrameFactory { buildContext.buildSuccess = false; } - if ((frame.frameContent instanceof BiLisDoubleTrackFrame.QFrame - || frame.frameContent instanceof BiLisDoubleTrackFrame.RFrame + if ((frame.frameContent instanceof QFrame + || frame.frameContent instanceof RFrame )) { String content = serializeFrameContent(frame.frameContent); buildContext.frameContentStr = content; @@ -272,6 +279,16 @@ public class BiLisDoubleTrackFrameFactory { return intToHexChars(checksum); } + public static boolean isCheckcodeEq(char c1, char c2, char[] computeCheckcode) { + char _c1 = Character.toLowerCase(c1); + char _c2 = Character.toLowerCase(c2); + + char _computeCheckcode1 = Character.toLowerCase(computeCheckcode[0]); + char _computeCheckcode2 = Character.toLowerCase(computeCheckcode[1]); + + return _c1 == _computeCheckcode1 && _c2 == _computeCheckcode2; + } + public static char[] intToHexChars(int value) { // 将整数转换为两个十六进制字符 ret (0-9,a-f) char[] hexChars = new char[2]; @@ -286,6 +303,7 @@ public class BiLisDoubleTrackFrameFactory { return hexChars; } + static public String strArraySafeGet(String[] list, int index) { if (list == null || index < 0 || index >= list.length) { return ""; @@ -295,7 +313,7 @@ public class BiLisDoubleTrackFrameFactory { } public static void main(String[] args) { - log.info("intToHexChars(0x1A) = {}", new String(intToHexChars(0x1A))); + // log.info("intToHexChars(0x1A) = {}", new String(intToHexChars(0x1A))); } } diff --git a/src/main/java/a8k/app/service/lis/BiLisDoubleTrackChannel.java b/src/main/java/a8k/app/service/lis/BiLisDoubleTrackChannel.java index d96585b..37aecdd 100644 --- a/src/main/java/a8k/app/service/lis/BiLisDoubleTrackChannel.java +++ b/src/main/java/a8k/app/service/lis/BiLisDoubleTrackChannel.java @@ -1,15 +1,18 @@ package a8k.app.service.lis; +import a8k.OS; import a8k.app.channel.iflytophald.channel.LisUartChannel; import a8k.app.channel.net.BiLisDoubleTrackTcpClient; import a8k.app.constant.AppConstant; +import a8k.app.dao.type.db.ReactionRecord; import a8k.app.factory.BiLisDoubleTrackFrameFactory; import a8k.app.type.lisprotocol.BiLisDoubleTrackFrame; +import a8k.app.type.lisprotocol.BiLisFrameParseException; +import a8k.app.type.lisprotocol.subframe.OFrame; import a8k.app.utils.ByteArrayUtils; import a8k.app.utils.ThreadUtils; import a8k.app.utils.ZByteRxBuffer; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import a8k.app.utils.ZJsonHelper; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -101,7 +104,6 @@ public class BiLisDoubleTrackChannel { if (!isChannelReady()) return false; - rxBuffer.clear(); txBytes(new byte[]{BiLisDoubleTrackFrame.ENQ}); boolean rxAckOrNak = ThreadUtils.waitingForSomeCondition("BiLisDoubleTrackChannel.rx", 30, 2000, @@ -112,7 +114,7 @@ public class BiLisDoubleTrackChannel { return rxAckOrNak; } - synchronized BiLisDoubleTrackFrame requestSampleInfo(String barcode) { + public synchronized OFrame requestSampleInfo(String barcode) { /* * 请求样本信息协议 * TX: FN Q|A5000P|^1111 C1 C2 @@ -124,11 +126,27 @@ public class BiLisDoubleTrackChannel { var cxt = BiLisDoubleTrackFrameFactory.createQFrameBytes(AppConstant.deviceTypeName, barcode); txBytes(cxt.frameBytes); - boolean rxAckOrNak = ThreadUtils.waitingForSomeCondition("BiLisDoubleTrackChannel", 30, 2000, + ThreadUtils.waitingForSomeCondition("BiLisDoubleTrackChannel", 30, 2000, () -> rxBuffer.readableBytes() != 0); byte[] rxData = rxBuffer.cpy(); - log.info("RX: {}", ByteArrayUtils.toByteString(rxData)); + if (rxData.length == 1 && rxData[0] == BiLisDoubleTrackFrame.NAK) { + txBytes(new byte[]{BiLisDoubleTrackFrame.EOT}); + return null; + } else if (rxData.length == 0) { + txBytes(new byte[]{BiLisDoubleTrackFrame.EOT}); + return null; + } + OFrame oFrame; + try { + oFrame = BiLisDoubleTrackFrameFactory.createOFrame(rxData); + } catch (BiLisFrameParseException e) { + log.error("BiLisDoubleTrackChannel: 解析样本信息失败: {}", ByteArrayUtils.toByteString(rxData), e); + txBytes(new byte[]{BiLisDoubleTrackFrame.EOT}); + return null; + } + + log.info("BiLisDoubleTrackChannel: 收到样本信息: {}", ZJsonHelper.objectToJson(oFrame)); txBytes(new byte[]{BiLisDoubleTrackFrame.ACK}); @@ -136,7 +154,61 @@ public class BiLisDoubleTrackChannel { ThreadUtils.waitingForSomeCondition("BiLisDoubleTrackChannel", 30, 2000, () -> rxBuffer.readableBytes() != 0); - return null; + return oFrame; + } + + public boolean reportReactionReport(ReactionRecord reactionRecord) { + if (reactionRecord == null) { + return false; + } + + boolean success = false; + + for (int i = 0; i < reactionRecord.getSubProjNum(); i++) { + var cxt = BiLisDoubleTrackFrameFactory.createRFrame(reactionRecord, i); + if (!cxt.buildSuccess) { + log.error("BiLisDoubleTrackChannel: 发送反应报告失败: {}", ZJsonHelper.objectToJson(cxt)); + break; + } + success = reportRFrameAutoReSend(cxt.frameBytes); + if (!success) { + break; + } + } + return success; + } + + private boolean reportRFrameAutoReSend(byte[] rFrame) { + for (int i = 0; i < 3; i++) { + if (!connectedState) { + log.warn("BiLisDoubleTrackChannel: 连接状态异常,无法发送反应报告"); + return false; + } + + if (reportRFrame(rFrame)) { + return true; + } else { + log.warn("BiLisDoubleTrackChannel: 发送反应报告失败,尝试重发第{}次", i + 1); + } + + } + return false; + } + + private boolean reportRFrame(byte[] rFrame) { + boolean transmitSuccess = false; + + txBytes(rFrame); + + ThreadUtils.waitingForSomeCondition("BiLisDoubleTrackChannel", 30, 2000, + () -> rxBuffer.readableBytes() != 0); + + if (rxBuffer.readableBytes() == 1 || rxBuffer.getByte(0) == BiLisDoubleTrackFrame.ACK) { + transmitSuccess = true; + } + txBytes(new byte[]{BiLisDoubleTrackFrame.EOT}); + + return transmitSuccess; } @@ -173,20 +245,35 @@ public class BiLisDoubleTrackChannel { } private void monitorThread() { + long lastPingTimestamp = OS.getMonotonicClockTimestamp(); while (!isShuttingDown) { - if (connectedState) { - ThreadUtils.trySleep(1000); - } else { - ThreadUtils.trySleep(1000); - } - boolean state = ping(); - if (state) { - connectedState = true; - } else { - connectedState = false; - log.warn("BiLisDoubleTrackChannel: Ping失败"); + try { + if (connectedState) { + if (channelType.equals(ChannelType.TCP) && !biLisDoubleTrackTcpClient.isConnected()) { + connectedState = false; + continue; + } + + if (OS.monotonicClockHasElapsed(lastPingTimestamp, PING_PERIOD)) { + lastPingTimestamp = OS.getMonotonicClockTimestamp(); + if (!ping()) { + connectedState = false; + continue; + } + } + + } else { + if (channelType.equals(ChannelType.TCP) && !biLisDoubleTrackTcpClient.isConnected()) { + continue; + } + + if (ping()) { + connectedState = true; + } + } + } catch (Exception ignored) { } - + ThreadUtils.trySleep(500); } } diff --git a/src/main/java/a8k/app/type/lisprotocol/BiLisDoubleTrackFrame.java b/src/main/java/a8k/app/type/lisprotocol/BiLisDoubleTrackFrame.java index 9ad346f..af265d2 100644 --- a/src/main/java/a8k/app/type/lisprotocol/BiLisDoubleTrackFrame.java +++ b/src/main/java/a8k/app/type/lisprotocol/BiLisDoubleTrackFrame.java @@ -21,95 +21,6 @@ public class BiLisDoubleTrackFrame { public static final Integer ACKOvertimeMS = 2000; - public static class QFrame { - public String type = "Q"; // 请求类型 - public String deviceName = "A8000"; - public String barcode = "";//格式示例 ^abcdef1234567 - } - - public static class OFrame { - public String type = "O"; // 请求类型 - public String deviceName = "A8000"; - public String sampleId = "";// - public List projList; - public Integer sampleType;//全血0,血清血浆1,尿2,粪便3,唾液4 - public String samplingDate;//采样时间 YYYYMMDDhhmmss - } - - public static class RFrame { - // DEMO - //Example: (注:每次只发送一条信息) - //R|A5000p|1234567890|^hsCRP^^#|18.57|mg/L||||F||||20211209100114| - //R|A10|123456789|^CRP^^#|76|mg/L|0.5-200||L_4^_02|F||||20141201144906||0| - //R|A10||^COVID-19 Ag^^%||||Positive|R_1^_01|F||||20210428153944||4| - // - //二联卡 (单项-PCT/CRP): - //R|A10|123456789|^PCT/CRP^CRP^#|46|mg/L|0.5-200||L_1^_03|F||||20141201144906||0| - //R|A10|123456789|^PCT/CRP^PCT^#|15|ng/mL|0.1-100||L_1^_03|F||||20141201144906||0| - // - //三联卡 (单项-TnI/CK-MB/Myo): - //R|A5000p|Aa123456|^TnI/CK-MB/Myo^TnI^#|3.84|ng/mL||||F||||20211209101158| - //R|A5000p|Aa123456|^TnI/CK-MB/Myo^CK-MB^#|46.39|ng/mL||||F||||20211209101158| - //R|A5000p|Aa123456|^TnI/CK-MB/Myo^Myoglobin^#|45.25|ng/mL||||F||||20211209101158| - // - //COVID-19 Ab: - //R|A10|123456789|^COVID-19 Ab^IgG^@||||Positive|L_1^|F||||20141201144906||1| - //R|A10|123456789|^COVID-19 Ab^IgM^@||||Negative|L_1^ |F||||20141201144906||1| - public String type = "R"; // 信息类型 R-1 - public String deviceName = "A8000"; //设备的名称 R-2 - public String sampleId = "";//样本ID,条形码 R-3 - public String projInfo = "";//R-4 项目名+子项目名+结果类型(定性,定量,半定性) - public String resultVal = "";//R-5 - public String resultUnit = "";//结果单位 R-6 - public String refRange = "";//参考范围,也可以为空 R-7 - public String qualitativeResult = "";//定性结果,如果是定量结果,则该值为空 R-8 - public String samplePosInfo = "";//通道号+试管号 R9 - public String resultType = "F";//always F R10 - public String pad0 = "";// 填充位,如果没有填充则为空 R11 - public String startIncubationTime = "";//开始孵育时间 R12 YYYYMMDDhhmmsss - public String pad1 = "";// 填充位,如果没有填充则为空 R13 - public String endIncubationTime = "";//结束孵育时间 R14 YYYYMMDDhhmmsss - public String pid = "";// 用户输入的ID R15 - public String sampleType = "1";//样本类型 - } - public byte frameType; //STX,ACK,EOF public Object frameContent; - - // @Override public String serialized() { - // // Q|A5000|^987654321 - // return type + "|" + deviceName + "|" + "^" + barcode + "\r"; - // } - // - // @Override public String getType() { - // return type; - // } - - // - // @Override - // - // @Override public String getType() { - // return "O"; - // } - // - // public static OFrame fromSerialized(String serialized) throws BiLisFrameParseException { - // OFrame frame = new OFrame(); - // String[] parts = serialized.split("\\|"); - // if (parts.length != 8) { - // throw new BiLisFrameParseException(BiLisFrameParseException.ErrorType.FrameFieldNumberError, "Invalid OFrame : " + serialized); - // } - // frame.type = parts[0];//O1 - // frame.deviceName = parts[1];//O2 - // frame.sampleId = parts[2];//O3 - // var projList = List.of(parts[4].split("\\\\"));//O5 - // //|^COVID-19 Ab^^ 去除掉所有^ - // for (var projName : projList) { - // projName = projName.replace("^^", ""); - // projName = projName.replace("^", ""); - // frame.projList.add(projName); - // } - // frame.sampleType = parts[5].isEmpty() ? null : Integer.parseInt(parts[5]);//O6 - // frame.samplingDate = parts[7];//O8 - // return frame; - // } } diff --git a/src/main/java/a8k/app/type/lisprotocol/subframe/OFrame.java b/src/main/java/a8k/app/type/lisprotocol/subframe/OFrame.java new file mode 100644 index 0000000..c0fcdd4 --- /dev/null +++ b/src/main/java/a8k/app/type/lisprotocol/subframe/OFrame.java @@ -0,0 +1,13 @@ +package a8k.app.type.lisprotocol.subframe; + +import java.util.ArrayList; +import java.util.List; + +public class OFrame { + public String type = "O"; // 请求类型 + public String deviceName = "A8000"; + public String sampleId = "";// + public List projList = new ArrayList<>(); + public Integer sampleType;//全血0,血清血浆1,尿2,粪便3,唾液4 + public String samplingDate;//采样时间 YYYYMMDDhhmmss +} diff --git a/src/main/java/a8k/app/type/lisprotocol/subframe/QFrame.java b/src/main/java/a8k/app/type/lisprotocol/subframe/QFrame.java new file mode 100644 index 0000000..ce8bc24 --- /dev/null +++ b/src/main/java/a8k/app/type/lisprotocol/subframe/QFrame.java @@ -0,0 +1,7 @@ +package a8k.app.type.lisprotocol.subframe; + +public class QFrame { + public String type = "Q"; // 请求类型 + public String deviceName = "A8000"; + public String barcode = "";//格式示例 ^abcdef1234567 +} diff --git a/src/main/java/a8k/app/type/lisprotocol/subframe/RFrame.java b/src/main/java/a8k/app/type/lisprotocol/subframe/RFrame.java new file mode 100644 index 0000000..11574d5 --- /dev/null +++ b/src/main/java/a8k/app/type/lisprotocol/subframe/RFrame.java @@ -0,0 +1,38 @@ +package a8k.app.type.lisprotocol.subframe; + +public class RFrame { + // DEMO + //Example: (注:每次只发送一条信息) + //R|A5000p|1234567890|^hsCRP^^#|18.57|mg/L||||F||||20211209100114| + //R|A10|123456789|^CRP^^#|76|mg/L|0.5-200||L_4^_02|F||||20141201144906||0| + //R|A10||^COVID-19 Ag^^%||||Positive|R_1^_01|F||||20210428153944||4| + // + //二联卡 (单项-PCT/CRP): + //R|A10|123456789|^PCT/CRP^CRP^#|46|mg/L|0.5-200||L_1^_03|F||||20141201144906||0| + //R|A10|123456789|^PCT/CRP^PCT^#|15|ng/mL|0.1-100||L_1^_03|F||||20141201144906||0| + // + //三联卡 (单项-TnI/CK-MB/Myo): + //R|A5000p|Aa123456|^TnI/CK-MB/Myo^TnI^#|3.84|ng/mL||||F||||20211209101158| + //R|A5000p|Aa123456|^TnI/CK-MB/Myo^CK-MB^#|46.39|ng/mL||||F||||20211209101158| + //R|A5000p|Aa123456|^TnI/CK-MB/Myo^Myoglobin^#|45.25|ng/mL||||F||||20211209101158| + // + //COVID-19 Ab: + //R|A10|123456789|^COVID-19 Ab^IgG^@||||Positive|L_1^|F||||20141201144906||1| + //R|A10|123456789|^COVID-19 Ab^IgM^@||||Negative|L_1^ |F||||20141201144906||1| + public String type = "R"; // 信息类型 R-1 + public String deviceName = "A8000"; //设备的名称 R-2 + public String sampleId = "";//样本ID,条形码 R-3 + public String projInfo = "";//R-4 项目名+子项目名+结果类型(定性,定量,半定性) + public String resultVal = "";//R-5 + public String resultUnit = "";//结果单位 R-6 + public String refRange = "";//参考范围,也可以为空 R-7 + public String qualitativeResult = "";//定性结果,如果是定量结果,则该值为空 R-8 + public String samplePosInfo = "";//通道号+试管号 R9 + public String resultType = "F";//always F R10 + public String pad0 = "";// 填充位,如果没有填充则为空 R11 + public String startIncubationTime = "";//开始孵育时间 R12 YYYYMMDDhhmmsss + public String pad1 = "";// 填充位,如果没有填充则为空 R13 + public String endIncubationTime = "";//结束孵育时间 R14 YYYYMMDDhhmmsss + public String pid = "";// 用户输入的ID R15 + public String sampleType = "1";//样本类型 +} diff --git a/src/main/java/a8k/app/utils/ThreadUtils.java b/src/main/java/a8k/app/utils/ThreadUtils.java index 817c9b5..26ea720 100644 --- a/src/main/java/a8k/app/utils/ThreadUtils.java +++ b/src/main/java/a8k/app/utils/ThreadUtils.java @@ -1,5 +1,6 @@ package a8k.app.utils; +import a8k.OS; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -10,6 +11,12 @@ public class ThreadUtils { boolean isConditionMet(); } + + private static long getMonotonicClockTimestamp() { + return System.nanoTime() / 1_000_000; // 转换为毫秒 + } + + static public void threadForceJoin(Thread thread) { boolean isInterrupted = false; while (thread.isAlive()) { @@ -27,9 +34,9 @@ public class ThreadUtils { static public void forceSleep(long millis) { boolean isInterrupted = false; - long startTime = System.currentTimeMillis(); + long startTime = getMonotonicClockTimestamp(); while (true) { - int remainingTime = (int) (millis - (System.currentTimeMillis() - startTime)); + int remainingTime = (int) (millis - (getMonotonicClockTimestamp() - startTime)); if(remainingTime <= 0) { break; } @@ -57,8 +64,8 @@ public class ThreadUtils { } static public boolean waitingForSomeCondition(String conditionName,long retryIntervalMillis, long timeoutMillis, ConditionListener condition ) { - long startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < timeoutMillis) { + long startTime =getMonotonicClockTimestamp(); + while (getMonotonicClockTimestamp() - startTime < timeoutMillis) { if (condition.isConditionMet()) return true; // 如果条件满足,直接返回 log.debug("Waiting for condition: {}", conditionName); diff --git a/src/main/java/a8k/extui/page/codetest/BiLisDoubleTrackChannelTestPage.java b/src/main/java/a8k/extui/page/codetest/BiLisDoubleTrackChannelTestPage.java index fee2062..4dd3d00 100644 --- a/src/main/java/a8k/extui/page/codetest/BiLisDoubleTrackChannelTestPage.java +++ b/src/main/java/a8k/extui/page/codetest/BiLisDoubleTrackChannelTestPage.java @@ -34,6 +34,12 @@ public class BiLisDoubleTrackChannelTestPage { biLisDoubleTrackChannel.ping(); } + synchronized public void execRequestSampleInfo(String sampleId) { + biLisDoubleTrackChannel.requestSampleInfo(sampleId); + log.info("请求样本信息: {}", sampleId); + + } + @PostConstruct public void init() { var page = extApiPageMgr.newPage(this); @@ -42,6 +48,8 @@ public class BiLisDoubleTrackChannelTestPage { .setParamVal("serverPort", () -> serverPort); page.addFunction("新建连接", this::newConnect); page.addFunction("执行Ping", this::execPing); + page.addFunction("请求样本信息", this::execRequestSampleInfo) + .setParamVal("sampleId", () -> ""); extApiPageMgr.addPage(page); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d6df058..f4b7fac 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,15 +1,16 @@ #WEB虚拟后端 -server.port: 80 -iflytophald.ip: 127.0.0.1 -iflytophald.enable: false -device.runmode: "VirtualStateGenerateMode" -lis.enable: false +#server.port: 80 +#iflytophald.ip: 127.0.0.1 +#iflytophald.enable: false +#device.runmode: "VirtualStateGenerateMode" +#lis.enable: false #PC调试 -#server.port: 80 -#device.runmode: "RealMode" -#iflytophald.ip: 192.168.8.10 -#iflytophald.enable: true +server.port: 80 +device.runmode: "RealMode" +iflytophald.ip: 192.168.8.10 +iflytophald.enable: true +lis.enable: false #硬件测试 #server.port: 8082