From cab084f03fff36185f3879b7afad20dd3357fb31 Mon Sep 17 00:00:00 2001 From: sige Date: Fri, 10 May 2024 09:22:28 +0800 Subject: [PATCH] 1 --- app.db | Bin 114688 -> 147456 bytes pom.xml | 2 +- .../java/com/iflytop/a800/ResourceLockManager.java | 53 ++++++++++ src/main/java/com/iflytop/a800/Task.java | 5 + src/main/java/com/iflytop/a800/TaskBase.java | 36 +++++++ src/main/java/com/iflytop/a800/TaskManager.java | 28 +++++ .../iflytop/a800/controller/DemoController.java | 25 +++++ .../iflytop/a800/controller/TaskController.java | 22 ++++ src/main/java/com/iflytop/a800/device/Device.java | 14 +++ src/main/java/com/iflytop/a800/device/Feeder.java | 42 ++++++++ .../java/com/iflytop/a800/device/Incubator.java | 4 + src/main/java/com/iflytop/a800/device/Pipette.java | 117 +++++++++++++++++++++ src/main/java/com/iflytop/a800/device/Scanner.java | 4 + src/main/java/com/iflytop/a800/device/Shaker.java | 4 + .../com/iflytop/a800/device/TestCardWarehouse.java | 4 + .../java/com/iflytop/a800/resource/TestTube.java | 10 ++ .../com/iflytop/a800/resource/TestTubeRack.java | 8 ++ .../java/com/iflytop/a800/task/TubeRackTask.java | 73 +++++++++++++ .../java/com/iflytop/a800/task/TubeTestTask.java | 52 +++++++++ 19 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/iflytop/a800/ResourceLockManager.java create mode 100644 src/main/java/com/iflytop/a800/Task.java create mode 100644 src/main/java/com/iflytop/a800/TaskBase.java create mode 100644 src/main/java/com/iflytop/a800/TaskManager.java create mode 100644 src/main/java/com/iflytop/a800/controller/DemoController.java create mode 100644 src/main/java/com/iflytop/a800/controller/TaskController.java create mode 100644 src/main/java/com/iflytop/a800/device/Device.java create mode 100644 src/main/java/com/iflytop/a800/device/Feeder.java create mode 100644 src/main/java/com/iflytop/a800/device/Incubator.java create mode 100644 src/main/java/com/iflytop/a800/device/Pipette.java create mode 100644 src/main/java/com/iflytop/a800/device/Scanner.java create mode 100644 src/main/java/com/iflytop/a800/device/Shaker.java create mode 100644 src/main/java/com/iflytop/a800/device/TestCardWarehouse.java create mode 100644 src/main/java/com/iflytop/a800/resource/TestTube.java create mode 100644 src/main/java/com/iflytop/a800/resource/TestTubeRack.java create mode 100644 src/main/java/com/iflytop/a800/task/TubeRackTask.java create mode 100644 src/main/java/com/iflytop/a800/task/TubeTestTask.java diff --git a/app.db b/app.db index e9185c016a2a8062b62aab0074498c533deb2c07..fce7551fb23342d8f79e091665545c71419269a3 100644 GIT binary patch literal 147456 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

