diff --git a/src/main/java/com/dreamworks/boditech/driver/Command.java b/src/main/java/com/dreamworks/boditech/driver/Command.java index 6d24f6e..178cf30 100644 --- a/src/main/java/com/dreamworks/boditech/driver/Command.java +++ b/src/main/java/com/dreamworks/boditech/driver/Command.java @@ -13,6 +13,8 @@ public class Command { public static final Integer CMD_MOTOR_EASY_MOVE_BY = 0x0212; public static final Integer CMD_MOTOR_EASY_MOVE_TO = 0x0213; public static final Integer CMD_XYMOTOR_MOVE_TO = 0x0303; + public static final Integer CMD_PIPETTE_CTRL_INIT_DEVICE = 0x0501; + public static final Integer CMD_PIPETTE_CTRL_MOVE_TO_UL = 0x0503; // command meta data public static final Map cmdMetaMap = new HashMap(); diff --git a/src/main/java/com/dreamworks/boditech/driver/Device.java b/src/main/java/com/dreamworks/boditech/driver/Device.java index 14afa5b..b7ce51c 100644 --- a/src/main/java/com/dreamworks/boditech/driver/Device.java +++ b/src/main/java/com/dreamworks/boditech/driver/Device.java @@ -5,6 +5,7 @@ import com.dreamworks.boditech.driver.consumable.CsmBufferTubeA; import com.dreamworks.boditech.driver.consumable.CsmBufferTubeB; import com.dreamworks.boditech.driver.consumable.CsmLargeBufferTube; import com.dreamworks.boditech.driver.consumable.CsmPipetteTip; +import com.dreamworks.boditech.service.RuntimeOptionService; import com.dreamworks.boditech.utils.MyByteBuffer; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; @@ -28,6 +29,9 @@ public class Device { @Resource private ComSerialPort serialPort; + @Resource + private RuntimeOptionService runtimeOptionService; + // actuators private final Map actuators = new HashMap(); // pipette tips @@ -55,6 +59,7 @@ public class Device { this.appendActuator(new ActMotor(ActuatorModule.TEST_TUBE_SHAKING_CLIP_MOTOR, this)); this.appendActuator(new ActArmXY(ActuatorModule.ARM_XY, this)); this.appendActuator(new ActMotor(ActuatorModule.ARM_Z_MOTOR, this)); + this.appendActuator(new ActPipette(ActuatorModule.ARM_Z_PIPETTE, this)); } // append actuator @@ -122,7 +127,7 @@ public class Device { } // get pipette tip - public CsmPipetteTip getPipeTip() { + public CsmPipetteTip getPipetteTip() { if ( this.pipetteTips.isEmpty() ) { throw new RuntimeException("no pipette tip"); } @@ -157,8 +162,17 @@ public class Device { return tube; } + // get location by name + public Integer getLocationByName( String name ) { + String key = "device.location." + name; + Integer value = this.runtimeOptionService.getInteger(key); + return value; + } + // call device command and wait for response public ByteBuffer call( Integer cmd, Integer mid, Object ... params ) { + + int length = 2 + 2 + 1 + 1 + 2; for ( Object param : params ) { if ( param instanceof Integer ) { diff --git a/src/main/java/com/dreamworks/boditech/driver/actuator/ActArmXY.java b/src/main/java/com/dreamworks/boditech/driver/actuator/ActArmXY.java index eeeecc4..71c219b 100644 --- a/src/main/java/com/dreamworks/boditech/driver/actuator/ActArmXY.java +++ b/src/main/java/com/dreamworks/boditech/driver/actuator/ActArmXY.java @@ -26,4 +26,14 @@ public class ActArmXY extends ActuatorBase { this.call(Command.CMD_XYMOTOR_MOVE_TO, x, y, velocity); this.waitForFinish(); } + + /** + * move to given position by name + * @param name name of position + */ + public void moveTo(String name) { + Integer x = this.getDevice().getLocationByName(name + ".x"); + Integer y = this.getDevice().getLocationByName(name + ".y"); + this.moveTo(x, y); + } } diff --git a/src/main/java/com/dreamworks/boditech/driver/actuator/ActMotor.java b/src/main/java/com/dreamworks/boditech/driver/actuator/ActMotor.java index 490765e..de0715c 100644 --- a/src/main/java/com/dreamworks/boditech/driver/actuator/ActMotor.java +++ b/src/main/java/com/dreamworks/boditech/driver/actuator/ActMotor.java @@ -18,6 +18,12 @@ public class ActMotor extends ActuatorBase { this.waitForFinish(); } + // move to given position by name + public void moveTo( String name ) { + Integer location = this.getDevice().getLocationByName(name); + this.moveTo(location); + } + // move by given distance public void moveBy(int distance) { this.call(Command.CMD_MOTOR_EASY_MOVE_BY, distance); diff --git a/src/main/java/com/dreamworks/boditech/driver/actuator/ActPipette.java b/src/main/java/com/dreamworks/boditech/driver/actuator/ActPipette.java new file mode 100644 index 0000000..31a6398 --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/driver/actuator/ActPipette.java @@ -0,0 +1,46 @@ +package com.dreamworks.boditech.driver.actuator; +import com.dreamworks.boditech.driver.Command; +import com.dreamworks.boditech.driver.Device; +public class ActPipette extends ActuatorBase { + // indicate whether the pipette has tip + // @TODO : this should be false by default + private boolean hasTip = true; + + // constructor + public ActPipette(Integer mid, Device device) { + super(mid, device); + } + + // set whether the pipette has tip + public void setHasTip(boolean hasTip) { + this.hasTip = hasTip; + } + + // get whether the pipette has tip + public boolean getHasTip() { + return this.hasTip; + } + + // init device + public void initDevice() { + this.call(Command.CMD_PIPETTE_CTRL_INIT_DEVICE); + this.waitForFinish(); + } + + // move to given position + public void aspiration(int volume) { + this.call(Command.CMD_PIPETTE_CTRL_MOVE_TO_UL, volume); + this.waitForFinish(); + } + + // move to given position + public void dispense(int volume) { + this.call(Command.CMD_PIPETTE_CTRL_MOVE_TO_UL, volume); + this.waitForFinish(); + } + + // move to given position + public void dispense() { + this.dispense(0); + } +} diff --git a/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorModule.java b/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorModule.java index 2b8eecf..63d26ec 100644 --- a/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorModule.java +++ b/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorModule.java @@ -2,6 +2,7 @@ package com.dreamworks.boditech.driver.actuator; public class ActuatorModule { public static Integer ARM_XY = 11; public static Integer ARM_Z_MOTOR = 81; + public static Integer ARM_Z_PIPETTE = 82; public static Integer TEST_TUBE_SHAKING_MOVE_MOTOR = 31; public static Integer TEST_TUBE_SHAKING_ROTATE_MOTOR = 32; public static Integer TEST_TUBE_SHAKING_CLIP_MOTOR = 33; diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeA.java b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeA.java index a5b4c19..7c47868 100644 --- a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeA.java +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeA.java @@ -1,5 +1,5 @@ package com.dreamworks.boditech.driver.consumable; -public class CsmBufferTubeA { +public class CsmBufferTubeA implements ReactionTube { // area index public Integer areaIndex; // position @@ -8,10 +8,17 @@ public class CsmBufferTubeA { public String projectName = "demo"; public Integer getLocationX() { - return 1000; + // @TODO: 需要根据areaIndex和position计算出x坐标 + return 976; } public Integer getLocationY() { - return 1000; + // @TODO: 需要根据areaIndex和position计算出y坐标 + return 1930; + } + + // get location z + public Integer getLocationZ() { + return 800; } } diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeB.java b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeB.java index cf29b9f..84fe4e7 100644 --- a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeB.java +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmBufferTubeB.java @@ -1,5 +1,5 @@ package com.dreamworks.boditech.driver.consumable; -public class CsmBufferTubeB { +public class CsmBufferTubeB implements ReactionTube { // area index public Integer areaIndex; // position @@ -14,4 +14,9 @@ public class CsmBufferTubeB { public Integer getLocationY() { return 0; } + + // get location z + public Integer getLocationZ() { + return 0; + } } diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmLargeBufferTube.java b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmLargeBufferTube.java index 5bf4511..a52b660 100644 --- a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmLargeBufferTube.java +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmLargeBufferTube.java @@ -15,6 +15,11 @@ public class CsmLargeBufferTube { return 0; } + // get location z + public Integer getLocationZ() { + return 0; + } + // get if the tube is empty public boolean isEmpty() { return false; diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmPipetteTip.java b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmPipetteTip.java index afc7423..2afb34b 100644 --- a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmPipetteTip.java +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmPipetteTip.java @@ -6,10 +6,10 @@ public class CsmPipetteTip { public Integer position; public Integer getLocationX() { - return 500; + return 5089; } public Integer getLocationY() { - return 500; + return -7; } } diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/CsmSampleTube.java b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmSampleTube.java new file mode 100644 index 0000000..5a45572 --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/CsmSampleTube.java @@ -0,0 +1,17 @@ +package com.dreamworks.boditech.driver.consumable; +public class CsmSampleTube { + // get location x + public Integer getLocationX() { + return 0; + } + + // get location y + public Integer getLocationY() { + return 0; + } + + // get location z + public Integer getLocationZ() { + return 1400; + } +} diff --git a/src/main/java/com/dreamworks/boditech/driver/consumable/ReactionTube.java b/src/main/java/com/dreamworks/boditech/driver/consumable/ReactionTube.java new file mode 100644 index 0000000..b797721 --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/driver/consumable/ReactionTube.java @@ -0,0 +1,11 @@ +package com.dreamworks.boditech.driver.consumable; +public interface ReactionTube { + // get location x + public Integer getLocationX(); + + // get location y + public Integer getLocationY(); + + // get location z + public Integer getLocationZ(); +} diff --git a/src/main/java/com/dreamworks/boditech/driver/task/TaskTest.java b/src/main/java/com/dreamworks/boditech/driver/task/TaskTest.java index f333fe2..7a4a893 100644 --- a/src/main/java/com/dreamworks/boditech/driver/task/TaskTest.java +++ b/src/main/java/com/dreamworks/boditech/driver/task/TaskTest.java @@ -2,6 +2,9 @@ package com.dreamworks.boditech.driver.task; import com.dreamworks.boditech.driver.Task; import com.dreamworks.boditech.driver.TaskExecutor; import com.dreamworks.boditech.driver.TestStep; +import com.dreamworks.boditech.driver.consumable.CsmBufferTubeA; +import com.dreamworks.boditech.driver.consumable.CsmSampleTube; +import com.dreamworks.boditech.driver.consumable.ReactionTube; import com.dreamworks.boditech.driver.task.step.Step; import com.dreamworks.boditech.driver.task.step.StepManager; @@ -27,10 +30,39 @@ public class TaskTest implements Task { public List steps; // step index public Integer stepIndex; + // reaction tube + private ReactionTube reactionTube; + // sample tube + private CsmSampleTube sampleTube; + + // set reaction tube + public void setReactionTube(ReactionTube reactionTube) { + this.reactionTube = reactionTube; + } + + // get reaction tube + public ReactionTube getReactionTube() { + return this.reactionTube; + } + + // set sample tube + public void setSampleTube(CsmSampleTube sampleTube) { + this.sampleTube = sampleTube; + } + + // get sample tube + public CsmSampleTube getSampleTube() { + return this.sampleTube; + } + @Override public void execute(TaskExecutor executor) { - List steps = StepManager.buildSteps("[{\"action\":\"puncture\",\"tubeType\":\"A\"}]"); + // @TODO : use real sample tube + this.sampleTube = new CsmSampleTube(); + this.reactionTube = new CsmBufferTubeA(); + + List steps = StepManager.buildSteps("[{\"action\":\"sampling\",\"sourceTubeType\":\"Sample\",\"amount\":\"100\"}]"); for ( Step step : steps ) { step.execute(executor, this); } diff --git a/src/main/java/com/dreamworks/boditech/driver/task/step/StepManager.java b/src/main/java/com/dreamworks/boditech/driver/task/step/StepManager.java index fa319a2..be4a977 100644 --- a/src/main/java/com/dreamworks/boditech/driver/task/step/StepManager.java +++ b/src/main/java/com/dreamworks/boditech/driver/task/step/StepManager.java @@ -17,6 +17,7 @@ public class StepManager { } StepManager.stepMap.put("shake", StepShake.class); StepManager.stepMap.put("puncture", StepPuncture.class); + StepManager.stepMap.put("sampling", StepSampling.class); } // build step by given name diff --git a/src/main/java/com/dreamworks/boditech/driver/task/step/StepPuncture.java b/src/main/java/com/dreamworks/boditech/driver/task/step/StepPuncture.java index d8d82c2..cd37063 100644 --- a/src/main/java/com/dreamworks/boditech/driver/task/step/StepPuncture.java +++ b/src/main/java/com/dreamworks/boditech/driver/task/step/StepPuncture.java @@ -25,7 +25,7 @@ public class StepPuncture extends StepBase { TaskTest taskTest = (TaskTest)task; // 01. 移动到TIP区域 - CsmPipetteTip tip = device.getPipeTip(); + CsmPipetteTip tip = device.getPipetteTip(); armXY.moveTo(tip.getLocationX(), tip.getLocationY()); // 02. 下降到TIP区域 diff --git a/src/main/java/com/dreamworks/boditech/driver/task/step/StepSampling.java b/src/main/java/com/dreamworks/boditech/driver/task/step/StepSampling.java new file mode 100644 index 0000000..c72492f --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/driver/task/step/StepSampling.java @@ -0,0 +1,116 @@ +package com.dreamworks.boditech.driver.task.step; +import com.dreamworks.boditech.driver.Device; +import com.dreamworks.boditech.driver.Task; +import com.dreamworks.boditech.driver.TaskExecutor; +import com.dreamworks.boditech.driver.actuator.ActArmXY; +import com.dreamworks.boditech.driver.actuator.ActMotor; +import com.dreamworks.boditech.driver.actuator.ActPipette; +import com.dreamworks.boditech.driver.actuator.ActuatorModule; +import com.dreamworks.boditech.driver.consumable.*; +import com.dreamworks.boditech.driver.task.TaskTest; +import com.fasterxml.jackson.annotation.JsonProperty; +public class StepSampling extends StepBase { + @JsonProperty("action") + public String action; + + @JsonProperty("sourceTubeType") + public String sourceTubeType; + + @JsonProperty("amount") + public String amount; + + @JsonProperty("dropTip") + public String dropTip = "YES"; + + @JsonProperty("requireNewTip") + public String requireNewTip = "YES"; + + @Override + public void execute(TaskExecutor executor, Task task) { + Device device = executor.getDevice(); + ActPipette pipette = (ActPipette)device.getActuator(ActuatorModule.ARM_Z_PIPETTE); + ActArmXY armXY = (ActArmXY)device.getActuator(ActuatorModule.ARM_XY); + ActMotor armZMotor = (ActMotor)device.getActuator(ActuatorModule.ARM_Z_MOTOR); + TaskTest taskTest = (TaskTest)task; + + // 丢弃TIP + if ( this.requireNewTip.equals("YES") && pipette.getHasTip() ) { + armXY.moveTo("tipDiscardPrepareHole"); + armZMotor.moveTo("tipDiscardDepth"); + armXY.moveTo("tipDiscardDrop"); + armZMotor.moveTo(0); + armXY.moveTo(0, 0); + pipette.setHasTip(false); + } + + // get tip + if ( !pipette.getHasTip() ) { + CsmPipetteTip tip = device.getPipetteTip(); + armXY.moveTo(tip.getLocationX(), tip.getLocationY()); + armZMotor.moveTo("tipPickUpDepth"); + armZMotor.moveTo(0); + pipette.setHasTip(true); + } + + // 1. 获取样本 + // 2. 移动到样本采集位置 + Integer srcTubeLocationZ = 0; + if ( this.sourceTubeType.equals("LargeBufferTube") ) { + CsmLargeBufferTube tube = device.getLargeBufferTube(taskTest.projectName); + armXY.moveTo(tube.getLocationX(), tube.getLocationY()); + srcTubeLocationZ = tube.getLocationZ(); + } else if ( this.sourceTubeType.equals("BufferTubeA") ) { + CsmBufferTubeA tube = device.getBufferTubeA(taskTest.projectName); + armXY.moveTo(tube.getLocationX(), tube.getLocationY()); + srcTubeLocationZ = tube.getLocationZ(); + } else if ( this.sourceTubeType.equals("BufferTubeB") ) { + CsmBufferTubeB tube = device.getBufferTubeB(taskTest.projectName); + armXY.moveTo(tube.getLocationX(), tube.getLocationY()); + srcTubeLocationZ = tube.getLocationZ(); + } else if ( this.sourceTubeType.equals("Sample") ) { + armXY.moveTo("sampleTestTube"); + srcTubeLocationZ = taskTest.getSampleTube().getLocationZ(); + } else { + throw new RuntimeException("unknown source tube type " + this.sourceTubeType); + } + + // 3. 下降到样本采集位置 + armZMotor.moveTo(srcTubeLocationZ); + + // 4. 吸入样本 + pipette.aspiration(Integer.parseInt(this.amount)); + + // 5. 上升到准备位置 + armZMotor.moveTo(0); + + // 6. 移动到反应试管放置位置 + ReactionTube reactionTube = taskTest.getReactionTube(); + armXY.moveTo(reactionTube.getLocationX(), reactionTube.getLocationY()); + + // 7. 下降到反应试管放置位置 + armZMotor.moveTo(reactionTube.getLocationZ()); + + // 8. 吐出样本 + pipette.dispense(); + + // 9. 吞吐混合均匀 + int mixAmount = Integer.parseInt(this.amount); + for ( int i = 0; i < 3; i++ ) { + pipette.aspiration(mixAmount); + pipette.dispense(); + } + + // 10. 上升到准备位置 + armZMotor.moveTo(0); + + // 丢弃TIP + if ( this.dropTip.equals("YES") ) { + armXY.moveTo("tipDiscardPrepareHole"); + armZMotor.moveTo("tipDiscardDepth"); + armXY.moveTo("tipDiscardDrop"); + armZMotor.moveTo(0); + armXY.moveTo(0, 0); + pipette.setHasTip(false); + } + } +} diff --git a/src/main/java/com/dreamworks/boditech/mapper/OptionMapper.java b/src/main/java/com/dreamworks/boditech/mapper/OptionMapper.java new file mode 100644 index 0000000..28c45aa --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/mapper/OptionMapper.java @@ -0,0 +1,8 @@ +package com.dreamworks.boditech.mapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +@Mapper +public interface OptionMapper { + @Select("SELECT `value` FROM bdt_options WHERE name=#{name} LIMIT 1") + String findByName(String name); +} diff --git a/src/main/java/com/dreamworks/boditech/service/RuntimeOptionService.java b/src/main/java/com/dreamworks/boditech/service/RuntimeOptionService.java new file mode 100644 index 0000000..dc057fa --- /dev/null +++ b/src/main/java/com/dreamworks/boditech/service/RuntimeOptionService.java @@ -0,0 +1,18 @@ +package com.dreamworks.boditech.service; +import com.dreamworks.boditech.mapper.OptionMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +@Service +public class RuntimeOptionService { + @Resource + private OptionMapper optionMapper; + + // get integer value + public Integer getInteger(String name) { + String value = this.optionMapper.findByName(name); + if ( value == null ) { + throw new RuntimeException("option not found: " + name); + } + return Integer.parseInt(value); + } +}