diff --git a/app.db b/app.db index a000dab..57905b2 100644 Binary files a/app.db and b/app.db differ diff --git a/src/main/java/com/iflytop/digester/DigestionTaskThread.java b/src/main/java/com/iflytop/digester/DigestionTaskThread.java index fb31728..007773a 100644 --- a/src/main/java/com/iflytop/digester/DigestionTaskThread.java +++ b/src/main/java/com/iflytop/digester/DigestionTaskThread.java @@ -8,9 +8,7 @@ import com.iflytop.digester.underframework.dao.model.UfMdbOption; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.util.*; - public class DigestionTaskThread extends Thread { // 任务完成回调 public interface FinishCallback { @@ -148,6 +146,7 @@ public class DigestionTaskThread extends Thread { this.taskModel.save(); if ( "auto".equals(this.taskModel.mode) ) { + this.lockLiquidPlate(); // 打开门 device.door.open(); // 等待放入试管架 @@ -175,6 +174,7 @@ public class DigestionTaskThread extends Thread { // 搬运到加热转盘 device.transferArm.moveTubeRackToHeatingTurntable(this.heatingSlot.index); + this.unlockLiquidPlate(); // 加热 for ( var heating : digestionRound.heatings ) { @@ -184,6 +184,7 @@ public class DigestionTaskThread extends Thread { } // 移至加液区, 以便执行下一轮 + this.lockLiquidPlate(); device.transferArm.moveTubeRackToLiquidPlate(this.heatingSlot.index); this.updateTaskStatus("RoundFinish", "配置轮次执行结束"); } @@ -204,6 +205,9 @@ public class DigestionTaskThread extends Thread { } this.updateTaskStatus("Liquid", "摇匀"); liquidAddition.shake(digestionRound.shakingCount); + // 搬运到加热转盘 + device.transferArm.moveTubeRackToHeatingTurntable(this.heatingSlot.index); + this.unlockLiquidPlate(); // 预检查定时, 在结束前10分钟执行 Integer totalDurationCount = 0; @@ -245,6 +249,10 @@ public class DigestionTaskThread extends Thread { this.updateTaskStatus("PreCheckWait", "预检完成"); } + // 移动到加液盘准备取出 + this.lockLiquidPlate(); + device.transferArm.moveTubeRackToLiquidPlate(this.heatingSlot.index); // 移动到加液盘 + // 加水定容 if ( 0 < digestionRound.waterVolume ) { this.updateTaskStatus("Water", String.format("加水定容 : %d", digestionRound.waterVolume)); @@ -253,12 +261,12 @@ public class DigestionTaskThread extends Thread { // 正常消解结束; this.updateTaskStatus("LastRound", "取出试管架"); - device.transferArm.moveTubeRackToLiquidPlate(this.heatingSlot.index); // 移动到加液盘 device.door.open(); // 打开门 this.waitForTubeRackTakeOut(); // 等待取出 device.door.close(); // 关闭门 this.heatingSlot.setTubeRackNo(null); // 释放加热位 this.updateTaskStatus("LastRound", "执行最后一轮配置结束"); + this.unlockLiquidPlate(); } // 执行消解最后一轮预检查 @@ -268,6 +276,8 @@ public class DigestionTaskThread extends Thread { // 检查试管 var device = Device.getInstance(); + this.lockLiquidPlate(); + // 移动到加液盘 device.transferArm.moveTubeRackToLiquidPlate(this.heatingSlot.index); // 拍照检查异常试管 @@ -302,6 +312,7 @@ public class DigestionTaskThread extends Thread { // 将正常试管放入加热转盘 device.transferArm.moveTubeRackToHeatingTurntable(this.heatingSlot.index); + this.unlockLiquidPlate(); // 启动异常处理线程 DigestionTaskThread.this.errorProcessThread = new Thread(DigestionTaskThread.this::executeErrorProcess); @@ -361,6 +372,7 @@ public class DigestionTaskThread extends Thread { this.updateTaskStatus("TubeCheck", String.format("取出剩余异常试管 : [%s]", errorTubeIndexes)); // 如果还存在异常, 则直接取出 var device = Device.getInstance(); + this.lockLiquidPlate(); // 等待放入空试管架 device.door.open(); this.waitForEmptyTubeRackPutIn(); @@ -377,6 +389,7 @@ public class DigestionTaskThread extends Thread { device.door.open(); this.waitForTubeRackTakeOut(); device.door.close(); + this.unlockLiquidPlate(); } catch (InterruptedException e) { UfMdbNotification.error("消解异常处理失败 : " + e.getMessage()); this.updateTaskStatus("Error", String.format("消解异常处理失败 : %s", e.getMessage())); @@ -399,6 +412,7 @@ public class DigestionTaskThread extends Thread { } this.updateTaskStatus("ErrorRound", "移动异常试管架至加液区"); + this.lockLiquidPlate(); device.transferArm.moveTubeRackToLiquidPlate(errorSlot.index); var liquidAddition = device.liquidAddition; @@ -412,6 +426,7 @@ public class DigestionTaskThread extends Thread { this.updateTaskStatus("ErrorRound", "移动异常试管架至加热区"); device.transferArm.moveTubeRackToHeatingTurntable(errorSlot.index); + this.unlockLiquidPlate(); for ( var heating : errorRound.heatings ) { this.updateTaskStatus("ErrorRoundHeating", String.format("异常加热 %d℃ %d分钟", heating.temperature, heating.duration)); @@ -420,19 +435,21 @@ public class DigestionTaskThread extends Thread { } this.updateTaskStatus("ErrorRound", "移动异常试管架至加液区"); + this.lockLiquidPlate(); device.transferArm.moveTubeRackToLiquidPlate(errorSlot.index); // 拍照检查是否存在消解完成的试管 this.executeErrorRoundTakeShotAndCheckFinishedTubes(); + this.updateTaskStatus("ErrorRound", "移动异常试管架至加热区"); + device.transferArm.moveTubeRackToHeatingTurntable(errorSlot.index); + // 如果不存在消解完成的试管则本轮次结束 if (this.finishedErrorTubeIndexes.isEmpty()) { + this.unlockLiquidPlate(); return ; } - this.updateTaskStatus("ErrorRound", "移动异常试管架至加热区"); - device.transferArm.moveTubeRackToHeatingTurntable(errorSlot.index); - // 等待放入空试管架 device.door.open(); this.waitForEmptyTubeRackPutIn(); @@ -446,6 +463,7 @@ public class DigestionTaskThread extends Thread { device.door.open(); this.waitForTubeRackTakeOut(); device.door.close(); + this.unlockLiquidPlate(); // 更新异常试管索引列表 this.errorTubeIndexes.removeAll(this.finishedErrorTubeIndexes); @@ -539,6 +557,31 @@ public class DigestionTaskThread extends Thread { this.updateTaskStatus("TubeRackTakeOutWaitDone", "试管架已取出"); } + // 使用加液盘 + private void lockLiquidPlate() { + var lockName = this.taskModel.id; + if ( "manual".equals(this.taskModel.mode) ) { + lockName = this.taskModel.batchNo; + } + + var device = Device.getInstance(); + this.updateTaskStatus("WaitForLiquidPlate", "等待加液盘空闲"); + device.liquidAddition.lockLiquidPlate(lockName); + this.updateTaskStatus("WaitForLiquidPlate", "锁定加液盘"); + } + + // 释放加液盘 + private void unlockLiquidPlate() { + var lockName = this.taskModel.id; + if ( "manual".equals(this.taskModel.mode) ) { + lockName = this.taskModel.batchNo; + } + + var device = Device.getInstance(); + this.updateTaskStatus("WaitForLiquidPlate", "释放加液盘"); + device.liquidAddition.unlockLiquidPlate(lockName); + } + // 更新任务状态 private void updateTaskStatus(String status, String message) { LOG.info("[Digestion Task : {}] ({}) {}", taskModel.id, status, message); diff --git a/src/main/java/com/iflytop/digester/controller/HeatSlotController.java b/src/main/java/com/iflytop/digester/controller/HeatSlotController.java index 18436e5..26960a4 100644 --- a/src/main/java/com/iflytop/digester/controller/HeatSlotController.java +++ b/src/main/java/com/iflytop/digester/controller/HeatSlotController.java @@ -8,6 +8,8 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import java.util.Map; +import java.util.UUID; + @Controller public class HeatSlotController extends UfApiControllerBase { @Resource @@ -26,6 +28,7 @@ public class HeatSlotController extends UfApiControllerBase { String tubeRackNo = (String)params.get("tubeRackNo"); try { var slot = this.device.heatingTurntable.allocSlot(tubeRackNo); + this.device.liquidAddition.lockLiquidPlate(tubeRackNo); return this.success(slot); } catch ( Exception e ) { return this.error(e.getMessage()); @@ -38,6 +41,7 @@ public class HeatSlotController extends UfApiControllerBase { Integer slotIndex = (Integer)params.get("slotIndex"); try { var slot = this.device.heatingTurntable.getSlotByIndex(slotIndex); + this.device.liquidAddition.unlockLiquidPlate(slot.tubeRackNo); slot.setTubeRackNo(null); return this.success(); } catch ( Exception e ) { diff --git a/src/main/java/com/iflytop/digester/deviceinstance/HeatingTurntableSlot.java b/src/main/java/com/iflytop/digester/deviceinstance/HeatingTurntableSlot.java index 5c261b0..0455846 100644 --- a/src/main/java/com/iflytop/digester/deviceinstance/HeatingTurntableSlot.java +++ b/src/main/java/com/iflytop/digester/deviceinstance/HeatingTurntableSlot.java @@ -1,11 +1,9 @@ package com.iflytop.digester.deviceinstance; import com.iflytop.digester.underframework.UfActuatorCmdExecutor; import com.iflytop.digester.underframework.UfCmdSnippetExecutor; - import java.util.ArrayList; import java.util.List; import java.util.Map; - public class HeatingTurntableSlot { // 槽位索引 public Integer index = -1; diff --git a/src/main/java/com/iflytop/digester/deviceinstance/LiquidAdditionInstance.java b/src/main/java/com/iflytop/digester/deviceinstance/LiquidAdditionInstance.java index 37d5e01..8e6167b 100644 --- a/src/main/java/com/iflytop/digester/deviceinstance/LiquidAdditionInstance.java +++ b/src/main/java/com/iflytop/digester/deviceinstance/LiquidAdditionInstance.java @@ -17,9 +17,12 @@ public class LiquidAdditionInstance { private List liquids; // lock for bucket empty private final Object bucketEmptyLock = new Object(); + // locks for liquid plate + private final List liquidPlateLocks = new ArrayList<>(); // setup public void setup() { + this.liquidPlateLocks.clear(); this.liquids = new ArrayList<>(); for (int i = 0; i < 8; i++) { LiquidAdditionLiquid liquid = new LiquidAdditionLiquid(); @@ -31,6 +34,43 @@ public class LiquidAdditionInstance { } } + // lock liquid plate + public void lockLiquidPlate( String lockName ) { + MdbRuntimeLog.log("LiquidPlateLock", "加液盘锁定等待 : %s", lockName); + this.liquidPlateLocks.add(lockName); + if ( 1 == this.liquidPlateLocks.size() ) { + MdbRuntimeLog.log("LiquidPlateLock", "加液盘锁定成功 : %s", lockName); + return ; + } + synchronized ( lockName ) { + try { + lockName.wait(); + MdbRuntimeLog.log("LiquidPlateLock", "加液盘锁定成功 : %s", lockName); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + // unlock liquid plate + public void unlockLiquidPlate( String lockName ) { + MdbRuntimeLog.log("LiquidPlateLock", "加液盘锁定解除 : %s", lockName); + String lock = null; + for ( String lockItem : this.liquidPlateLocks ) { + if ( lockItem.equals(lockName) ) { + lock = lockItem; + } + } + this.liquidPlateLocks.remove(lock); + if (this.liquidPlateLocks.isEmpty()) { + return ; + } + Object nextLock = this.liquidPlateLocks.remove(0); + synchronized ( nextLock ) { + nextLock.notify(); + } + } + // get liquids public List getLiquids() { return this.liquids; @@ -95,6 +135,13 @@ public class LiquidAdditionInstance { // 检查液体量 private void bucketVolumeCheck( Integer pumpIndex ) { + var env = UfApplication.getApp().getEnv(); + Boolean bucketVolumeCheckEnable = env.getProperty("app.bucketVolumeCheckEnable", Boolean.class); + assert null != bucketVolumeCheckEnable; + if ( !bucketVolumeCheckEnable ) { + return ; + } + var liquid = liquids.get((0==pumpIndex%2) ? pumpIndex/2 : (pumpIndex-1)/2); var stateStr = UfActuatorCmdExecutor.execute("LiquidBucketVolumeIO", "module_readio"); var state = Integer.parseInt(stateStr); @@ -108,7 +155,6 @@ public class LiquidAdditionInstance { liquid.setVolume(0); // 如果缺少的液体不是水, 则需要发送通知手动补充 - var env = UfApplication.getApp().getEnv(); Integer waterBucketIndex = env.getProperty("app.liquidWaterBucketIndex", Integer.class); assert waterBucketIndex != null; if (!waterBucketIndex.equals(liquid.bucketIndex)) { diff --git a/src/main/java/com/iflytop/digester/underframework/dao/record/UfActiveRecord.java b/src/main/java/com/iflytop/digester/underframework/dao/record/UfActiveRecord.java index ae51de4..9bd410e 100644 --- a/src/main/java/com/iflytop/digester/underframework/dao/record/UfActiveRecord.java +++ b/src/main/java/com/iflytop/digester/underframework/dao/record/UfActiveRecord.java @@ -28,7 +28,9 @@ public class UfActiveRecord { String tableName = UfActiveRecord.getTableNameFromModelClass(this.getClass()); Map data = UfActiveRecord.exportModelData(this); if ( this.isNewRecord ) { - this.id = UUID.randomUUID().toString(); + if ( null == this.id ) { + this.id = UUID.randomUUID().toString(); + } data.put("id", this.id); mapper.insert(tableName, data); this.isNewRecord = false;