8MgjLY7qMXyHRLHr* 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 com.iflytop uf - 0.0.12 + 0.0.19 diff --git a/src/main/java/com/iflytop/a800/ResourceLockManager.java b/src/main/java/com/iflytop/a800/ResourceLockManager.java new file mode 100644 index 0000000..85ce74d --- /dev/null +++ b/src/main/java/com/iflytop/a800/ResourceLockManager.java @@ -0,0 +1,53 @@ +package com.iflytop.a800; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +public class ResourceLockManager { + // lock map + private final Map> locks = new HashMap<>(); + // singleton + private static final ResourceLockManager instance = new ResourceLockManager(); + + // get instance + public static ResourceLockManager getInstance() { + return instance; + } + + // lock + public Object lock( String resName ) { + Object lock = new Object(); + synchronized ( this.locks ) { + var locks = this.locks.computeIfAbsent(resName, k -> new ArrayList<>()); + if ( locks.isEmpty() ) { + locks.add(lock); + return lock; + } + locks.add(lock); + } + synchronized ( lock ) { + try { + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return lock; + } + + // unlock + public void unlock( String resName, Object lock ) { + Object nextLock = null; + synchronized ( this.locks ) { + var locks = this.locks.get(resName); + if ( null == locks ) { + return ; + } + locks.remove(lock); + nextLock = locks.isEmpty() ? null : locks.get(0); + } + synchronized ( nextLock ) { + nextLock.notify(); + } + } +} diff --git a/src/main/java/com/iflytop/a800/Task.java b/src/main/java/com/iflytop/a800/Task.java new file mode 100644 index 0000000..ed56ef3 --- /dev/null +++ b/src/main/java/com/iflytop/a800/Task.java @@ -0,0 +1,5 @@ +package com.iflytop.a800; +public interface Task { + // start this task + void start(); +} diff --git a/src/main/java/com/iflytop/a800/TaskBase.java b/src/main/java/com/iflytop/a800/TaskBase.java new file mode 100644 index 0000000..0be7bd0 --- /dev/null +++ b/src/main/java/com/iflytop/a800/TaskBase.java @@ -0,0 +1,36 @@ +package com.iflytop.a800; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +abstract public class TaskBase extends Thread implements Task { + // 事件回调 + public interface EventCallback { + void callback( Task task, List args ); + } + + // 事件回调列表 + public Map> eventCallbacks = new HashMap<>(); + + // 追加事件回调 + public void on( String name, EventCallback callback ) { + if ( !eventCallbacks.containsKey(name) ) { + eventCallbacks.put(name, new ArrayList<>()); + } + eventCallbacks.get(name).add(callback); + } + + // 触发事件 + public void emit( String name, List args ) { + if ( !eventCallbacks.containsKey(name) ) { + return; + } + for ( var callback : eventCallbacks.get(name) ) { + callback.callback(this, args); + } + } + + + public void lockRes() {} + public void unlockRes() {} +} diff --git a/src/main/java/com/iflytop/a800/TaskManager.java b/src/main/java/com/iflytop/a800/TaskManager.java new file mode 100644 index 0000000..2f233ee --- /dev/null +++ b/src/main/java/com/iflytop/a800/TaskManager.java @@ -0,0 +1,28 @@ +package com.iflytop.a800; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; +@Component +public class TaskManager { + // list of tasks + private final List tasks = new ArrayList(); + // singleton instance + private static TaskManager instance; + + // get instance + public static TaskManager getInstance() { + return instance; + } + + @PostConstruct + public void init() { + instance = this; + } + + // append task + public void append( Task task ) { + tasks.add(task); + task.start(); + } +} diff --git a/src/main/java/com/iflytop/a800/controller/DemoController.java b/src/main/java/com/iflytop/a800/controller/DemoController.java new file mode 100644 index 0000000..919480c --- /dev/null +++ b/src/main/java/com/iflytop/a800/controller/DemoController.java @@ -0,0 +1,25 @@ +package com.iflytop.a800.controller; +import com.iflytop.a800.device.Device; +import com.iflytop.uf.controller.UfApiControllerBase; +import com.iflytop.uf.controller.UfApiResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +@Controller +public class DemoController extends UfApiControllerBase { + @PostMapping("/api/demo/pipette-tip-pick-up") + @ResponseBody + public UfApiResponse pipetteTipPickUp() { + var pipette = Device.getInstance().pipette; + pipette.tipPickUp(); + return this.success(); + } + + @PostMapping("/api/demo/pipette-tip-drop") + @ResponseBody + public UfApiResponse pipetteTipDrop() { + var pipette = Device.getInstance().pipette; + pipette.tipDrop(); + return this.success(); + } +} diff --git a/src/main/java/com/iflytop/a800/controller/TaskController.java b/src/main/java/com/iflytop/a800/controller/TaskController.java new file mode 100644 index 0000000..287c41d --- /dev/null +++ b/src/main/java/com/iflytop/a800/controller/TaskController.java @@ -0,0 +1,22 @@ +package com.iflytop.a800.controller; +import com.iflytop.a800.TaskManager; +import com.iflytop.a800.task.TubeRackTask; +import com.iflytop.uf.controller.UfApiControllerBase; +import com.iflytop.uf.controller.UfApiResponse; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +@Controller +public class TaskController extends UfApiControllerBase { + @Resource + private TaskManager taskManager; + + @PostMapping("/api/task/tube-rack-append") + @ResponseBody + public UfApiResponse tubeRackAppend() { + var task = new TubeRackTask(); + this.taskManager.append(task); + return this.success(); + } +} diff --git a/src/main/java/com/iflytop/a800/device/Device.java b/src/main/java/com/iflytop/a800/device/Device.java new file mode 100644 index 0000000..2e6a099 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Device.java @@ -0,0 +1,14 @@ +package com.iflytop.a800.device; +public class Device { + // singleton instance + private static final Device instance = new Device(); + // feeder + public final Feeder feeder = new Feeder(); + // pipette + public final Pipette pipette = new Pipette(); + + // get instance + public static Device getInstance() { + return instance; + } +} diff --git a/src/main/java/com/iflytop/a800/device/Feeder.java b/src/main/java/com/iflytop/a800/device/Feeder.java new file mode 100644 index 0000000..8e7b4b2 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Feeder.java @@ -0,0 +1,42 @@ +package com.iflytop.a800.device; +import com.iflytop.uf.UfCmdSnippetExecutor; +public class Feeder { + // 试管架进料 + public void feed() { + UfCmdSnippetExecutor.execute("FeedTubeRackFeed"); + } + + // 试管架出料 + public void exit() { + UfCmdSnippetExecutor.execute("FeedTubeRackExit"); + } + + // 读取试管架类型 + public Number readTubeRackType() { + UfCmdSnippetExecutor.execute("FeedTubeRackTypeReadPrepare"); + return 1; + } + + // 读取试管是否存在 + public Boolean readIsTestTubeExisted(int i) { + UfCmdSnippetExecutor.execute("FeedTubeRackTubeExistsCheckPrepare"); + return false; + } + + // 读取是否为5ml管 + public Boolean readIsWb5ml(int i) { + UfCmdSnippetExecutor.execute("FeedTubeRackTubeIsWb5mlCheckPrepare"); + return true; + } + + // 读取条码 + public String readBarCode(int i) { + UfCmdSnippetExecutor.execute("FeedTubeRackTubeBarCodeScan"); + return "1222"; + } + + // 准备 + public void prepareForTesting() { + UfCmdSnippetExecutor.execute("FeedTubeRackPrepareForTesting"); + } +} diff --git a/src/main/java/com/iflytop/a800/device/Incubator.java b/src/main/java/com/iflytop/a800/device/Incubator.java new file mode 100644 index 0000000..323c007 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Incubator.java @@ -0,0 +1,4 @@ +package com.iflytop.a800.device; + +public class Incubator { +} diff --git a/src/main/java/com/iflytop/a800/device/Pipette.java b/src/main/java/com/iflytop/a800/device/Pipette.java new file mode 100644 index 0000000..6a7c9ed --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Pipette.java @@ -0,0 +1,117 @@ +package com.iflytop.a800.device; +import com.iflytop.uf.UfActuatorCmdExecutor; +import com.iflytop.uf.UfCmdSnippetExecutor; +import com.iflytop.uf.model.UfMdbOption; +import com.iflytop.uf.util.UfCommon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +public class Pipette { + // logger + public static final Logger LOG = LoggerFactory.getLogger(Pipette.class); + // if pipette has tip + private Boolean hasTip = false; + // tip amount list + private final List tipAmountList = new ArrayList<>(); + + // + public Pipette() { + // @TODO : 删除下面三行,测试用 + this.tipAmountList.add(0); + this.tipAmountList.add(120-29); + this.tipAmountList.add(120); + } + + // set tip amount + public void setTipAmount( Integer index, Integer amount ) { + this.tipAmountList.set(index, amount); + } + + // pick up tip + public void tipPickUp() { + if ( this.hasTip ) { + return ; + } + + int zoneIndex = -1; + int tipIndex = -1; + for ( int i = 0; i < this.tipAmountList.size(); i++ ) { + if ( this.tipAmountList.get(i) > 0 ) { + zoneIndex = i; + tipIndex = 120 - this.tipAmountList.get(i); + break; + } + } + if ( -1 == zoneIndex ) { + throw new RuntimeException("No tip available"); + } + this.tipAmountList.set(zoneIndex, 120 - tipIndex -1); + + Integer zoneStartX = UfMdbOption.getInteger(String.format("PipetteTipZoneStartX.%d", zoneIndex), 0); + Integer zoneStartY = UfMdbOption.getInteger(String.format("PipetteTipZoneStartY.%d", zoneIndex), 0); + Integer zoneZ = UfMdbOption.getInteger(String.format("PipetteTipZoneZ.%d", zoneIndex), 0); + Integer distanceX = UfMdbOption.getInteger("PipetteTipDistanceX", 0); + Integer distanceY = UfMdbOption.getInteger("PipetteTipDistanceY", 0); + Integer indexX = tipIndex % 12; + Integer indexY = tipIndex / 12; + Integer x = zoneStartX + indexX * distanceX; + Integer y = zoneStartY + indexY * distanceY; + + Map pickUpParams = Map.of("tipX", x, "tipY", y, "tipZ", zoneZ); + // 尝试三次拾取枪头 + for ( int i=0; i<3; i++ ) { + LOG.info("[Pipette] Pick up tip at [{},{}](x={}, y={}, z={})", indexX, indexY, x, y, zoneZ); + UfCmdSnippetExecutor.execute("PipetteTipPickUp", pickUpParams); + String tipState = UfActuatorCmdExecutor.execute("Pipette", "read_pipette_tip_state"); + if ("1".equals(tipState)) { + break ; + } + + } + + String tipState = UfActuatorCmdExecutor.execute("Pipette", "read_pipette_tip_state"); + if ("0".equals(tipState)) { + this.tipPickUp(); + return ; // 如果拾取失败,则跳过这个TIP取下一个 + } + this.hasTip = true; + } + + // 丢弃枪头 + public void tipDrop() { + if ( !this.hasTip ) { + return ; + } + + LOG.info("[Pipette] Drop tip"); + UfCmdSnippetExecutor.execute("PipetteTipDrop"); + for ( int i=0; i<10; i++ ) { + if ( !this.isTipExistOnPipette() ) { + break; + } + UfCommon.delay(500); + UfActuatorCmdExecutor.execute("Pipette", "pipette_ctrl_put_tip"); + LOG.info("[Pipette] Drop tip : retry {}", i); + } + if ( this.isTipExistOnPipette() ) { + throw new RuntimeException("Failed to drop tip"); + } + UfCmdSnippetExecutor.execute("ArmReset"); + this.hasTip = false; + } + + // check if tip exists on pipette by reading tip state + private Boolean isTipExistOnPipette() { + int existsCount = 0; + for ( int i=0; i<3; i++ ) { + String tipState = UfActuatorCmdExecutor.execute("Pipette", "read_pipette_tip_state"); + if ("1".equals(tipState)) { + existsCount++; + } + UfCommon.delay(100); + } + return existsCount > 1; + } +} diff --git a/src/main/java/com/iflytop/a800/device/Scanner.java b/src/main/java/com/iflytop/a800/device/Scanner.java new file mode 100644 index 0000000..6ea0070 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Scanner.java @@ -0,0 +1,4 @@ +package com.iflytop.a800.device; + +public class Scanner { +} diff --git a/src/main/java/com/iflytop/a800/device/Shaker.java b/src/main/java/com/iflytop/a800/device/Shaker.java new file mode 100644 index 0000000..12bca42 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/Shaker.java @@ -0,0 +1,4 @@ +package com.iflytop.a800.device; + +public class Shaker { +} diff --git a/src/main/java/com/iflytop/a800/device/TestCardWarehouse.java b/src/main/java/com/iflytop/a800/device/TestCardWarehouse.java new file mode 100644 index 0000000..1484206 --- /dev/null +++ b/src/main/java/com/iflytop/a800/device/TestCardWarehouse.java @@ -0,0 +1,4 @@ +package com.iflytop.a800.device; + +public class TestCardWarehouse { +} diff --git a/src/main/java/com/iflytop/a800/resource/TestTube.java b/src/main/java/com/iflytop/a800/resource/TestTube.java new file mode 100644 index 0000000..3fe02df --- /dev/null +++ b/src/main/java/com/iflytop/a800/resource/TestTube.java @@ -0,0 +1,10 @@ +package com.iflytop.a800.resource; +import com.iflytop.a800.task.TubeTestTask; +public class TestTube { + // is tube existed + public Boolean isExisted = false; + // tube type + public Number type; + // tube bar code + public String barCode; +} diff --git a/src/main/java/com/iflytop/a800/resource/TestTubeRack.java b/src/main/java/com/iflytop/a800/resource/TestTubeRack.java new file mode 100644 index 0000000..10c5aa9 --- /dev/null +++ b/src/main/java/com/iflytop/a800/resource/TestTubeRack.java @@ -0,0 +1,8 @@ +package com.iflytop.a800.resource; +import java.util.List; +public class TestTubeRack { + // tube rack type + public Number type; + // list of test tubes + public List tubes; +} diff --git a/src/main/java/com/iflytop/a800/task/TubeRackTask.java b/src/main/java/com/iflytop/a800/task/TubeRackTask.java new file mode 100644 index 0000000..bfecdcb --- /dev/null +++ b/src/main/java/com/iflytop/a800/task/TubeRackTask.java @@ -0,0 +1,73 @@ +package com.iflytop.a800.task; +import com.iflytop.a800.Task; +import com.iflytop.a800.TaskBase; +import com.iflytop.a800.TaskManager; +import com.iflytop.a800.device.Device; +import com.iflytop.a800.resource.TestTube; +import com.iflytop.a800.resource.TestTubeRack; +import java.util.ArrayList; +import java.util.List; +public class TubeRackTask extends TaskBase { + // sampling tubes + private List samplingTubes; + + @Override + public void run() { + var device = Device.getInstance(); + var feeder = device.feeder; + var taskMan = TaskManager.getInstance(); + + feeder.feed(); + this.samplingTubes = new ArrayList<>(); + // test tube rack + TestTubeRack tubeRack = new TestTubeRack(); + tubeRack.type = feeder.readTubeRackType(); + tubeRack.tubes = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + var tube = new TestTube(); + tubeRack.tubes.add(tube); + + tube.isExisted = feeder.readIsTestTubeExisted(i); + if ( !tube.isExisted ) { + continue ; + } + + this.samplingTubes.add(tube); + tube.type = tubeRack.type; + if ( !tube.type.equals(1) ) { + continue; + } + + Boolean isWb5ml = feeder.readIsWb5ml(i); + if ( isWb5ml ) { + tube.type = 2; + } + + tube.barCode = feeder.readBarCode(i); + + var testTask = new TubeTestTask(); + testTask.tubeRack = tubeRack; + testTask.tube = tube; + testTask.on("StartIncubating", this::onTubeTaskStartIncubating); + taskMan.append(testTask); + } + + feeder.prepareForTesting(); + } + + // tube task finish callback + private void onTubeTaskStartIncubating(Task task, List args ) { + TubeTestTask tubeTask = null; + if ( !(task instanceof TubeTestTask) ) { + return ; + } + + tubeTask = (TubeTestTask)task; + var tube = tubeTask.tube; + this.samplingTubes.remove(tube); + if ( !this.samplingTubes.isEmpty() ) { + return ; + } + Device.getInstance().feeder.exit(); + } +} diff --git a/src/main/java/com/iflytop/a800/task/TubeTestTask.java b/src/main/java/com/iflytop/a800/task/TubeTestTask.java new file mode 100644 index 0000000..f1c37df --- /dev/null +++ b/src/main/java/com/iflytop/a800/task/TubeTestTask.java @@ -0,0 +1,52 @@ +package com.iflytop.a800.task; +import com.iflytop.a800.TaskBase; +import com.iflytop.a800.resource.TestTube; +import com.iflytop.a800.resource.TestTubeRack; +import com.iflytop.uf.UfCmdSnippetExecutor; +public class TubeTestTask extends TaskBase { + public TestTubeRack tubeRack; + public TestTube tube; + public String status; + + @Override + public void run() { + this.shake(); + this.uncap(); + this.sampling(); + this.cap(); + this.incubate(); + this.scan(); + } + + // 摇匀 + private void shake() { + UfCmdSnippetExecutor.execute("SampleTestShake"); + } + + // 取盖 + private void uncap() { + UfCmdSnippetExecutor.execute("SampleTestUnCap"); + } + + // 取样 + private void sampling() { + UfCmdSnippetExecutor.execute("SampleTestSamplingFromWb5ml"); + UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); + UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); + } + + // 盖回 + private void cap() { + UfCmdSnippetExecutor.execute("SampleTestCap"); + } + + // 孵育 + private void incubate() { + UfCmdSnippetExecutor.execute("SampleTestIncubate"); + } + + // 扫描 + private void scan() { + UfCmdSnippetExecutor.execute("SampleTestScanResult"); + } +}