Browse Source

update

master
zhaohe 2 months ago
parent
commit
7b81e47a8e
  1. 21
      src/main/java/a8k/app/hardware/driver/PipetteCtrlDriverV2.java
  2. 13
      src/main/java/a8k/app/hardware/type/A8kPacket.java
  3. 1
      src/main/java/a8k/app/hardware/type/CmdId.java
  4. 1
      src/main/java/a8k/app/hardware/type/pipette_module/cfg/PipetteCommonConfigIndex.java
  5. 10
      src/main/java/a8k/app/hardware/type/pipette_module/cfgbean/PipetteCommonConfig.java
  6. 84
      src/main/java/a8k/app/service/module/TurntableAndOptScannerCtrlModule.java
  7. 64
      src/main/java/a8k/app/service/statemgr/ReactionPlateContainerStateMgr.java
  8. 100
      src/main/java/a8k/app/utils/ZWorkThread.java
  9. 7
      src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunCommonConfigPage.java
  10. 36
      src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunOperationCtrlPage.java
  11. 22
      src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunTestCtrlPage.java

21
src/main/java/a8k/app/hardware/driver/PipetteCtrlDriverV2.java

@ -18,6 +18,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
@ -444,6 +447,24 @@ public class PipetteCtrlDriverV2 {
return callcmd(MId.PipetteMod, CmdId.pipette_read_capacitance).getContentI32(0);
}
public List<Integer> getAspPressureData() throws AppException {
List<Integer> pressuredata = new ArrayList<>();
for (int i = 0; ; i++) {
A8kPacket packet = callcmd(MId.PipetteMod, CmdId.pipette_get_asp_pressure_data, i);
if (packet.getDataLen() == 0) {
break;
}
var content = packet.getContentI16Array();
for (Integer val : content) {
pressuredata.add(val);
}
}
return pressuredata;
}
//
// UTILS
//

13
src/main/java/a8k/app/hardware/type/A8kPacket.java

@ -33,7 +33,7 @@ public class A8kPacket {
/**
*
* @WARNING
* 1. 修改这里时需要注意连同createPacket一起修改
* 1. 修改这里时需要注意连同createPacket一起修改
* 2. PACKET_MIN_LEN 比Header多一个字节是因为还有一个字节的校验位
*/
@ -99,6 +99,15 @@ public class A8kPacket {
return cmdcontent;
}
public int[] getContentI16Array() {
int[] ret = new int[getDataLen() / 2];
for (int i = 0; i < ret.length; i++) {
ret[i] = ByteArray.readS16bit(raw, DATA_OFFSET + i * 2);
}
return ret;
}
public int getContentI32(int index) {
return ByteArray.read32bit(raw, DATA_OFFSET + index * 4);
}
@ -220,7 +229,7 @@ public class A8kPacket {
}
public static void main(String[] args) {
var packet = createPacket(41, A8kPacket.PACKET_TYPE_CMD, CmdId.module_stop.index, new Integer[]{});
var packet = createPacket(41, A8kPacket.PACKET_TYPE_CMD, CmdId.module_stop.index, new Integer[]{});
logger.info("{}", packet.toByteString());
}

1
src/main/java/a8k/app/hardware/type/CmdId.java

@ -203,6 +203,7 @@ public enum CmdId {
pipette_read_capacitance(0x758D, "pipette_read_capacitance"),
pipette_pump_distribu_all_set_param(0x758E, "pipette_pump_distribu_all_set_param"), // {paramid,val}, ack:{}
pipette_pump_distribu_all(0x758F, "pipette_pump_distribu_all"), // {}, ack:{}
pipette_get_asp_pressure_data(0x7590, "pipette_get_asp_pressure_data"), // {section_off}, ack:{int16_t data[]}
pipette_test_pump_move_to_x100nl(0x7600, "pipette_test_pump_move_to_x100nl"), // int32_t x100nl, int32_t vcfgindex

1
src/main/java/a8k/app/hardware/type/pipette_module/cfg/PipetteCommonConfigIndex.java

@ -4,6 +4,7 @@ public enum PipetteCommonConfigIndex {
pressureRecordEnable,
platformInfoCpyid,
eachActionDelayTime,
aspiratePressureReportExtendTime, // 吸液压力采集延长时间,吸液完成后多采集压力的时间
mark;
public Integer toInteger() {

10
src/main/java/a8k/app/hardware/type/pipette_module/cfgbean/PipetteCommonConfig.java

@ -2,6 +2,7 @@ package a8k.app.hardware.type.pipette_module.cfgbean;
import a8k.app.hardware.type.pipette_module.cfg.PipetteCommonConfigIndex;
import a8k.app.type.exception.AppException;
import io.swagger.v3.oas.models.security.SecurityScheme;
public class PipetteCommonConfig {
@ -23,10 +24,11 @@ public class PipetteCommonConfig {
//
// lld
//
public Integer pressureRecordEnable = 0; // 是否记录压力数据
public Integer platformInfoCpyid = 0; // 平台信息配置索引,当调用initPumpDevice时如果传入的参数为-1,则使用platformInfoMCpyid
public Integer eachActionDelayTime = 0; // 每个动作之间的延时单位ms (动作调试使用,方便观察液体变化情况)
public Integer mark = 0; // 结构体最后一个数值设置9973用于保证单片机端和java端均正确更新了枚举
public Integer pressureRecordEnable = 0; // 是否记录压力数据
public Integer platformInfoCpyid = 0; // 平台信息配置索引,当调用initPumpDevice时如果传入的参数为-1,则使用platformInfoMCpyid
public Integer eachActionDelayTime = 0; // 每个动作之间的延时单位ms (动作调试使用,方便观察液体变化情况)
public Integer aspiratePressureReportExtendTime = 0; // 吸液压力采集延长时间,吸液完成后多采集压力的时间
public Integer mark = 0; // 结构体最后一个数值设置9973用于保证单片机端和java端均正确更新了枚举
@FunctionalInterface

84
src/main/java/a8k/app/service/module/TurntableAndOptScannerCtrlModule.java

@ -0,0 +1,84 @@
package a8k.app.service.module;
import a8k.app.service.data.ReactionRecordMgrService;
import a8k.app.service.lowerctrl.OptScanModuleCtrlService;
import a8k.app.service.statemgr.IncubationPlateStateMgrService;
import a8k.app.service.statemgr.OptScanModuleStateMgrService;
import a8k.app.teststate.VirtualDevice;
import a8k.app.utils.ZWorkThread;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class TurntableAndOptScannerCtrlModule {
public enum State {
idle,
starting,
working,
stopping,
}
//
//状态
//
private final VirtualDevice virtualDevice;
private final IncubationPlateStateMgrService incubationPlateStateMgrService;
private final OptScanModuleStateMgrService optScanModuleStateMgrService;
//
// 数据库
//
private final ReactionRecordMgrService reactionRecordMgrService;
//
// 控制
//
private final OptScanModuleCtrlService optScanModuleCtrlService;
private ZWorkThread workThread;
private State state = State.idle;
private final Object stateLock = new Object();
private final Object turntableLock = new Object();
@PostConstruct
public void init() {
state = State.idle;
workThread = new ZWorkThread();
workThread.init("TurntableAndOptScannerCtrlModule-WorkThread");
}
public void workThreadFn() {
// while (!workThread.isStopping()) {
// }
}
//
// utils
//
private void setState(State state) {
synchronized (stateLock) {
this.state = state;
log.info("TurntableAndOptScannerCtrlModule state changed to {}", state);
}
}
private State getState() {
synchronized (stateLock) {
return state;
}
}
private void waitForState(State state) {
while (!getState().equals(state)) {
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
}
}
}

64
src/main/java/a8k/app/service/statemgr/ReactionPlateContainerStateMgr.java

@ -0,0 +1,64 @@
package a8k.app.service.statemgr;
import a8k.app.constant.AppConstant;
import a8k.app.type.a8k.ConsumableGroup;
import a8k.app.type.a8k.container.ReactionPlateContainer;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class ReactionPlateContainerStateMgr {
public ReactionPlateContainer[] reactionPlateGroup = new ReactionPlateContainer[AppConstant.CONSUMABLE_CHANNEL_NUM];
@PostConstruct
void init() {
for (int i = 0; i < reactionPlateGroup.length; i++) {
reactionPlateGroup[i] = new ReactionPlateContainer();
}
}
synchronized void installReactionPlateContainer(ConsumableGroup group, Integer projId, String projName, String projShortName, String lotId, String color, Integer num) {
ReactionPlateContainer reactionPlateContainer = reactionPlateGroup[group.off];
reactionPlateContainer.projId = projId;
reactionPlateContainer.projName = projName;
reactionPlateContainer.projShortName = projShortName;
reactionPlateContainer.lotId = lotId;
reactionPlateContainer.color = color;
reactionPlateContainer.num = num;
reactionPlateContainer.isInstall = true;
reactionPlateContainer.reserveNum = 0;
}
synchronized void setReactionPlateNum(ConsumableGroup group, Integer num) {
log.debug("setReactionPlateNum: group={}, num={}", group, num);
ReactionPlateContainer reactionPlateContainer = reactionPlateGroup[group.off];
if (reactionPlateContainer.isInstall) {
reactionPlateContainer.num = num;
} else {
log.warn("setReactionPlateNum: Reaction plate container is not installed for group: {}", group);
}
}
synchronized Boolean popOneReactionPlate(ConsumableGroup group) {
log.debug("popOneReactionPlate: group={}", group);
ReactionPlateContainer reactionPlateContainer = reactionPlateGroup[group.off];
if (reactionPlateContainer.isInstall && reactionPlateContainer.num > 0) {
reactionPlateContainer.num--;
return true;
} else {
return false;
}
}
synchronized void uninstallReactionPlateContainer(ConsumableGroup group) {
log.debug("uninstallReactionPlateContainer: group={}", group);
ReactionPlateContainer reactionPlateContainer = reactionPlateGroup[group.off];
reactionPlateContainer.reset();
}
}

100
src/main/java/a8k/app/utils/ZWorkThread.java

@ -0,0 +1,100 @@
package a8k.app.utils;
import org.springframework.util.Assert;
import java.util.concurrent.atomic.AtomicBoolean;
public class ZWorkThread {
Thread thread;
Runnable pendingRunnable;
AtomicBoolean workingFlag = new AtomicBoolean(false);
AtomicBoolean stopPendingFlag = new AtomicBoolean(false);
public void init(String name) {
thread = new Thread(this::workThread);
thread.setName(name);
thread.start();
}
synchronized public void start(Runnable runnable) {
Assert.isTrue(thread != null, "Thread not initialized. Call init() first.");
if (workingFlag.get()) {
stop();
}
pendingRunnable = runnable;
}
synchronized public void stop() {
Assert.isTrue(thread != null, "Thread not initialized. Call init() first.");
stopPendingFlag.set(true);
while (workingFlag.get()) {
threadSleep(50);
}
stopPendingFlag.set(false);
}
public Boolean isStopping() {
return stopPendingFlag.get();
}
public Boolean isWorking() {
return workingFlag.get();
}
private void workThread() {
while (true) {
Runnable runnable = null;
if (pendingRunnable != null) {
runnable = pendingRunnable;
pendingRunnable = null;
}
if (runnable == null) {
threadSleep(50);
continue;
}
workingFlag.set(true);
runnable.run();
workingFlag.set(false);
}
}
public void threadSleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException ignored) {
}
}
public static void main(String[] args) throws InterruptedException {
ZWorkThread workThread = new ZWorkThread();
workThread.init("TestWorkThread");
workThread.start(() -> {
while (!workThread.isStopping()) {
System.out.println("Task 1 is running");
workThread.threadSleep(100);
}
});
Thread.sleep(3000);
workThread.stop();
workThread.start(() -> {
while (!workThread.isStopping()) {
System.out.println("Task 2 is running");
workThread.threadSleep(100);
}
});
Thread.sleep(3000);
workThread.stop();
}
}

7
src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunCommonConfigPage.java

@ -43,6 +43,11 @@ public class PipetteGunCommonConfigPage {
pipetteCtrlDriverV2.setCommonConfig(PipetteCommonConfigIndex.eachActionDelayTime, timems);
}
// aspiratePressureReportExtendTime
public void setAspiratePressureReportExtendTime(Integer timems) throws AppException {
pipetteCtrlDriverV2.setCommonConfig(PipetteCommonConfigIndex.aspiratePressureReportExtendTime, timems);
}
@PostConstruct
void init() {
var page = extApiPageMgr.newPage(this);
@ -53,6 +58,8 @@ public class PipetteGunCommonConfigPage {
.setParamVal("platInfoCpyIdx", () -> PlatInfoCpyIdx.of(getVal(PipetteCommonConfigIndex.platformInfoCpyid)));
page.addFunction("设置每次动作延时(ms)", this::setEachActionDelayTime)
.setParamVal("timems", () -> getVal(PipetteCommonConfigIndex.eachActionDelayTime));
page.addFunction("设置吸液压力采集延长时间(ms)", this::setAspiratePressureReportExtendTime)
.setParamVal("timems", () -> getVal(PipetteCommonConfigIndex.aspiratePressureReportExtendTime));
extApiPageMgr.addPage(page);
}

36
src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunOperationCtrlPage.java

@ -7,13 +7,18 @@ import a8k.app.hardware.type.pipette_module.cpyidx.PlatInfoCpyIdx;
import a8k.app.hardware.type.pipette_module.param.AspirationParam;
import a8k.app.hardware.type.pipette_module.param.DistribuAllParam;
import a8k.app.type.exception.AppException;
import a8k.extui.factory.CurveBuilder;
import a8k.extui.mgr.ExtApiPageMgr;
import a8k.extui.type.ret.ExtApiCurve;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
@ -23,7 +28,6 @@ public class PipetteGunOperationCtrlPage {
private final PipetteCtrlDriverV2 pipetteCtrlDriverV2;
AspirationParam aspirateParam = new AspirationParam();
DistribuAllParam distributeParam = new DistribuAllParam();
@ -40,7 +44,7 @@ public class PipetteGunOperationCtrlPage {
aspirateParam.containerInfoCpyId = containerInfoCpyId.toInteger();
}
public void setAspirationParamLiquidCfgIndex(PlatInfoCpyIdx liquidCfgIdx) {
public void setAspirationParamLiquidCfgIndex(LiquidConfigCpyIdx liquidCfgIdx) {
aspirateParam.liquidCfgIdx = liquidCfgIdx.ordinal();
}
@ -109,9 +113,10 @@ public class PipetteGunOperationCtrlPage {
}
public void execAspirate() throws AppException {
public ExtApiCurve execAspirate() throws AppException {
pipetteCtrlDriverV2.aspirateSetParam(aspirateParam);
pipetteCtrlDriverV2.aspirateBlock();
return getAspPressureRecord();
}
public void execDistribute() throws AppException {
@ -140,6 +145,24 @@ public class PipetteGunOperationCtrlPage {
pipetteCtrlDriverV2.pierceThroughBlock(containerCpyId, containerPos);
}
public ExtApiCurve getAspPressureRecord() throws AppException {
var datas = pipetteCtrlDriverV2.getAspPressureData();
Integer i = 0;
List<Object[]> points = new ArrayList<>();
for (var data : datas) {
points.add(new Object[]{i, data});
i++;
}
return CurveBuilder.buidCurve("压力曲线", "value", "value", 2000, 3000, points);
}
public Integer readPressure() throws AppException {
return pipetteCtrlDriverV2.readPressure();
}
public Integer readCapacity() throws AppException {
return pipetteCtrlDriverV2.readCapacitance();
}
@PostConstruct
void init() {
@ -147,7 +170,7 @@ public class PipetteGunOperationCtrlPage {
page.newGroup("配置吸液参数");
page.addFunction("设置吸取体积", this::setAspirationParamVolume).setParamVal("volume", () -> aspirateParam.volume);
page.addFunction("设置容器位置", this::setAspirationParamContainerPos).setParamVal("containerPos", () -> aspirateParam.containerPos);
page.addFunction("设置容器信息", this::setAspirationParamContainerInfoIndex).setParamVal("containerInfoCpyId", () ->ContainerCpyId.of(aspirateParam.containerInfoCpyId));
page.addFunction("设置容器信息", this::setAspirationParamContainerInfoIndex).setParamVal("containerInfoCpyId", () -> ContainerCpyId.of(aspirateParam.containerInfoCpyId));
page.addFunction("设置液体配置", this::setAspirationParamLiquidCfgIndex).setParamVal("liquidCfgIdx", () -> LiquidConfigCpyIdx.of(aspirateParam.liquidCfgIdx));
page.addFunction("设置吸取模式", this::setAspirationParamAspirationMode).setParamVal("aspirationMode", () -> aspirateParam.aspirationMode);
page.addFunction("设置LLDEnable", this::setAspirationParamLldEnable).setParamVal("lldEnable", () -> aspirateParam.lldEnable);
@ -177,6 +200,11 @@ public class PipetteGunOperationCtrlPage {
page.addFunction("吸取", this::execAspirate);
page.addFunction("分配全部", this::execDistribute);
page.newGroup(" 数据");
page.addFunction("获取吸液压力记录", this::getAspPressureRecord);
page.addFunction("读取压力", this::readPressure);
page.addFunction("读取电容", this::readCapacity);
extApiPageMgr.addPage(page);
}

22
src/main/java/a8k/extui/page/driver/pipette_module/PipetteGunTestCtrlPage.java

@ -6,7 +6,9 @@ import a8k.app.hardware.type.pipette_module.cpyidx.ContainerCpyId;
import a8k.app.hardware.type.pipette_module.cpyidx.LiquidConfigCpyIdx;
import a8k.app.hardware.type.pipette_module.cpyidx.PMVCpyIdx;
import a8k.app.type.exception.AppException;
import a8k.extui.factory.CurveBuilder;
import a8k.extui.mgr.ExtApiPageMgr;
import a8k.extui.type.ret.ExtApiCurve;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@ -14,6 +16,9 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
@ -27,9 +32,21 @@ public class PipetteGunTestCtrlPage {
@Setter
private ContainerCpyId containerCpyId = ContainerCpyId.Default; // 默认容器配置索引
public ExtApiCurve getAspPressureRecord() throws AppException {
var datas = pipetteCtrlDriverV2.getAspPressureData();
Integer i = 0;
List<Object[]> points = new ArrayList<>();
for (var data : datas) {
points.add(new Object[]{i, data});
i++;
}
return CurveBuilder.buidCurve("压力曲线", "value", "value", 2000, 3000, points);
}
public void pipetteTestPumpMoveToX100nl(Integer x100nl, PMVCpyIdx vcpyidx) throws AppException {
public ExtApiCurve pipetteTestPumpMoveToX100nl(Integer x100nl, PMVCpyIdx vcpyidx) throws AppException {
pipetteCtrlDriverV2.pipetteTestPumpMoveToX100nl(x100nl, vcpyidx.toInteger());
return getAspPressureRecord();
}
public void pipetteTestLld(LiquidConfigCpyIdx liquidCpyId) throws AppException {
@ -67,7 +84,8 @@ public class PipetteGunTestCtrlPage {
public void pipetteTestMoveToPiercePos() throws AppException {
pipetteCtrlDriverV2.pipetteTestMoveToPiercePos(containerPos, containerCpyId);
}
public void pipetteTestMoveToLldEndPos() throws AppException {
public void pipetteTestMoveToLldEndPos() throws AppException {
pipetteCtrlDriverV2.pipetteTestMoveToLldEndPos(containerPos, containerCpyId);
}

Loading…
Cancel
Save