diff --git a/app.db b/app.db index e7f5075..24e9426 100644 Binary files a/app.db and b/app.db differ diff --git a/src/main/java/a8k/service/app/appctrl/AppDeviceCtrlService.java b/src/main/java/a8k/service/app/appctrl/AppDeviceCtrlService.java index 6192863..29116b1 100644 --- a/src/main/java/a8k/service/app/appctrl/AppDeviceCtrlService.java +++ b/src/main/java/a8k/service/app/appctrl/AppDeviceCtrlService.java @@ -48,19 +48,13 @@ public class AppDeviceCtrlService { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ @ExtApiFn(name = "设备初始化前检查", group = "设备初始化") public List checkBeforeInitDevice() throws AppException { - if (appDebugHelperService.isDebug()) { - return appDebugHelperService.checkBeforeInitDevice(); - } + return deviceInitializationModule.checkBeforeInitDevice(); } @ExtApiFn(name = "设备初始化(阻塞接口)", group = "设备初始化") public void initDevice() throws AppException { - if (appDebugHelperService.isDebug()) { - OS.forceSleep(5000); - gstate.setDeviceInited(true); - return; - } + deviceInitializationModule.deviceMoveToZero(); } @@ -102,6 +96,25 @@ public class AppDeviceCtrlService { } + /** + * 设置Tip数量 + * @param group Tip组 + * @param num 数量 + */ + @ExtApiFn(name = "设置Tip数量", group = "耗材管理") + synchronized public void setTipNum(Integer group, Integer num) { + consumablesMgrService.setTipNum(group, num); + } + + /** + * 设置耗材数量 + * @param group 耗材组 + * @param num 数量 + */ + @ExtApiFn(name = "设置耗材数量", group = "耗材管理") + synchronized public void setCounsumableNum(ConsumableGroup group, Integer num) { + consumablesMgrService.setCounsumableNum(group, num); + } /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * 设备状态 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ @@ -142,25 +155,7 @@ public class AppDeviceCtrlService { return gstate.getConsumableState(); } - @ExtApiFn(name = "获取运行步骤状态", group = "调试") - public List getA8kStepActionList() { - return mainFlowCtrlSampleScanService.getA8kStepActionList(); - } - @ExtApiFn(name = "获取设备状态(调试使用)", group = "调试") - public GStateService getGState() { - return gstate; - } - - @ExtApiFn(name = "获取所有正在处理的项目Context(调试使用)", group = "调试") - public List getAllProcessingProjProcessContexts() { - return projectProcessContextMgrService.getAllProcessingProjProcessContexts(); - } - - @ExtApiFn(name = "获取所有项目Context(调试使用)", group = "调试") - public List getAllErrorProjProcessContexts() { - return projectProcessContextMgrService.getAllErrorProjProcessContexts(); - } /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * 系统控制 diff --git a/src/main/java/a8k/service/app/appctrl/DeviceInitCtrlService.java b/src/main/java/a8k/service/app/appctrl/DeviceInitCtrlService.java index de0e1b5..068a001 100644 --- a/src/main/java/a8k/service/app/appctrl/DeviceInitCtrlService.java +++ b/src/main/java/a8k/service/app/appctrl/DeviceInitCtrlService.java @@ -5,6 +5,7 @@ import a8k.hardware.type.a8kcanprotocol.A8kEcode; import a8k.hardware.type.a8kcanprotocol.IOId; import a8k.hardware.type.a8kcanprotocol.MId; import a8k.service.bases.ActionReactorService; +import a8k.service.debug.AppDebugHelperService; import a8k.type.checkpoint.CheckResult; import a8k.type.checkpoint.Checkpoint; import a8k.service.app.appstate.GStateService; @@ -29,7 +30,10 @@ public class DeviceInitCtrlService { A8kCanBusService canBus; @Resource - GStateService stateMgrService; + GStateService gstate; + + @Resource + AppDebugHelperService appDebugHelper; Integer actionOvertime = 10000; @@ -52,6 +56,9 @@ public class DeviceInitCtrlService { public List checkBeforeInitDevice() throws AppException { + if (appDebugHelper.isDebug()) { + return appDebugHelper.checkBeforeInitDevice(); + } List results = new java.util.ArrayList<>(); for (Checkpoint checkPoint : checkPoints) { CheckResult result = new CheckResult(); @@ -75,6 +82,13 @@ public class DeviceInitCtrlService { // public void deviceMoveToZero() throws AppException { + + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("初始化设备", 3); + gstate.setDeviceInited(true); + return; + } + Boolean suc = checkBeforeInitDevicePass(); if (!suc) { throw new AppException(A8kEcode.APPE_CHECK_POINT_CHECK_FAIL); @@ -104,7 +118,7 @@ public class DeviceInitCtrlService { //转盘归零 ar.dosome("转盘回零", () -> canBus.stepMotorEasyMoveToZeroBlock(MId.IncubatorRotateCtrlM, actionOvertime)); - stateMgrService.setDeviceInited(true); + gstate.setDeviceInited(true); } diff --git a/src/main/java/a8k/service/app/appctrl/TubeSettingMgrService.java b/src/main/java/a8k/service/app/appctrl/TubeSettingMgrService.java index 22cc177..b94793b 100644 --- a/src/main/java/a8k/service/app/appctrl/TubeSettingMgrService.java +++ b/src/main/java/a8k/service/app/appctrl/TubeSettingMgrService.java @@ -73,6 +73,7 @@ public class TubeSettingMgrService { return gState.getTubeHolderSettings(); } + //添加试管架,返回整个列表 @ExtApiFn(name = "添加<试管架>配置", group = "试管架", order = ORDER.addCfg) synchronized public List newTubeHolderSetting() { var newSetting = new TubeHolderSetting(); @@ -82,6 +83,14 @@ public class TubeSettingMgrService { return getTubeHolderSettings(); } + + //添加试管架,返回被添加的试管架 + synchronized public TubeHolderSetting newTubeHolderSettingV2() { + var newSetting = new TubeHolderSetting(); + gState.getTubeHolderSettings().add(newSetting); + return newSetting; + } + @ExtApiFn(name = "删除<试管架>配置", group = "试管架", order = ORDER.removeCfg) synchronized public List removeTubeHolderSetting(String uuid) throws AppException { logger.info("removeTubeHolderSetting {}", uuid); @@ -90,12 +99,6 @@ public class TubeSettingMgrService { return getTubeHolderSettings(); } - synchronized public void removeTubeHolderSetting(TubeHolderSetting setting) throws AppException { - if (setting == null) { - return; - } - removeTubeHolderSetting(setting.uuid); - } @ExtApiFn(name = "设置<试管架>激活状态", group = "试管架", order = ORDER.activeCfg) synchronized public List tubeHodlerSettingSetActiveState(String uuid, Boolean active) throws AppException { @@ -153,8 +156,15 @@ public class TubeSettingMgrService { * internal use *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + synchronized public void interUseRemoveTubeHolderSetting(TubeHolderSetting removeSetting) throws AppException { + if (removeSetting == null) { + return; + } + gState.getTubeHolderSettings().removeIf(setting -> setting.uuid.equals(removeSetting.uuid)); + } + //锁定第一个激活的试管架配置 - synchronized public TubeHolderSetting getThelastActiveTubeHolderSettingAndLock() { + synchronized public TubeHolderSetting interUseGetThelastActiveTubeHolderSettingAndLock() { for (int i = gState.getTubeHolderSettings().size() - 1; i >= 0; i--) { TubeHolderSetting setting = gState.getTubeHolderSettings().get(i); if (setting.active) { @@ -166,7 +176,7 @@ public class TubeSettingMgrService { return null; } - synchronized public void unlockTubeHolderSetting(String uuid) { + synchronized public void interUseUnlockTubeHolderSetting(String uuid) { for (TubeHolderSetting setting : gState.getTubeHolderSettings()) { if (setting.uuid.equals(uuid)) { setting.lock = false; diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/CondtionMgrService.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/CondtionMgrService.java index a3798e3..d02a374 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/CondtionMgrService.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/CondtionMgrService.java @@ -35,7 +35,7 @@ public class CondtionMgrService { @Resource AppDebugHelperService appDebugHelper; - Boolean getTubeholderEnterPosPPS() { //入料通道是否为空 + public Boolean getTubeholderEnterPosPPS() { //入料通道是否为空 if (appDebugHelper.isDebug()) { return appDebugHelper.getTubeholderEnterPosPPS(); } @@ -107,9 +107,7 @@ public class CondtionMgrService { Boolean cond1 = isCanDoAction(); //没有试管在处理 或者 当前试管处理完成 Boolean cond2 = tube.getState().equals(TubeState.RESOURCE_IS_READY); - //急诊有待处理的试管,或者试管架正在处理 - Boolean cond3 = incubationPlateStateMgrService.isHasEnoughIncubationIDLEPos(tube.getProjInfo().size()); - return cond1 && cond2 && cond3; + return cond1 && cond2; } public Boolean isTimeToProcessTube() { @@ -121,9 +119,7 @@ public class CondtionMgrService { Boolean cond1 = isCanDoAction(); //没有试管在处理 或者 当前试管处理完成 Boolean cond2 = tube.getState().equals(TubeState.PRE_PROCESSED); - //急诊有待处理的试管,或者试管架正在处理 - Boolean cond3 = incubationPlateStateMgrService.isHasEnoughIncubationIDLEPos(tube.getProjInfo().size()); - return cond1 && cond2 && cond3; + return cond1 && cond2 ; } public Boolean isTimeToPostProcessTube() { @@ -135,9 +131,7 @@ public class CondtionMgrService { Boolean cond1 = isCanDoAction(); //没有试管在处理 或者 当前试管处理完成 Boolean cond2 = tube.getState().equals(TubeState.PROCESSED); - //急诊有待处理的试管,或者试管架正在处理 - Boolean cond3 = incubationPlateStateMgrService.isHasEnoughIncubationIDLEPos(tube.getProjInfo().size()); - return cond1 && cond2 && cond3; + return cond1 && cond2 ; } public Boolean isHasSomeErrorPlatesToBeProcessed() { diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/MainFlowCtrlScheduler.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/MainFlowCtrlScheduler.java index 28574ec..7be49d2 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/MainFlowCtrlScheduler.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/MainFlowCtrlScheduler.java @@ -120,11 +120,11 @@ public class MainFlowCtrlScheduler { A8kErrorContext callFn(A8kStepAction key) { logger.info("----------------------{}---------------------->", ZStringUtils.leftAlignStr(key.step.name(), '-', 35)); - logger.info("Relate {} ", key.logCxtState()); + logger.info("RELATE {} PARALLEL:{} ", key.logCxtState(), key.isAllowsParallelRunning()); beforeDoWhat(key.step); try { key.doaction(); - return new A8kErrorContext(key.step, null); + return null; } catch (AppException appe) { return new A8kErrorContext(key.step, appe.error); } finally { @@ -152,14 +152,14 @@ public class MainFlowCtrlScheduler { for (Future future : futureList) { try { - ecodeList.add(future.get()); + if (future.get() != null) + ecodeList.add(future.get()); } catch (Exception e) { throw new RuntimeException(e); } } //清空NoError - ecodeList.removeIf(ecode -> ecode.ecode == null); return ecodeList; } @@ -193,6 +193,7 @@ public class MainFlowCtrlScheduler { //等待并行任务完成 List ecodeListParallel = waitAllActionIsDone(futureList); + ecodeList.removeIf(ecode -> ecode == null || ecode.ecode == null); resourceMgrService.releaseAllResource(this); //合并错误 diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_FINISH_TUBE_PROCESS.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_FINISH_TUBE_PROCESS.java index 3205bc0..6dd6d91 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_FINISH_TUBE_PROCESS.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_FINISH_TUBE_PROCESS.java @@ -66,13 +66,9 @@ public class DO_FINISH_TUBE_PROCESS extends A8kStepAction { } if (appDebugHelper.isDebug()) { - logger.info("处理错误试管:{}", tube.getSampleId()); - logger.info("复位摇匀模组"); - OS.forceSleep(4000); - //复位摇匀模组 - logger.info("复位HBOT"); - //复位HBOT - OS.forceSleep(4000); + appDebugHelper.doVirtualThings("处理错误试管", 2); + appDebugHelper.doVirtualThings("复位摇匀模组", 2); + appDebugHelper.doVirtualThings("复位HBOT", 2); } projectProcessContextMgrService.finishedTubeProcess(); } @@ -94,6 +90,6 @@ public class DO_FINISH_TUBE_PROCESS extends A8kStepAction { var tube = gstate.getCurProcessingTube(); if (tube == null) return ""; - return String.format("[sid: %s, tanpos: %s]", tube.getSampleId(), tube.getPos()); + return String.format("[sid: %s, tippos: %s]", tube.getSampleId(), tube.getPos()); } } diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_PROCESS_ERROR_PLATE.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_PROCESS_ERROR_PLATE.java index 68013cd..c7cf0dc 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_PROCESS_ERROR_PLATE.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_PROCESS_ERROR_PLATE.java @@ -11,7 +11,6 @@ import a8k.service.app.appstate.ProjectProcessContextMgrService; import a8k.service.app.appstate.resource.A8kPublicResourceType; import a8k.service.app.appstate.type.IncubationSubTank; import a8k.service.app.appstate.type.MainFlowCtrlState; -import a8k.service.app.appstate.type.state.IncubationSubTankState; import a8k.service.debug.AppDebugHelperService; import a8k.type.exception.AppException; import jakarta.annotation.PostConstruct; @@ -42,7 +41,7 @@ public class DO_PROCESS_ERROR_PLATE extends A8kStepAction { @Resource OptScanModuleStateMgrService optScanModuleStateMgrService; @Resource - AppDebugHelperService appDebugHelperService; + AppDebugHelperService appDebugHelper; @Resource ProjectProcessContextMgrService projectProcessContextMgrService; @@ -63,12 +62,15 @@ public class DO_PROCESS_ERROR_PLATE extends A8kStepAction { break; } - if (appDebugHelperService.isDebug()) { + if (appDebugHelper.isDebug()) { logger.info("处理错误板夹:{}", errorTank.getPos()); //TODO:推出板夹到光学模组,同时丢弃板夹 logger.info("推出板夹到光学模组"); logger.info("丢弃板夹"); OS.forceSleep(3000); + appDebugHelper.doVirtualThings("处理错误板夹:" + errorTank.getPos(), 2); + appDebugHelper.doVirtualThings("推出板夹到光学模组", 2); + appDebugHelper.doVirtualThings("丢弃板夹", 2); } // projectProcessContextMgrService.newPlateToOptScanPos(errorTank); diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_START.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_START.java index 1a2610f..febcba1 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_START.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/DO_START.java @@ -44,10 +44,10 @@ public class DO_START extends A8kStepAction { @Override public void doaction() throws AppException { mfcs.workStateChangeFlag = false; - if (!appDebugHelper.isDebug()) { - sampleScanTransportHardwareControler.ejectTubeHolder(); + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("弹出试管架", 2); } else { - logger.info("弹出试管架"); + sampleScanTransportHardwareControler.ejectTubeHolder(); } } diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PLATE_OPT_SCAN.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PLATE_OPT_SCAN.java index f9de5de..cf1937a 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PLATE_OPT_SCAN.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PLATE_OPT_SCAN.java @@ -70,7 +70,7 @@ public class PLATE_OPT_SCAN extends A8kStepAction { logger.info("扫描板夹"); //记录扫描结果 if (appDebugHelper.isDebug()) { - OS.forceSleep(3000); + appDebugHelper.doVirtualThings("扫描板夹", 2); } else { } //修改板夹状态 diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PROCESS_INCUBATE_COMPLETED_PLATE.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PROCESS_INCUBATE_COMPLETED_PLATE.java index 6693522..1b963e9 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PROCESS_INCUBATE_COMPLETED_PLATE.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/PROCESS_INCUBATE_COMPLETED_PLATE.java @@ -67,7 +67,7 @@ public class PROCESS_INCUBATE_COMPLETED_PLATE extends A8kStepAction { return; } if (appDebugHelper.isDebug()) { - OS.forceSleep(3000); + appDebugHelper.doVirtualThings("处理孵育完成的板夹", 2); } projectProcessContextMgrService.newPlateToOptScanPos(tank); diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ1_ENTER_TUBEHOLDER_AND_SCAN.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ1_ENTER_TUBEHOLDER_AND_SCAN.java index dfc3d18..9d3d9ad 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ1_ENTER_TUBEHOLDER_AND_SCAN.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ1_ENTER_TUBEHOLDER_AND_SCAN.java @@ -164,7 +164,7 @@ public class SEQ1_ENTER_TUBEHOLDER_AND_SCAN extends A8kStepAction { TubeHolder parseScanResult(TubeHolderScanResult scanResult) throws AppException { TubeHolder tubeholder = new TubeHolder(); - TubeHolderSetting setting = tubeSettingMgrService.getThelastActiveTubeHolderSettingAndLock(); + TubeHolderSetting setting = tubeSettingMgrService.interUseGetThelastActiveTubeHolderSettingAndLock(); try { //获取试管架类型 A8kTubeHolderType tubeHolderType = A8kTubeHolderType.of(scanResult.tubeHolderType); @@ -188,12 +188,13 @@ public class SEQ1_ENTER_TUBEHOLDER_AND_SCAN extends A8kStepAction { //设置试管架状态 tubeholder.setState(TubeHolderState.PROCESSING); //删除之前的试管架配置 - tubeSettingMgrService.removeTubeHolderSetting(setting); + tubeSettingMgrService.interUseRemoveTubeHolderSetting(setting); return tubeholder; } catch (AppException e) { //回滚部分状态 - tubeSettingMgrService.removeTubeHolderSetting(setting); + if (setting != null) + tubeSettingMgrService.interUseUnlockTubeHolderSetting(setting.uuid); throw e; } } diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ2_SWITCH_TO_THE_NEXT_TUBE.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ2_SWITCH_TO_THE_NEXT_TUBE.java index cbb0002..3130dea 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ2_SWITCH_TO_THE_NEXT_TUBE.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ2_SWITCH_TO_THE_NEXT_TUBE.java @@ -72,7 +72,7 @@ public class SEQ2_SWITCH_TO_THE_NEXT_TUBE extends A8kStepAction { public void moveToNextTube(Integer tubeIndex) throws AppException { if (appDebugHelperService.isDebug()) { - OS.forceSleep(1000); + appDebugHelperService.doVirtualThings("移动到下一个试管", 2); } else { sstc.moveTubeToPreProcessPos(tubeIndex); } diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ3_APPLAY_RESOURCE.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ3_APPLAY_RESOURCE.java index 9903671..1cf1297 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ3_APPLAY_RESOURCE.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ3_APPLAY_RESOURCE.java @@ -57,6 +57,13 @@ public class SEQ3_APPLAY_RESOURCE extends A8kStepAction { @Override public void doaction() throws AppException { Tube tube = gstate.getCurProcessingTube(); + if(tube.getProjIndex().isEmpty()){ + logger.warn("试管待做项目为空,skip"); + projectProcessContextMgrService.finishedTubeProcess(); + return; + } + + projectProcessContextMgrService.startPrepareRecourseOK(); assert tube != null; boolean applyConsumable = projectProcessContextMgrService.takeResourceConsumable(tube); diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ4_PRE_PROCESS.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ4_PRE_PROCESS.java index b605bd4..1110b8d 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ4_PRE_PROCESS.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ4_PRE_PROCESS.java @@ -5,6 +5,7 @@ import a8k.service.app.appctrl.mainflowctrl.CondtionMgrService; import a8k.service.app.appdata.AppProjInfoMgrService; import a8k.service.app.appstate.type.state.IncubationSubTankState; import a8k.service.app.appstate.type.state.ProjProcessState; +import a8k.service.debug.AppDebugHelperService; import a8k.utils.AppExceptionBuilder; import a8k.service.bases.AppEventBusService; import a8k.service.app.appctrl.mainflowctrl.base.A8kActionStepType; @@ -67,6 +68,8 @@ public class SEQ4_PRE_PROCESS extends A8kStepAction { IncubationPlateStateMgrService incubationPlateStateMgrService; @Resource ProjectProcessContextMgrService projectProcessContextMgrService; + @Resource + AppDebugHelperService appDebugHelper; @Resource CondtionMgrService cms; @@ -83,11 +86,11 @@ public class SEQ4_PRE_PROCESS extends A8kStepAction { * @throws AppException 异常 */ void prepareReactionPlate() throws AppException { - if (gstate.debugMode) { - logger.info("prepareReactionPlate"); - OS.forceSleep(1000); + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("准备反应板夹", 2); return; } + // Tube tube = gstate.getCurProcessingTube(); // List projIndex = tube.projIndex; // List incubatorPos = tube.incubatorPos; @@ -106,12 +109,12 @@ public class SEQ4_PRE_PROCESS extends A8kStepAction { * @throws AppException */ void shakeAndTakeCap() throws AppException { - if (gstate.debugMode) { - logger.info("shakeAndTakeCap"); - OS.forceSleep(1000); + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("摇匀并取盖", 2); return; } + // Tube tube = gstate.getCurProcessingTube(); // if (tube.isEmergency) { // //如果事急诊位则什么也不做 @@ -132,9 +135,8 @@ public class SEQ4_PRE_PROCESS extends A8kStepAction { } void hbotPrepareTip() throws AppException { - if (gstate.debugMode) { - logger.info("hbotPrepareTip"); - OS.forceSleep(1000); + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("准备Hbot Tip", 2); return; } // Tube tube = gstate.getCurProcessingTube(); diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ5_PROCESS.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ5_PROCESS.java index 2667044..9ab81f3 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ5_PROCESS.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ5_PROCESS.java @@ -2,6 +2,7 @@ package a8k.service.app.appctrl.mainflowctrl.action; import a8k.OS; import a8k.service.app.appstate.type.state.ProjProcessState; +import a8k.service.debug.AppDebugHelperService; import a8k.utils.AppExceptionBuilder; import a8k.service.app.appctrl.mainflowctrl.CondtionMgrService; import a8k.service.app.appctrl.mainflowctrl.base.A8kActionStepType; @@ -46,6 +47,8 @@ public class SEQ5_PROCESS extends A8kStepAction { CondtionMgrService cms; @Resource ProjectProcessContextMgrService projectProcessContextMgrService; + @Resource + AppDebugHelperService appDebugHelper; MainFlowCtrlState state; @@ -63,7 +66,11 @@ public class SEQ5_PROCESS extends A8kStepAction { // var tube = gstate.getCurProcessingTube(); projectProcessContextMgrService.startProcessTube(); - OS.forceSleep(3000); + + if(appDebugHelper.isDebug()){ + appDebugHelper.doVirtualThings("处理样本(准备反应板,取tip,摇匀,脱帽)", 5); + } + List projList = tube.getProjIndex(); for (Integer projIndex : projList) { ProjProcessContext cxt = projectProcessContextMgrService.getProjProcessContext(tube.getSampleId(), diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ6_POST_PROCESS.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ6_POST_PROCESS.java index cd889e1..b249731 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ6_POST_PROCESS.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ6_POST_PROCESS.java @@ -1,6 +1,7 @@ package a8k.service.app.appctrl.mainflowctrl.action; import a8k.OS; +import a8k.service.debug.AppDebugHelperService; import a8k.utils.AppExceptionBuilder; import a8k.service.app.appctrl.mainflowctrl.CondtionMgrService; import a8k.service.app.appctrl.mainflowctrl.base.A8kActionStepType; @@ -47,6 +48,8 @@ public class SEQ6_POST_PROCESS extends A8kStepAction { ProjectProcessContextMgrService projectProcessContextMgrService; @Resource CondtionMgrService cms; + @Resource + AppDebugHelperService appDebugHelper; ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10)); @@ -61,7 +64,10 @@ public class SEQ6_POST_PROCESS extends A8kStepAction { @Override public void doaction() throws AppException { projectProcessContextMgrService.postProcessTube(); - OS.forceSleep(3000); + + if (appDebugHelper.isDebug()) { + appDebugHelper.doVirtualThings("后处理样本", 3); + } projectProcessContextMgrService.postProcessTubeOK(); } diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ7_EJECT_TUBEHOLDER.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ7_EJECT_TUBEHOLDER.java index f4c0052..fd562e9 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ7_EJECT_TUBEHOLDER.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/action/SEQ7_EJECT_TUBEHOLDER.java @@ -49,6 +49,7 @@ public class SEQ7_EJECT_TUBEHOLDER extends A8kStepAction { SST_HControler.ejectTubeHolder(); SST_HControler.moveTubeRackMoveToEnterPos(); } else { + appDebugHelper.doVirtualThings("弹出试管架", 2); } gstate.getTubeHolder().setState(TubeHolderState.IDLE); diff --git a/src/main/java/a8k/service/app/appctrl/mainflowctrl/base/A8kStepAction.java b/src/main/java/a8k/service/app/appctrl/mainflowctrl/base/A8kStepAction.java index 7c2fda4..a7a0d42 100644 --- a/src/main/java/a8k/service/app/appctrl/mainflowctrl/base/A8kStepAction.java +++ b/src/main/java/a8k/service/app/appctrl/mainflowctrl/base/A8kStepAction.java @@ -34,11 +34,10 @@ public class A8kStepAction { } public Boolean isAllowsParallelRunning() { - return true; + return false; } public String logCxtState() { return ""; } - } diff --git a/src/main/java/a8k/service/app/appstate/ConsumablesMgrService.java b/src/main/java/a8k/service/app/appstate/ConsumablesMgrService.java index 758c08b..1a20fd3 100644 --- a/src/main/java/a8k/service/app/appstate/ConsumablesMgrService.java +++ b/src/main/java/a8k/service/app/appstate/ConsumablesMgrService.java @@ -91,14 +91,15 @@ public class ConsumablesMgrService { } synchronized Integer priTakeOneConsumable(ConsumableGroup group) { - var cState = gstate.getConsumableState(); - cState.reactionPlateGroup[group.off].num--; - return AppConstant.CONSUMABLE_NUM - cState.reactionPlateGroup[group.off].num + 1; + Integer num = priGetConsumableRemainNum(group); + priSetConsumableGroupNum(group, num - 1); + return AppConstant.CONSUMABLE_NUM - num - 1 + 1; } synchronized void priBakOneConsumable(ConsumableGroup group) { - var cState = gstate.getConsumableState(); - cState.reactionPlateGroup[group.off].num++; + var cState = gstate.getConsumableState(); + Integer num = priGetConsumableRemainNum(group); + priSetConsumableGroupNum(group, num + 1); } synchronized String priGetLotId(ConsumableGroup group) { @@ -183,6 +184,8 @@ public class ConsumablesMgrService { List ret = new ArrayList<>(); for (int i = 0; i < takeCnt; i++) { TipPos tipPos = takeNextTipPos(); + if (tipPos == null) + break; ret.add(tipPos); } diff --git a/src/main/java/a8k/service/app/appstate/EmergencySamplePosStateMgrService.java b/src/main/java/a8k/service/app/appstate/EmergencySamplePosStateMgrService.java index a92f0a4..cbb8e8f 100644 --- a/src/main/java/a8k/service/app/appstate/EmergencySamplePosStateMgrService.java +++ b/src/main/java/a8k/service/app/appstate/EmergencySamplePosStateMgrService.java @@ -56,6 +56,10 @@ public class EmergencySamplePosStateMgrService { throw new AppException(A8kEcode.APPE_ACTION_IS_NOT_ALLOWED_WHEN_WORKING); } + if(projIndexList == null || projIndexList.isEmpty()) { + throw new AppException(A8kEcode.APPE_PROJ_INDEX_IS_EMPTY); + } + for (String index : projIndexList.split(",")) { projIndex.add(Integer.parseInt(index)); } diff --git a/src/main/java/a8k/service/app/appstate/GStateService.java b/src/main/java/a8k/service/app/appstate/GStateService.java index 7e880bc..f03a475 100644 --- a/src/main/java/a8k/service/app/appstate/GStateService.java +++ b/src/main/java/a8k/service/app/appstate/GStateService.java @@ -43,8 +43,6 @@ public class GStateService { String sn = "NOTSET"; - public Boolean debugMode = true; - //主流程控制状态 public MainFlowCtrlState mainFlowCtrlState = new MainFlowCtrlState(); diff --git a/src/main/java/a8k/service/app/appstate/ProjectProcessContextMgrService.java b/src/main/java/a8k/service/app/appstate/ProjectProcessContextMgrService.java index 577bd9b..e4d9c48 100644 --- a/src/main/java/a8k/service/app/appstate/ProjectProcessContextMgrService.java +++ b/src/main/java/a8k/service/app/appstate/ProjectProcessContextMgrService.java @@ -125,7 +125,9 @@ public class ProjectProcessContextMgrService { } } } - + synchronized public List getAllContexts() { + return contexts; + } synchronized public List getAllProcessingProjProcessContexts() { List ret = new ArrayList<>(); diff --git a/src/main/java/a8k/service/app/appstate/type/IncubationSubTank.java b/src/main/java/a8k/service/app/appstate/type/IncubationSubTank.java index 1a39f70..1a0a4ac 100644 --- a/src/main/java/a8k/service/app/appstate/type/IncubationSubTank.java +++ b/src/main/java/a8k/service/app/appstate/type/IncubationSubTank.java @@ -70,5 +70,16 @@ public class IncubationSubTank { return String.format("%s(%d)", projInfo.projShotName, projInfo.projIndex); } + public Integer getIncubatedRemainTimeSec() { + if (state == IncubationSubTankState.INCUBATING) { + long remain = incubatedTimeSec - (System.currentTimeMillis() - startIncubatedTime) / 1000; + if (remain < 0) { + return 0; + } + return (int) remain; + } + return 0; + } + } diff --git a/src/main/java/a8k/service/app/appstate/type/ProjProcessContext.java b/src/main/java/a8k/service/app/appstate/type/ProjProcessContext.java index 5acd1b1..6d0cc0b 100644 --- a/src/main/java/a8k/service/app/appstate/type/ProjProcessContext.java +++ b/src/main/java/a8k/service/app/appstate/type/ProjProcessContext.java @@ -8,6 +8,7 @@ import a8k.type.ecode.AppError; import a8k.type.type.BloodType; import a8k.utils.A8kProjCfg; import a8k.utils.ProjBriefInfo; +import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,7 @@ public class ProjProcessContext { public ProjBriefInfo projInfo;//项目信息 + @JsonIgnore public A8kProjCfg projCfg;//项目配置 有分配耗材时,才会拿到具体的项目配置 public Consumable consumable;//耗材绑定的通道号 public IncubatorPos incubatorPos;//孵育位置 diff --git a/src/main/java/a8k/service/debug/AppDebugHelperService.java b/src/main/java/a8k/service/debug/AppDebugHelperService.java index d54f504..3f853d4 100644 --- a/src/main/java/a8k/service/debug/AppDebugHelperService.java +++ b/src/main/java/a8k/service/debug/AppDebugHelperService.java @@ -8,10 +8,18 @@ import a8k.extapi_controler.utils.ExtApiTab; import a8k.hardware.type.a8kcanprotocol.A8kEcode; import a8k.hardware.type.a8kcanprotocol.CmdId; import a8k.hardware.type.a8kcanprotocol.MId; +import a8k.service.app.appctrl.AppDeviceCtrlService; import a8k.service.app.appctrl.CheckPointType; +import a8k.service.app.appctrl.MainFlowCtrlService; +import a8k.service.app.appctrl.TubeSettingMgrService; +import a8k.service.app.appctrl.mainflowctrl.CondtionMgrService; import a8k.service.app.appctrl.mainflowctrl.base.A8kActionStepType; import a8k.service.app.appctrl.mainflowctrl.base.A8kErrorContext; +import a8k.service.app.appctrl.mainflowctrl.base.A8kStepAction; +import a8k.service.app.appstate.ConsumablesMgrService; import a8k.service.app.appstate.GStateService; +import a8k.service.app.appstate.ProjectProcessContextMgrService; +import a8k.service.app.appstate.type.ProjProcessContext; import a8k.service.bases.AppEventBusService; import a8k.service.bases.appevent.*; import a8k.service.db.A8kProjIdCardDBService; @@ -31,13 +39,17 @@ import a8k.type.ecode.ConsumeNotEnoughError; import a8k.type.ecode.HardwareError; import a8k.type.exception.AppException; import a8k.type.reaction_result_type.ReactionResultStatus; +import a8k.type.tube_setting.TubeHolderSetting; import a8k.type.type.A8kTubeHolderType; import a8k.type.type.BloodType; +import a8k.type.type.TipGroup; import a8k.utils.A8kPacketBuilder; import a8k.utils.ReactionPlate2DCodeHelper; import a8k.utils.ZDateUtils; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -48,6 +60,7 @@ import java.util.List; @ExtApiTab(cfg = ExtApiTabConfig.AppDebugHelperService) public class AppDebugHelperService { + private static final Logger logger = LoggerFactory.getLogger(AppDebugHelperService.class); Boolean debugFlag = true; @Resource @@ -243,6 +256,80 @@ public class AppDebugHelperService { eventBus.pushEvent(new A8kEcodeContextListPromptEvent(errorContexts)); } + + @ExtApiFn(name = "获取运行步骤状态", group = "调试") + public List getA8kStepActionList() { + MainFlowCtrlService mainFlowCtrlService = SpringBootBeanUtil.getBean(MainFlowCtrlService.class); + return mainFlowCtrlService.getA8kStepActionList(); + } + + @ExtApiFn(name = "获取设备状态()", group = "调试") + public GStateService getGState() { + return gstate; + } + + @ExtApiFn(name = "获取所有项目<运行中>Context", group = "调试") + public List getAllProcessingProjProcessContexts() { + ProjectProcessContextMgrService projectProcessContextMgrService = SpringBootBeanUtil.getBean(ProjectProcessContextMgrService.class); + return projectProcessContextMgrService.getAllProcessingProjProcessContexts(); + } + + @ExtApiFn(name = "获取所有项目Context", group = "调试") + public List getAllContext() { + ProjectProcessContextMgrService projectProcessContextMgrService = SpringBootBeanUtil.getBean(ProjectProcessContextMgrService.class); + return projectProcessContextMgrService.getAllContexts(); + } + + @ExtApiFn(name = "获取所有项目<异常>Context", group = "调试") + public List getAllErrorProjProcessContexts() { + ProjectProcessContextMgrService projectProcessContextMgrService = SpringBootBeanUtil.getBean(ProjectProcessContextMgrService.class); + return projectProcessContextMgrService.getAllErrorProjProcessContexts(); + } + + @ExtApiFn(name = "获取动作运行条件", group = "调试") + public CondtionMgrService getCondtions() { + return SpringBootBeanUtil.getBean(CondtionMgrService.class); + } + + + @ExtApiFn(name = "测试0", group = "虚拟测试", order = 1) + public void virtulaTest0() throws AppException { + //插入ID卡 + logger.info("保存测试的ID卡信息"); + saveAllIDCard(); + + //添加试管配置 + logger.info("添加试管配置"); + TubeSettingMgrService tubeSettingMgrService = SpringBootBeanUtil.getBean(TubeSettingMgrService.class); + tubeSettingMgrService.newTubeHolderSetting(); + TubeHolderSetting setting = tubeSettingMgrService.newTubeHolderSettingV2(); + for (int i = 0; i < 10; i++) { + tubeSettingMgrService.setTubeSettingUserIdAndSampleBarcode(setting.uuid, i, String.format("USR%d", i), ""); + tubeSettingMgrService.setTubeSetting(setting.uuid, i, BloodType.WHOLE_BLOOD,// + String.format("%d,%d,%d,%d,%d,%d", p01_hscrr.projIndex, p02_pct.projIndex, p03_tsh.projIndex, p05_t3.projIndex, p06_t4.projIndex, p22_pctAndHsCRP.projIndex)); + } + tubeSettingMgrService.tubeHodlerSettingSetActiveState(setting.uuid, true); + + //初始化设备 + logger.info("初始化设备"); + AppDeviceCtrlService appDeviceCtrlService = SpringBootBeanUtil.getBean(AppDeviceCtrlService.class); + appDeviceCtrlService.initDevice(); + + //扫描耗材 + logger.info("扫描耗材"); + putRightConsumable(); + appDeviceCtrlService.scanConsumables(); + + ConsumablesMgrService consumablesMgrService = SpringBootBeanUtil.getBean(ConsumablesMgrService.class); + consumablesMgrService.setTipNum(TipGroup.GROUP0.ordinal(),120); + + + //放入一个虚拟试管 + logger.info("放入一个虚拟试管"); + insertVirtualBloodTubeHolder(); + } + + //InterUse public Boolean getVirtualIDCard() { return mountIdcard != null; @@ -280,7 +367,7 @@ public class AppDebugHelperService { public ConsumableOneChResult scanOneCH(Integer ch) { - OS.forceSleep(1500); + doVirtualThings("扫描耗材-板夹" + ch, 1); ConsumableOneChResult result = new ConsumableOneChResult(ch); switch (consumablesErrorType[ch]) { case PASS: //通过 @@ -364,8 +451,16 @@ public class AppDebugHelperService { synchronized public TubeHolderScanResult takeTubeHolderScanResult() { TubeHolderScanResult tubeHolderScanResult = this.tubeHolderScanResult; this.tubeHolderScanResult = null; + doVirtualThings("进入一个新的试管架", 2); return tubeHolderScanResult; } + synchronized public void doVirtualThings(String dowhat, Integer time) { + // for (int i = 0; i < time; i++) { + logger.info("VirtualDoing:" + dowhat); + OS.forceSleep(100); + // } + } + } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/FakeProjInfo.java b/src/main/java/a8k/service/debug/fakeprojinfo/FakeProjInfo.java index 48e9759..67589c4 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/FakeProjInfo.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/FakeProjInfo.java @@ -32,7 +32,7 @@ public class FakeProjInfo { public Integer shakeTimes = 5; public Integer bigBufferSampleUl = 0; public Integer mixLiquidAspirMixingCnt = 3; - public Integer reactionPlateIncubationTimeMin = 3; + public Integer reactionPlateIncubationTimeMin = 1; public Integer reactionPlateDropletVolUl = 75; diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P01_hsCRR.java b/src/main/java/a8k/service/debug/fakeprojinfo/P01_hsCRR.java index ca7bad4..c4b67b6 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P01_hsCRR.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P01_hsCRR.java @@ -30,7 +30,7 @@ public class P01_hsCRR extends FakeProjInfo { shakeTimes = 5; // 振荡次数 bigBufferSampleUl = 0; // 大缓冲样本体积 mixLiquidAspirMixingCnt = 3; // 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 3; // 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 1; // 反应板孵育时间(min) reactionPlateDropletVolUl = 75; // 反应板滴样体积 } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P02_PCT.java b/src/main/java/a8k/service/debug/fakeprojinfo/P02_PCT.java index 746f807..c2e86d5 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P02_PCT.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P02_PCT.java @@ -20,7 +20,7 @@ public class P02_PCT extends FakeProjInfo { shakeTimes = 5; // 振荡次数 bigBufferSampleUl = 0; // 大缓冲样本体积 mixLiquidAspirMixingCnt = 3; // 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 3; // 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 1; // 反应板孵育时间(min) reactionPlateDropletVolUl = 75; // 反应板滴样体积 } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P03_TSH.java b/src/main/java/a8k/service/debug/fakeprojinfo/P03_TSH.java index f5c7bcb..4d0d7c9 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P03_TSH.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P03_TSH.java @@ -20,7 +20,7 @@ public class P03_TSH extends FakeProjInfo { shakeTimes = 5; // 振荡次数 bigBufferSampleUl = 0; // 大缓冲样本体积 mixLiquidAspirMixingCnt = 3; // 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 3; // 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 1; // 反应板孵育时间(min) reactionPlateDropletVolUl = 75; // 反应板滴样体积 } } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P05_T3.java b/src/main/java/a8k/service/debug/fakeprojinfo/P05_T3.java index 08d1d7a..4aacd52 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P05_T3.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P05_T3.java @@ -20,7 +20,7 @@ public class P05_T3 extends FakeProjInfo { shakeTimes = 5; // 振荡次数 bigBufferSampleUl = 0; // 大缓冲样本体积 mixLiquidAspirMixingCnt = 3; // 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 3; // 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 1; // 反应板孵育时间(min) reactionPlateDropletVolUl = 75; // 反应板滴样体积 } } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P06_T4.java b/src/main/java/a8k/service/debug/fakeprojinfo/P06_T4.java index d9a2ca7..b62f306 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P06_T4.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P06_T4.java @@ -20,7 +20,7 @@ public class P06_T4 extends FakeProjInfo { shakeTimes = 5; // 振荡次数 bigBufferSampleUl = 0; // 大缓冲样本体积 mixLiquidAspirMixingCnt = 3; // 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 3; // 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 1; // 反应板孵育时间(min) reactionPlateDropletVolUl = 75; // 反应板滴样体积 } } diff --git a/src/main/java/a8k/service/debug/fakeprojinfo/P22_PCTAndHsCRP.java b/src/main/java/a8k/service/debug/fakeprojinfo/P22_PCTAndHsCRP.java index 8c0eb42..3f9199d 100644 --- a/src/main/java/a8k/service/debug/fakeprojinfo/P22_PCTAndHsCRP.java +++ b/src/main/java/a8k/service/debug/fakeprojinfo/P22_PCTAndHsCRP.java @@ -20,7 +20,7 @@ public class P22_PCTAndHsCRP extends FakeProjInfo { shakeTimes = 5;// 振荡次数 bigBufferSampleUl = 150;// 大缓冲样本体积 mixLiquidAspirMixingCnt = 3;// 混合液吸液混合次数 - reactionPlateIncubationTimeMin = 12;// 反应板孵育时间(min) + reactionPlateIncubationTimeMin = 2;// 反应板孵育时间(min) reactionPlateDropletVolUl = 75;// 反应板滴样体积 } }