19 changed files with 502 additions and 1 deletions
-
BINapp.db
-
2pom.xml
-
53src/main/java/com/iflytop/a800/ResourceLockManager.java
-
5src/main/java/com/iflytop/a800/Task.java
-
36src/main/java/com/iflytop/a800/TaskBase.java
-
28src/main/java/com/iflytop/a800/TaskManager.java
-
25src/main/java/com/iflytop/a800/controller/DemoController.java
-
22src/main/java/com/iflytop/a800/controller/TaskController.java
-
14src/main/java/com/iflytop/a800/device/Device.java
-
42src/main/java/com/iflytop/a800/device/Feeder.java
-
4src/main/java/com/iflytop/a800/device/Incubator.java
-
117src/main/java/com/iflytop/a800/device/Pipette.java
-
4src/main/java/com/iflytop/a800/device/Scanner.java
-
4src/main/java/com/iflytop/a800/device/Shaker.java
-
4src/main/java/com/iflytop/a800/device/TestCardWarehouse.java
-
10src/main/java/com/iflytop/a800/resource/TestTube.java
-
8src/main/java/com/iflytop/a800/resource/TestTubeRack.java
-
73src/main/java/com/iflytop/a800/task/TubeRackTask.java
-
52src/main/java/com/iflytop/a800/task/TubeTestTask.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<String, List<Object>> 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(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
package com.iflytop.a800; |
|||
public interface Task { |
|||
// start this task |
|||
void start(); |
|||
} |
@ -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<Object> args ); |
|||
} |
|||
|
|||
// 事件回调列表 |
|||
public Map<String,List<EventCallback>> 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<Object> args ) { |
|||
if ( !eventCallbacks.containsKey(name) ) { |
|||
return; |
|||
} |
|||
for ( var callback : eventCallbacks.get(name) ) { |
|||
callback.callback(this, args); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void lockRes() {} |
|||
public void unlockRes() {} |
|||
} |
@ -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<Task> tasks = new ArrayList<Task>(); |
|||
// 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(); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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"); |
|||
} |
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.iflytop.a800.device; |
|||
|
|||
public class Incubator { |
|||
} |
@ -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<Integer> 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<String,Object> 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; |
|||
} |
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.iflytop.a800.device; |
|||
|
|||
public class Scanner { |
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.iflytop.a800.device; |
|||
|
|||
public class Shaker { |
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.iflytop.a800.device; |
|||
|
|||
public class TestCardWarehouse { |
|||
} |
@ -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; |
|||
} |
@ -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<TestTube> tubes; |
|||
} |
@ -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<TestTube> 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<Object> 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(); |
|||
} |
|||
} |
@ -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"); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue