diff --git a/app.db b/app.db index ce753d5..6809d9b 100644 Binary files a/app.db and b/app.db differ diff --git a/src/main/java/com/iflytop/digester/StartResetTaskThread.java b/src/main/java/com/iflytop/digester/StartResetTaskThread.java index 5810e11..e27e21a 100644 --- a/src/main/java/com/iflytop/digester/StartResetTaskThread.java +++ b/src/main/java/com/iflytop/digester/StartResetTaskThread.java @@ -1,9 +1,24 @@ package com.iflytop.digester; import com.iflytop.digester.deviceinstance.Device; +import com.iflytop.digester.underframework.UfActuatorCmdExecutor; +import com.iflytop.digester.underframework.UfApplication; +import com.iflytop.digester.underframework.UfCmdSnippetExecutor; +import com.iflytop.digester.underframework.dao.model.UfMdbNotification; +import com.iflytop.digester.underframework.dao.model.UfMdbOption; import com.iflytop.digester.underframework.dao.model.UfMdbRuntimeVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + public class StartResetTaskThread extends Thread { + // logger + public static final Logger LOG = LoggerFactory.getLogger(StartResetTaskThread.class); // 是否需要放置异常处理试管架 private Boolean isErrorTubeRackRequired = true; + // pause lock + private final Object pauseLock = new Object(); @Override public void run() { @@ -11,57 +26,15 @@ public class StartResetTaskThread extends Thread { device.setStatus("preparing"); this.setProgressMessage("设备初始化..."); + UfCmdSnippetExecutor.execute("StartResetInitDevices"); + try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } -// this.getDevice().getIO().setValue("LightRed", 1); -// -// this.setProgressMessage("设备初始化 : 关闭设备门"); -// DiActMotor doorMotor = this.getActuator(MyDevice.ACT_DOOR_MOTOR, DiActMotor.class); -// doorMotor.setEnable(true); -// MoveDoorClose.execute(this.getDevice()); -// -// this.setProgressMessage("设备初始化 : 加液臂复位"); -// DiActMotor liquidMotor = this.getActuator(MyDevice.ACT_LIQUID_MOTOR, DiActMotor.class); -// liquidMotor.setEnable(true); -// liquidMotor.reset(); -// liquidMotor.moveTo("LiquidArmStandby"); -// -// this.setProgressMessage("设备初始化 : 关闭夹爪"); -// DiActServo transferClipServo = this.getActuator(MyDevice.ACT_TRANSFER_CLIP_SERVO, DiActServo.class); -// transferClipServo.setEnable(true); -// transferClipServo.moveTo("TransClipServoClose"); -// -// this.setProgressMessage("设备初始化 : 搬运机械臂上下移动复位"); -// DiActMotor transUdMotor = this.getActuator(MyDevice.ACT_TRANSFER_UD_MOTOR, DiActMotor.class); -// transUdMotor.setEnable(true); -// transUdMotor.reset(); -// transUdMotor.moveTo("TransUdMotorStandby"); -// -// this.setProgressMessage("设备初始化 : 搬运机械臂左右移动复位"); -// DiActMotor transLrMotor = this.getActuator(MyDevice.ACT_TRANSFER_LR_MOTOR, DiActMotor.class); -// transLrMotor.setEnable(true); -// transLrMotor.reset(); -// transLrMotor.setMoveOffset("TransLrMotorGlobalOffset"); -// transLrMotor.moveTo("TransLrMotorStandby"); -// -// // @TODO : http://127.0.0.1:5566/issues/39 -// this.setProgressMessage("设备初始化 : 加液转盘复位"); -// DiActMotor liquidPlateMotor = this.getActuator(MyDevice.ACT_LIQUID_PLATE_MOTOR, DiActMotor.class); -// liquidPlateMotor.setEnable(true); -// liquidPlateMotor.reset(); -// -// // @TODO : http://127.0.0.1:5566/issues/40 -// this.setProgressMessage("设备初始化 : 加热盘转盘复位"); -// DiActMotor heatPlateMotor = this.getActuator(MyDevice.ACT_HEAT_PLATE_MOTOR, DiActMotor.class); -// heatPlateMotor.setEnable(true); -// heatPlateMotor.reset(); -// heatPlateMotor.moveTo("HeatPlateStandby"); -// -// this.setupHeatingSlotCover(); + this.setupHeatingSlotCover(); // this.setupPeristalticPump(); // // this.setProgressMessage("设备初始化 : 相机"); @@ -146,63 +119,60 @@ public class StartResetTaskThread extends Thread { // MoveDoorClose.execute(this.getDevice()); // } // -// /** -// * 清理试管架 -// * @issue : 无法获取加液位置是否存在试管架 -// */ -// private void setupHeatingSlotCover() { -// boolean isDoorOpen = false; -// Integer errorSlotIndex = this.getDevice().getEnv().getProperty("app.errorSlotIndex",Integer.class); -// -// for ( int i=0; i<5; i++ ) { -// this.setProgressMessage("设备初始化 : 检查试管架 " + (i+1) + "/5"); -// String key = "HeatingPlateMotorSlotCover_" + i; -// DiActServo heatSlotCoverServo = this.getActuator(key, DiActServo.class); -// heatSlotCoverServo.setEnable(true); -// var currentPos = heatSlotCoverServo.getCurrentPos(); -// LOG.info("HeatingPlateMotorSlotCover #{} : current pos = {}", i, currentPos); -// -// // 如果是空的,则初始化位置后继续下一步 -// var rackExistsPos = heatSlotCoverServo.getLocationValue("HeatingPlateMotorSlotCoverRackExists"); -// if ( currentPos > rackExistsPos ) { -// heatSlotCoverServo.setCurrentPosAsMiddle(); -// continue; -// } -// -// // 如果是空试管架, 又是异常区域则不处理, 因为异常区域始终要有个试管架 -// var emptyRackExistsPos = heatSlotCoverServo.getLocationValue("HeatingPlateMotorSlotCoverEmptyRackExists"); -// if ( currentPos > emptyRackExistsPos && null != errorSlotIndex && errorSlotIndex == i ) { // 异常区域存在空试管架 -// this.isErrorTubeRackRequired = false; -// continue; -// } -// -// var moveToLiquidPlate = new MoveMoveTubeRackFromHeatPlateToLiquidPlate(); -// moveToLiquidPlate.setDevice(this.getDevice()); -// moveToLiquidPlate.slotIndex = i; -// moveToLiquidPlate.updateSlotStatus = false; -// moveToLiquidPlate.run(); -// -// if ( !isDoorOpen ) { -// MoveDoorOpen.execute(this.getDevice()); -// isDoorOpen = true; -// } -// -// heatSlotCoverServo.moveTo("HeatingPlateMotorSlotCoverEmpty"); -// heatSlotCoverServo.setEnable(false); -// -// // 等待取出试管架 -// DiMdbNotification.taskAction(this, "TaskStartResetErrorTubeRackTakeOut"); -// this.waitAction("TaskStartResetErrorTubeRackTakeOut"); -// LOG.info("HeatingPlateMotorSlotCover #{} : take out", i); -// -// heatSlotCoverServo.setEnable(true); -// heatSlotCoverServo.setCurrentPosAsMiddle(); -// } -// -// if ( isDoorOpen ) { -// MoveDoorClose.execute(this.getDevice()); -// } -// } + /** + * 清理试管架 + * @issue : 无法获取加液位置是否存在试管架 + */ + private void setupHeatingSlotCover() { + boolean isDoorOpen = false; + Integer errorSlotIndex = UfApplication.getApp().getEnv().getProperty("app.errorSlotIndex", Integer.class); + + for ( int i=0; i<5; i++ ) { + this.setProgressMessage("设备初始化 : 检查试管架 " + (i+1) + "/5"); + UfCmdSnippetExecutor.execute(String.format("HeatingSlotCoverEnable.%d",i)); + var actuatorKey = String.format("HeatingPlateMotorSlotCover_%d", i); + var currentPosStr = UfActuatorCmdExecutor.execute(actuatorKey, "motor_read_pos"); + var currentPos = Integer.parseInt(currentPosStr); + LOG.info("HeatingPlateMotorSlotCover #{} : current pos = {}", i, currentPos); + + // 如果是空的,则初始化位置后继续下一步 + var rackExistsPos = UfMdbOption.getInteger("HeatingPlateMotorSlotCoverRackExists", 0); + if ( currentPos > rackExistsPos ) { + UfActuatorCmdExecutor.execute(actuatorKey, "motor_easy_set_current_pos", "2047"); + continue; + } + + // 如果是空试管架, 又是异常区域则不处理, 因为异常区域始终要有个试管架 + var emptyRackExistsPos = UfMdbOption.getInteger("HeatingPlateMotorSlotCoverEmptyRackExists", 0); + if ( currentPos > emptyRackExistsPos && null != errorSlotIndex && errorSlotIndex == i ) { // 异常区域存在空试管架 + this.isErrorTubeRackRequired = false; + continue; + } + + // 将试管架移至加液盘 + UfCmdSnippetExecutor.execute(String.format("TubeRackMoveToLiquidPlate.%d", i)); + // 打开柜门 + if ( !isDoorOpen ) { + UfCmdSnippetExecutor.execute("DoorOpen"); + isDoorOpen = true; + } + // 禁用该区位密封盖,用于后续重置状态 + UfCmdSnippetExecutor.execute(String.format("HeatingSlotCoverDisable.%d", i)); + + // 等待取出试管架 + UfMdbNotification.action("StartResetTubeRackTakeOut", new HashMap<>()); + this.waitAction("TaskStartResetErrorTubeRackTakeOut"); + LOG.info("HeatingPlateMotorSlotCover #{} : take out", i); + + // 重新启用密封盖 + UfCmdSnippetExecutor.execute(String.format("HeatingSlotCoverEnable.%d", i)); + UfCmdSnippetExecutor.execute(String.format("HeatingSlotCoverServoSetCurPosAsMiddle.%d", i)); + } + + if ( isDoorOpen ) { + UfCmdSnippetExecutor.execute("DoorClose"); + } + } // // // 异常处理位试管架取出 // public void actionErrorTubeRackTakeOut() { @@ -241,6 +211,26 @@ public class StartResetTaskThread extends Thread { // action.finish(); // } + // wait for continue + private void waitAction( String key ) { + LOG.info("Start reset wait for action : {}", key); + synchronized (pauseLock) { + try { + pauseLock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + // execute action + public void executeAction( String key ) { + LOG.info("Start reset execute action : {}", key); + synchronized (pauseLock) { + pauseLock.notifyAll(); + } + } + // set progress message private void setProgressMessage(String message) { UfMdbRuntimeVariable.setString(message, "device.start.message"); diff --git a/src/main/java/com/iflytop/digester/controller/DeviceController.java b/src/main/java/com/iflytop/digester/controller/DeviceController.java index cd66146..1497044 100644 --- a/src/main/java/com/iflytop/digester/controller/DeviceController.java +++ b/src/main/java/com/iflytop/digester/controller/DeviceController.java @@ -6,11 +6,18 @@ import com.iflytop.digester.underframework.controller.UfApiResponse; import jakarta.annotation.Resource; import org.springframework.stereotype.Controller; 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; + @Controller public class DeviceController extends UfApiControllerBase { @Resource private Device device; + // start reset task thread + private StartResetTaskThread startReset = null; + @ResponseBody @PostMapping("/api/device/start") @@ -18,8 +25,16 @@ public class DeviceController extends UfApiControllerBase { if (!"new".equals(this.device.getStatus())) { return this.error("当前设备无法执行启动操作"); } - var startReset = new StartResetTaskThread(); - startReset.start(); + this.startReset = new StartResetTaskThread(); + this.startReset.start(); + return this.success(); + } + + @ResponseBody + @PostMapping("/api/device/start-action-execute") + public UfApiResponse startActionExecute(@RequestBody Map params) { + String action = (String)params.get("action"); + this.startReset.executeAction(action); return this.success(); } diff --git a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java index cd39c85..c40697e 100644 --- a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java +++ b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java @@ -1,9 +1,33 @@ package com.iflytop.digester.underframework; +import com.iflytop.digester.underframework.dao.model.UfMdbActuator; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; +import com.iflytop.digester.underframework.dao.record.UfActiveRecord; +import java.util.Map; public class UfActuatorCmdExecutor { // execute cmd public static String execute(UfMdbActuatorCmd cmd) { var con = UfApplication.getApp().connections.get(cmd.connectionKey); return con.execute(cmd); } + + // execute cmd + public static String execute(String actuatorKey, String cmdKey, String cmdParams ) { + var actuator = UfActiveRecord.findOne(UfMdbActuator.class, Map.of("key", actuatorKey)); + if ( null == actuator ) { + throw new RuntimeException("无效的设备KEY :" + actuatorKey); + } + var command = UfActiveRecord.findOne(UfMdbActuatorCmd.class, Map.of("cmdKey", cmdKey, "actuatorId", actuator.id)); + if ( null == command ) { + throw new RuntimeException("无效的命令KEY :" + cmdKey); + } + if ( null != cmdParams ) { + command.parameters = cmdParams; + } + return execute(command); + } + + // execute cmd + public static String execute(String actuatorKey, String cmdKey) { + return execute(actuatorKey, cmdKey, null); + } } diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java b/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java index 43efb43..525f7a9 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java @@ -1,5 +1,5 @@ package com.iflytop.digester.underframework.connection; -import com.iflytop.digester.underframework.dao.model.TsMdbActuator; +import com.iflytop.digester.underframework.dao.model.UfMdbActuator; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; import com.iflytop.digester.underframework.util.TsByteBuffer; @@ -109,7 +109,7 @@ public class UfZcancmderWebsocket extends UfConnectionBase { } if ( null == this.response ) { - var actuator = UfActiveRecord.findOne(TsMdbActuator.class, actuatorCmd.actuatorId); + var actuator = UfActiveRecord.findOne(UfMdbActuator.class, actuatorCmd.actuatorId); throw new RuntimeException(String.format("设备 [%s] 响应超时: %s", actuator.name, actuatorCmd.cmdKey)); } @@ -153,7 +153,7 @@ public class UfZcancmderWebsocket extends UfConnectionBase { private void sendCommandRequest(UfMdbActuatorCmd actuatorCmd) { String cmd = this.buildCommand(actuatorCmd); - TsMdbActuator actuator = TsMdbActuator.findOne(TsMdbActuator.class, actuatorCmd.actuatorId); + UfMdbActuator actuator = UfMdbActuator.findOne(UfMdbActuator.class, actuatorCmd.actuatorId); LOG.info("[Command-Executor:{}] {}({}) => {}", actuator.name, actuatorCmd.cmdKey, actuatorCmd.parameters, cmd); if ( !this.client.isOpen() ) { throw new RuntimeException("ZCanCmder 连接已断开"); @@ -177,7 +177,7 @@ public class UfZcancmderWebsocket extends UfConnectionBase { paramList = List.of(params.split(",")); } - TsMdbActuator actuator = TsMdbActuator.findOne(TsMdbActuator.class, actuatorCmd.actuatorId); + UfMdbActuator actuator = UfMdbActuator.findOne(UfMdbActuator.class, actuatorCmd.actuatorId); int moduleId = Integer.parseInt(actuatorCmd.cmdFlags); int cmdId = Integer.parseInt(actuatorCmd.cmdId, 16); @@ -283,7 +283,7 @@ public class UfZcancmderWebsocket extends UfConnectionBase { this.executeDeviceCommand(command); this.waitForActuatorFinish(command); - var actuator = UfActiveRecord.findOne(TsMdbActuator.class, command.actuatorId); + var actuator = UfActiveRecord.findOne(UfMdbActuator.class, command.actuatorId); var encoderAvailable = actuator.getProperty("encoderAvailable"); if ( null == encoderAvailable || !encoderAvailable.asBoolean() ) { return ; diff --git a/src/main/java/com/iflytop/digester/underframework/controller/TsApiActuator.java b/src/main/java/com/iflytop/digester/underframework/controller/TsApiActuator.java index e4b2c98..ac2d1fa 100644 --- a/src/main/java/com/iflytop/digester/underframework/controller/TsApiActuator.java +++ b/src/main/java/com/iflytop/digester/underframework/controller/TsApiActuator.java @@ -1,7 +1,7 @@ package com.iflytop.digester.underframework.controller; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; import com.iflytop.digester.underframework.dao.record.UfActiveRecordCriteria; -import com.iflytop.digester.underframework.dao.model.TsMdbActuator; +import com.iflytop.digester.underframework.dao.model.UfMdbActuator; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -15,13 +15,13 @@ public class TsApiActuator extends UfApiControllerBase { String moduleId = (String) params.get("moduleId"); var criteria = new UfActiveRecordCriteria(); criteria.conditions = Map.of("moduleId", moduleId); - var items = UfActiveRecord.find(TsMdbActuator.class, criteria); + var items = UfActiveRecord.find(UfMdbActuator.class, criteria); return success(items); } @PostMapping("/api/actuator/save") @ResponseBody - public UfApiResponse save(@RequestBody TsMdbActuator actuator ) { + public UfApiResponse save(@RequestBody UfMdbActuator actuator ) { if ( null != actuator.id ) { actuator.isNewRecord = false; } @@ -31,7 +31,7 @@ public class TsApiActuator extends UfApiControllerBase { @PostMapping("/api/actuator/delete") @ResponseBody - public UfApiResponse delete(@RequestBody TsMdbActuator actuator ) { + public UfApiResponse delete(@RequestBody UfMdbActuator actuator ) { actuator.delete(); return success(); } diff --git a/src/main/java/com/iflytop/digester/underframework/dao/model/TsMdbActuator.java b/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuator.java similarity index 90% rename from src/main/java/com/iflytop/digester/underframework/dao/model/TsMdbActuator.java rename to src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuator.java index c6b81d1..d2135ae 100644 --- a/src/main/java/com/iflytop/digester/underframework/dao/model/TsMdbActuator.java +++ b/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuator.java @@ -2,13 +2,12 @@ package com.iflytop.digester.underframework.dao.model; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.iflytop.digester.deviceinstance.HeatingTurntableSlotTube; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; import com.iflytop.digester.underframework.dao.record.UfActiveRecordField; /* 底层资源,比如电机、传感器等 */ -public class TsMdbActuator extends UfActiveRecord { +public class UfMdbActuator extends UfActiveRecord { @UfActiveRecordField public String moduleId; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ac7c199..997974a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -31,3 +31,5 @@ mqtt-broker: my-topic : "stw-a80" transbot-topic : "transbot" +app : + errorSlotIndex : 4 \ No newline at end of file diff --git a/web b/web index e2b8752..2b86529 160000 --- a/web +++ b/web @@ -1 +1 @@ -Subproject commit e2b87526304961b2bac163b58e372fd54ebffdb3 +Subproject commit 2b865293c1311ba5bcefddcb1df3ac83a97799ef