From 3b8af4b5848f1edc21bcca82b8003b5690322e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Thu, 17 Jul 2025 20:41:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E8=AE=A1=E7=AE=97=E5=96=B7=E6=B6=82?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/common/constant/WebSocketMessageType.java | 4 + .../ms/app/controller/SprayTaskController.java | 1 + .../ms/app/device/spray/SprayTaskExecutor.java | 135 ++++++++++++++++++++- .../com/qyft/ms/app/device/status/SprayTask.java | 7 +- .../qyft/ms/app/model/vo/SprayTaskStatusVO.java | 18 +-- .../com/qyft/ms/system/common/utils/TimeUtils.java | 26 ++++ 6 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/qyft/ms/system/common/utils/TimeUtils.java diff --git a/src/main/java/com/qyft/ms/app/common/constant/WebSocketMessageType.java b/src/main/java/com/qyft/ms/app/common/constant/WebSocketMessageType.java index 275fbcb..5d230a4 100644 --- a/src/main/java/com/qyft/ms/app/common/constant/WebSocketMessageType.java +++ b/src/main/java/com/qyft/ms/app/common/constant/WebSocketMessageType.java @@ -18,6 +18,10 @@ public class WebSocketMessageType { */ public static final String SPRAY_POINT = "spray_point"; /** + * 喷涂预计完成时间 + */ + public static final String SPRAY_TASK_FINISH_TIME = "spray_task_finish_time"; + /** * 传感器 */ public static final String SENSOR = "sensor"; diff --git a/src/main/java/com/qyft/ms/app/controller/SprayTaskController.java b/src/main/java/com/qyft/ms/app/controller/SprayTaskController.java index 6485f88..0276e46 100644 --- a/src/main/java/com/qyft/ms/app/controller/SprayTaskController.java +++ b/src/main/java/com/qyft/ms/app/controller/SprayTaskController.java @@ -31,6 +31,7 @@ public class SprayTaskController { sprayTaskStatusVO.setCmdCode(sprayTask.getCmdCode()); sprayTaskStatusVO.setSprayTaskSprayedList(sprayTask.getSprayTaskSprayedList()); sprayTaskStatusVO.setSprayTaskParams(sprayTask.getSprayTaskParams()); + sprayTaskStatusVO.setFinishTime(sprayTask.getFinishTimeMap()); return Result.success(sprayTaskStatusVO); } diff --git a/src/main/java/com/qyft/ms/app/device/spray/SprayTaskExecutor.java b/src/main/java/com/qyft/ms/app/device/spray/SprayTaskExecutor.java index 3d87241..fc825ab 100644 --- a/src/main/java/com/qyft/ms/app/device/spray/SprayTaskExecutor.java +++ b/src/main/java/com/qyft/ms/app/device/spray/SprayTaskExecutor.java @@ -16,6 +16,7 @@ import com.qyft.ms.system.common.constant.CommandStatus; import com.qyft.ms.system.common.device.command.CommandFuture; import com.qyft.ms.system.common.device.command.DeviceCommandGenerator; import com.qyft.ms.system.common.device.command.FrontResponseGenerator; +import com.qyft.ms.system.common.utils.TimeUtils; import com.qyft.ms.system.model.bo.DeviceCommand; import com.qyft.ms.system.service.WebSocketService; import com.qyft.ms.system.service.device.DeviceCommandService; @@ -24,9 +25,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; @@ -74,6 +75,10 @@ public class SprayTaskExecutor { taskThread = new Thread(() -> { try { webSocketService.pushCMDResponseMsg(FrontResponseGenerator.generateJson(sprayTask.getCmdId(), sprayTask.getCmdCode(), CommandStatus.START, "喷涂任务开始执行")); + //计算剩余时间 + List> finishTimeMap = estimateCompletionTimePerSlide(); + webSocketService.pushMsg(WebSocketMessageType.SPRAY_TASK_FINISH_TIME, finishTimeMap);//向前端推送当前路径 + sprayTask.setFinishTimeMap(finishTimeMap); List sprayTaskParams = sprayTask.getSprayTaskParams(); for (SprayTaskParams sprayTaskParam : sprayTaskParams) {//循环玻片 if (sprayTask.getCurrentIndex() != null && sprayTaskParam.getIndex() < sprayTask.getCurrentIndex()) {//喷涂过的玻片跳过 @@ -107,7 +112,7 @@ public class SprayTaskExecutor { SysSettings slideHeightSysSettings = sysSettingsService.getOne(new LambdaQueryWrapper().eq(SysSettings::getCode, "slide_height")); Double slideHeight = Double.parseDouble(slideHeightSysSettings.getValue()); Double height = slideHeight - sprayTimes.getMotorZHeight();//下降z轴高度 - DeviceCommand motorZPositionSetAboveSlideCommand = DeviceCommandGenerator.motorZPositionSet(height, 15.0); + DeviceCommand motorZPositionSetAboveSlideCommand = DeviceCommandGenerator.motorZPositionSet(height, 20.0); CommandFuture motorZPositionSetAboveSlideCommandFuture = deviceCommandService.sendCommandSprayTask(sprayTask.getCmdId(), sprayTask.getCmdCode(), motorZPositionSetAboveSlideCommand); commandWait(motorXPositionSetCommandFuture, motorYPositionSetCommandFuture, motorZPositionSetAboveSlideCommandFuture); } @@ -305,6 +310,128 @@ public class SprayTaskExecutor { return sprayTaskStepList; } + /** + * 返回一个 List,每个元素对应 sprayTaskParams 中每个玻片的预计耗时(毫秒), + */ + public List> estimateCompletionTimePerSlide() { + SprayTask task = SprayTask.getInstance(); + List params = task.getSprayTaskParams(); + int resumeIndex = task.getCurrentIndex() == null ? 0 : task.getCurrentIndex(); + int resumeSprayNum = task.getSprayNum(); + + // 常量 + final long COMMAND_LATENCY_MS = 12;// 指令往返平均耗时(ms) + final double HOME_SPEED_X = 20;// 回原点速度 X 轴 (mm/s) + final double HOME_SPEED_Y = 20;// 回原点速度 Y 轴 (mm/s) + final double HOME_SPEED_Z = 20;// 回原点速度 Z 轴 (mm/s) + final long PER_SPRAY_OVERHEAD_MS = 1;// 每次喷涂固定附加时间(ms) + final long TOTAL_JOB_OVERHEAD_MS = 1;// 全局附加时间(ms) + + double slideHeight = Double.parseDouble( + sysSettingsService.getOne( + new LambdaQueryWrapper() + .eq(SysSettings::getCode, "slide_height")) + .getValue() + ); + + LocalDateTime startTime = LocalDateTime.now(); + List> finishTimeMap = new ArrayList<>(params.size()); + + long cumulativeMs = 0; + for (int i = 0; i < params.size(); i++) { + SprayTaskParams p = params.get(i); + int idx = p.getIndex(); + // 如果此玻片已在之前完成,直接添加当前累计时间 + if (idx < resumeIndex) { + Map ft = new HashMap<>(); + ft.put("index", p.getIndex()); + ft.put("finishTime", startTime.plus(Duration.ofMillis(cumulativeMs))); + finishTimeMap.add(ft); + continue; + } + + long sliceMs = 0; + // 1) 移到玻片起点 + 下降 + Double[] slide = slideArr[idx]; + SprayTimes firstT = p.getTimes().get(0); + sliceMs += moveMs(0, slide[0], firstT.getMovingSpeed(), COMMAND_LATENCY_MS); + sliceMs += moveMs(0, slide[1], firstT.getMovingSpeed(), COMMAND_LATENCY_MS); + double targetZ = slideHeight - firstT.getMotorZHeight(); + sliceMs += moveMs(0, targetZ, 20, COMMAND_LATENCY_MS); + + // 2) 管路切换 & 等待(已在中途恢复时跳过) + boolean switched = (idx == resumeIndex && resumeSprayNum > 1); + if (!switched) { + sliceMs += COMMAND_LATENCY_MS + 500;//500是切换注射器固定等待时间 + } + + // 3) 喷涂循环 + double curX = slide[0], curY = slide[1]; + int sprayCount = 1; + for (SprayTimes t : p.getTimes()) { + // 跳过已完成的喷涂轮次 + if (idx == resumeIndex && sprayCount < resumeSprayNum) { + // grid 模式双计数 + if ("grid".equals(t.getMatrixPathType())) sprayCount++; + sprayCount++; + continue; + } + // 开阀/泵/高压指令 + sliceMs += COMMAND_LATENCY_MS * 3; + if (t.getHighVoltage()) sliceMs += COMMAND_LATENCY_MS * 2; + // 路径移动 + for (SprayTaskStep step : getSprayPath(t)) { + for (Point2D pt : step.getSprayPathPointList()) { + double nx = slide[0] + pt.getX(); + double ny = slide[1] + pt.getY(); + sliceMs += moveMs(curX, nx, t.getMovingSpeed(), COMMAND_LATENCY_MS); + curX = nx; + sliceMs += moveMs(curY, ny, t.getMovingSpeed(), COMMAND_LATENCY_MS); + curY = ny; + } + // 结束阀门指令 + grid 延时 + sliceMs += COMMAND_LATENCY_MS * 3; + if ("grid".equals(t.getMatrixPathType()) && t.getGridDelay() != null) { + sliceMs += t.getGridDelay() * 1000; + } + } + // 喷涂后延时 + 附加时间 + sliceMs += t.getDelay() * 1000; + sliceMs += PER_SPRAY_OVERHEAD_MS; + sprayCount++; + } + + // 4) 回原点 + sliceMs += moveMs(curX, 0, HOME_SPEED_X, COMMAND_LATENCY_MS); + sliceMs += moveMs(curY, 0, HOME_SPEED_Y, COMMAND_LATENCY_MS); + sliceMs += moveMs(targetZ, 0, HOME_SPEED_Z, COMMAND_LATENCY_MS); + + // 累计并加全局附加 + cumulativeMs += sliceMs; + if (i == params.size() - 1) cumulativeMs += TOTAL_JOB_OVERHEAD_MS; + Map ft = new HashMap<>(); + ft.put("index", p.getIndex()); + ft.put("finishTime", TimeUtils.toEpochMilli(startTime.plus(Duration.ofMillis(cumulativeMs)))); + finishTimeMap.add(ft); + } + + return finishTimeMap; + } + + /** + * 计算单轴移动耗时 + 指令平均延迟 + * + * @param from 起始坐标 (mm) + * @param to 目标坐标 (mm) + * @param speed 运动速度 (mm/s) + * @param cmdDelay 每条指令往返延迟 (ms) + */ + private long moveMs(double from, double to, double speed, long cmdDelay) { + double dist = Math.abs(to - from); + return (long) (dist / speed * 1000) + cmdDelay; + } + + private void delay(long millisecond) throws InterruptedException { delay(null, millisecond); } diff --git a/src/main/java/com/qyft/ms/app/device/status/SprayTask.java b/src/main/java/com/qyft/ms/app/device/status/SprayTask.java index 977d0b6..cc5eb46 100644 --- a/src/main/java/com/qyft/ms/app/device/status/SprayTask.java +++ b/src/main/java/com/qyft/ms/app/device/status/SprayTask.java @@ -8,6 +8,7 @@ import lombok.Data; import java.util.ArrayList; import java.util.List; +import java.util.Map; @Data public class SprayTask { @@ -64,7 +65,10 @@ public class SprayTask { * 已喷涂点位 */ private volatile List sprayTaskSprayedList = new ArrayList<>(); - + /** + * 喷涂任务预计剩余时间 + */ + private List> finishTimeMap = null; /** * 标志喷涂任务暂停 */ @@ -104,6 +108,7 @@ public class SprayTask { operationLogId = null; remainingGridDelay = null; remainingDelay = null; + finishTimeMap = null; sprayTaskSprayedList.clear(); } diff --git a/src/main/java/com/qyft/ms/app/model/vo/SprayTaskStatusVO.java b/src/main/java/com/qyft/ms/app/model/vo/SprayTaskStatusVO.java index 28621cb..5b12b6b 100644 --- a/src/main/java/com/qyft/ms/app/model/vo/SprayTaskStatusVO.java +++ b/src/main/java/com/qyft/ms/app/model/vo/SprayTaskStatusVO.java @@ -13,25 +13,19 @@ import java.util.Map; */ @Data public class SprayTaskStatusVO { - /** - * 前端指令id - */ + private String cmdId; - /** - * 前端指令code - */ + private String cmdCode; - /** - * 已喷涂点位 - */ + @Schema(description = "已喷涂点位") private volatile List sprayTaskSprayedList; - /** - * 喷涂参数 - */ @Schema(description = "喷涂参数") private List sprayTaskParams; + @Schema(description = "喷涂任务预计剩余时间") + private List> finishTime; + } diff --git a/src/main/java/com/qyft/ms/system/common/utils/TimeUtils.java b/src/main/java/com/qyft/ms/system/common/utils/TimeUtils.java new file mode 100644 index 0000000..80e733c --- /dev/null +++ b/src/main/java/com/qyft/ms/system/common/utils/TimeUtils.java @@ -0,0 +1,26 @@ +package com.qyft.ms.system.common.utils; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimeUtils { + /** + * 将 LocalDateTime 转为毫秒级时间戳,使用系统默认时区 + */ + public static long toEpochMilli(LocalDateTime ldt) { + return ldt + .atZone(ZoneId.systemDefault()) // LocalDateTime → ZonedDateTime(系统默认时区) + .toInstant() // ZonedDateTime → Instant + .toEpochMilli(); // Instant → 毫秒时间戳 + } + + /** + * 将 LocalDateTime 转为秒级时间戳,使用系统默认时区 + */ + public static long toEpochSecond(LocalDateTime ldt) { + return ldt + .atZone(ZoneId.systemDefault()) + .toInstant() + .getEpochSecond(); + } +}