|
|
@@ -4,9 +4,13 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
|
|
|
import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
|
|
|
import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
|
|
|
import cn.sciento.farm.automationv2.domain.entity.TaskExecution;
|
|
|
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskGroupNodeVO;
|
|
|
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskGroupVO;
|
|
|
import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskLog;
|
|
|
import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
|
|
|
import cn.sciento.farm.automationv2.domain.enums.AckStatus;
|
|
|
+import cn.sciento.farm.automationv2.domain.enums.DeviceType;
|
|
|
+import cn.sciento.farm.automationv2.domain.enums.NodeType;
|
|
|
import cn.sciento.farm.automationv2.domain.repository.IrrigationTaskLogRepository;
|
|
|
import cn.sciento.farm.automationv2.domain.repository.TaskExecutionRepository;
|
|
|
import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
|
|
|
@@ -71,6 +75,12 @@ public class SafeShutdownService {
|
|
|
List<Long> failedDevices = new ArrayList<>();
|
|
|
List<Long> successDevices = new ArrayList<>();
|
|
|
|
|
|
+ Optional<IrrigationTaskLog> logOpt = taskLogRepository.findByExecutionId(executionId);
|
|
|
+ IrrigationTaskLog taskLog = logOpt.get();
|
|
|
+
|
|
|
+ // 找出取消的灌区(未完成的灌区)并标记为失败
|
|
|
+ markCancelledZonesAsFailed(execution, taskLog);
|
|
|
+
|
|
|
// 步骤1: 关闭施肥机
|
|
|
// if (openedDevices.fertilizer != null) {
|
|
|
// log.info("步骤1: 关闭施肥机,deviceId={}", openedDevices.fertilizer.getDeviceId());
|
|
|
@@ -85,7 +95,7 @@ public class SafeShutdownService {
|
|
|
// 步骤2: 关闭水泵
|
|
|
if (openedDevices.pump != null) {
|
|
|
log.info("步骤2: 关闭水泵,deviceId={}", openedDevices.pump.getDeviceId());
|
|
|
- boolean success = closeDevice(executionId, openedDevices.pump, execution.getTenantId());
|
|
|
+ boolean success = closeDevice(execution, openedDevices.pump, taskLog, null);
|
|
|
if (success) {
|
|
|
successDevices.add(openedDevices.pump.getDeviceId());
|
|
|
IrrigationTaskMainVO mainVO = logService.getByTaskMainData(execution.getTaskId());
|
|
|
@@ -103,7 +113,9 @@ public class SafeShutdownService {
|
|
|
for (DeviceInfo valve : openedDevices.valves) {
|
|
|
log.info("步骤3: 关闭阀门,deviceId={}, deviceType={}",
|
|
|
valve.getDeviceId(), valve.getDeviceType());
|
|
|
- boolean success = closeDevice(executionId, valve, execution.getTenantId());
|
|
|
+ // 找到该阀门所属的灌区
|
|
|
+ Integer zoneIndex = findZoneIndexForDevice(executionId, valve.getDeviceId());
|
|
|
+ boolean success = closeDevice(execution, valve, taskLog, zoneIndex);
|
|
|
if (success) {
|
|
|
successDevices.add(valve.getDeviceId());
|
|
|
} else {
|
|
|
@@ -111,6 +123,9 @@ public class SafeShutdownService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 保存更新后的任务日志
|
|
|
+ taskLogRepository.save(taskLog);
|
|
|
+
|
|
|
// 构建结果
|
|
|
ShutdownResult result;
|
|
|
if (failedDevices.isEmpty()) {
|
|
|
@@ -129,14 +144,19 @@ public class SafeShutdownService {
|
|
|
/**
|
|
|
* 关闭单个设备
|
|
|
*
|
|
|
- * @param executionId 执行实例ID
|
|
|
+ * @param execution 执行实例
|
|
|
* @param device 设备信息
|
|
|
- * @param tenantId 租户ID
|
|
|
+ * @param taskLog 任务日志
|
|
|
+ * @param zoneIndex 灌区索引(可为null,表示不属于特定灌区)
|
|
|
* @return true-成功,false-失败
|
|
|
*/
|
|
|
- private boolean closeDevice(Long executionId, DeviceInfo device, Long tenantId) {
|
|
|
+ private boolean closeDevice(TaskExecution execution, DeviceInfo device,
|
|
|
+ IrrigationTaskLog taskLog, Integer zoneIndex) {
|
|
|
+ Long executionId = execution.getId();
|
|
|
+ Long tenantId = execution.getTenantId();
|
|
|
Long deviceId = device.getDeviceId();
|
|
|
String deviceType = device.getDeviceType();
|
|
|
+ NodeType closeAction = getCloseActionType(deviceType);
|
|
|
|
|
|
try {
|
|
|
// 注册ACK期望
|
|
|
@@ -170,23 +190,33 @@ public class SafeShutdownService {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ boolean success = result != null && result == 1;
|
|
|
+ String status = success ? "SUCCESS" : "FAILED";
|
|
|
+
|
|
|
// 根据返回值更新ACK状态
|
|
|
- if (result != null && result == 1) {
|
|
|
+ if (success) {
|
|
|
ackManager.updateAckStatus(executionId, -1, deviceId,
|
|
|
AckStatus.SUCCESS, null);
|
|
|
log.info("设备关闭成功,deviceId={}", deviceId);
|
|
|
- return true;
|
|
|
} else {
|
|
|
ackManager.updateAckStatus(executionId, -1, deviceId,
|
|
|
AckStatus.FAIL, "设备控制返回失败,result=" + result);
|
|
|
log.error("设备关闭失败,deviceId={}, result={}", deviceId, result);
|
|
|
- return false;
|
|
|
}
|
|
|
|
|
|
+ // 记录关闭操作到灌区日志
|
|
|
+ recordDeviceCloseToLog(execution, taskLog, device, closeAction, status, zoneIndex);
|
|
|
+
|
|
|
+ return success;
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
|
ackManager.updateAckStatus(executionId, -1, deviceId,
|
|
|
AckStatus.FAIL, "调用设备控制异常: " + e.getMessage());
|
|
|
log.error("关闭设备异常,deviceId={}", deviceId, e);
|
|
|
+
|
|
|
+ // 记录失败到日志
|
|
|
+ recordDeviceCloseToLog(execution, taskLog, device, closeAction, "FAILED", zoneIndex);
|
|
|
+
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
@@ -203,17 +233,22 @@ public class SafeShutdownService {
|
|
|
}
|
|
|
|
|
|
OpenedDevices result = new OpenedDevices();
|
|
|
-
|
|
|
- for (ExecutionNode node : plan.getSuccessNodes()) {
|
|
|
- switch (node.getNodeType()) {
|
|
|
- case START_PUMP:
|
|
|
- // 找到水泵设备
|
|
|
- result.pump = node.getDevices().stream()
|
|
|
- .filter(d -> "PUMP".equals(d.getDeviceType()))
|
|
|
+ // 直接关闭水泵(含重复操作)
|
|
|
+ result.pump = plan.getWaterPumpNodes().getDevices().stream()
|
|
|
+ .filter(d -> DeviceType.PUMP.getCode().equals(d.getDeviceType()))
|
|
|
.findFirst()
|
|
|
.orElse(null);
|
|
|
- break;
|
|
|
|
|
|
+ for (ExecutionNode node : plan.getSuccessNodes()) {
|
|
|
+ switch (node.getNodeType()) {
|
|
|
+// case START_PUMP:
|
|
|
+// // 找到水泵设备
|
|
|
+// result.pump = node.getDevices().stream()
|
|
|
+// .filter(d -> "PUMP".equals(d.getDeviceType()))
|
|
|
+// .findFirst()
|
|
|
+// .orElse(null);
|
|
|
+//
|
|
|
+// break;
|
|
|
// case START_FERTILIZER:
|
|
|
// // 找到施肥机设备
|
|
|
// result.fertilizer = node.getDevices().stream()
|
|
|
@@ -225,8 +260,8 @@ public class SafeShutdownService {
|
|
|
case OPEN_GROUP:
|
|
|
// 收集电磁阀和球阀
|
|
|
List<DeviceInfo> valves = node.getDevices().stream()
|
|
|
- .filter(d -> "SOLENOID_VALVE".equals(d.getDeviceType()) ||
|
|
|
- "BALL_VALVE".equals(d.getDeviceType()))
|
|
|
+ .filter(d -> DeviceType.SOLENOID_VALVE.getCode().equals(d.getDeviceType()) ||
|
|
|
+ DeviceType.BALL_VALVE.getCode().equals(d.getDeviceType()))
|
|
|
.collect(Collectors.toList());
|
|
|
result.valves.addAll(valves);
|
|
|
// 如果存在同灌区ack部分成功的,需要额外查出
|
|
|
@@ -254,6 +289,189 @@ public class SafeShutdownService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 标记取消的灌区为失败状态(仅标记当前正在执行的灌区)
|
|
|
+ *
|
|
|
+ * @param execution 执行实例
|
|
|
+ * @param taskLog 任务日志
|
|
|
+ */
|
|
|
+ private void markCancelledZonesAsFailed(TaskExecution execution, IrrigationTaskLog taskLog) {
|
|
|
+ if (taskLog == null || taskLog.getGroups() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从执行计划获取当前节点的灌区索引
|
|
|
+ ExecutionPlan plan = executionPlanStore.get(execution.getId());
|
|
|
+ if (plan == null) {
|
|
|
+ log.warn("执行计划不存在,executionId={}", execution.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer currentIndex = execution.getCurrentIndex();
|
|
|
+ if (currentIndex == null) {
|
|
|
+ log.warn("当前节点索引为空,executionId={}", execution.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ExecutionNode currentNode = plan.getNode(currentIndex);
|
|
|
+ if (currentNode == null) {
|
|
|
+ log.warn("当前节点不存在,executionId={}, currentIndex={}", execution.getId(), currentIndex);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer currentZoneIndex = currentNode.getZoneIndex();
|
|
|
+ if (currentZoneIndex == null || currentZoneIndex < 0 || currentZoneIndex >= taskLog.getGroups().size()) {
|
|
|
+ log.warn("当前灌区索引无效,executionId={}, zoneIndex={}", execution.getId(), currentZoneIndex);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只标记当前正在执行的灌区为失败
|
|
|
+ IrrigationTaskGroupVO currentGroup = taskLog.getGroups().get(currentZoneIndex);
|
|
|
+ if (!"SUCCESS".equals(currentGroup.getStatus())) {
|
|
|
+ log.info("标记当前灌区为失败,灌区名称={}, 原状态={}",
|
|
|
+ currentGroup.getGroupName(), currentGroup.getStatus());
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ currentGroup.setStatus("FAILED");
|
|
|
+ if (currentGroup.getGroupEndTime() == null) {
|
|
|
+ currentGroup.setGroupEndTime(now);
|
|
|
+ }
|
|
|
+ taskLog.getFailVO().setFailGroupIndex(currentZoneIndex);
|
|
|
+ taskLog.getFailVO().setCurrentIndex(currentIndex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找设备所属的灌区索引
|
|
|
+ *
|
|
|
+ * @param executionId 执行实例ID
|
|
|
+ * @param deviceId 设备ID
|
|
|
+ * @return 灌区索引,如果未找到返回null
|
|
|
+ */
|
|
|
+ private Integer findZoneIndexForDevice(Long executionId, Long deviceId) {
|
|
|
+ ExecutionPlan plan = executionPlanStore.get(executionId);
|
|
|
+ if (plan == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历所有节点,找到包含该设备的 OPEN_GROUP 节点
|
|
|
+ for (ExecutionNode node : plan.getNodes()) {
|
|
|
+ if (node.getNodeType() == NodeType.OPEN_GROUP && node.getDevices() != null) {
|
|
|
+ for (DeviceInfo device : node.getDevices()) {
|
|
|
+ if (device.getDeviceId().equals(deviceId)) {
|
|
|
+ return node.getZoneIndex();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录设备关闭操作到灌区日志
|
|
|
+ *
|
|
|
+ * @param execution 执行实例
|
|
|
+ * @param taskLog 任务日志
|
|
|
+ * @param device 设备信息
|
|
|
+ * @param closeAction 关闭动作类型
|
|
|
+ * @param status 操作状态
|
|
|
+ * @param zoneIndex 灌区索引(可为null)
|
|
|
+ */
|
|
|
+ private void recordDeviceCloseToLog(TaskExecution execution, IrrigationTaskLog taskLog, DeviceInfo device,
|
|
|
+ NodeType closeAction, String status, Integer zoneIndex) {
|
|
|
+ if (taskLog == null || taskLog.getGroups() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+
|
|
|
+ // 如果指定了灌区索引,只记录到该灌区
|
|
|
+ if (zoneIndex != null && zoneIndex >= 0 && zoneIndex < taskLog.getGroups().size()) {
|
|
|
+ IrrigationTaskGroupVO group = taskLog.getGroups().get(zoneIndex);
|
|
|
+ addCloseNodeToGroup(group, device, closeAction, status, now);
|
|
|
+ log.info("记录设备关闭到灌区日志,灌区={}, 设备ID={}, 状态={}",
|
|
|
+ group.getGroupName(), device.getDeviceId(), status);
|
|
|
+ } else {
|
|
|
+ // 如果没有指定灌区(如水泵),只记录到当前正在执行的灌区
|
|
|
+ ExecutionPlan plan = executionPlanStore.get(execution.getId());
|
|
|
+ if (plan == null) {
|
|
|
+ log.warn("执行计划不存在,无法记录设备关闭日志,executionId={}", execution.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer currentIndex = execution.getCurrentIndex();
|
|
|
+ if (currentIndex == null) {
|
|
|
+ log.warn("当前节点索引为空,无法记录设备关闭日志,executionId={}", execution.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ExecutionNode currentNode = plan.getNode(currentIndex);
|
|
|
+ if (currentNode == null) {
|
|
|
+ log.warn("当前节点不存在,无法记录设备关闭日志,executionId={}, currentIndex={}",
|
|
|
+ execution.getId(), currentIndex);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer currentZoneIndex = currentNode.getZoneIndex();
|
|
|
+ if (currentZoneIndex != null && currentZoneIndex >= 0 && currentZoneIndex < taskLog.getGroups().size()) {
|
|
|
+ IrrigationTaskGroupVO currentGroup = taskLog.getGroups().get(currentZoneIndex);
|
|
|
+ addCloseNodeToGroup(currentGroup, device, closeAction, status, now);
|
|
|
+ log.info("记录设备关闭到当前灌区日志,灌区={}, 设备ID={}, 状态={}",
|
|
|
+ currentGroup.getGroupName(), device.getDeviceId(), status);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加关闭节点到灌区
|
|
|
+ *
|
|
|
+ * @param group 灌区VO
|
|
|
+ * @param device 设备信息
|
|
|
+ * @param closeAction 关闭动作类型
|
|
|
+ * @param status 操作状态
|
|
|
+ * @param time 操作时间
|
|
|
+ */
|
|
|
+ private void addCloseNodeToGroup(IrrigationTaskGroupVO group, DeviceInfo device,
|
|
|
+ NodeType closeAction, String status, LocalDateTime time) {
|
|
|
+ if (group.getNodes() == null) {
|
|
|
+ group.setNodes(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ IrrigationTaskGroupNodeVO nodeVO = new IrrigationTaskGroupNodeVO();
|
|
|
+ nodeVO.setDeviceId(device.getDeviceId().toString());
|
|
|
+ nodeVO.setDeviceName(device.getDeviceName());
|
|
|
+ nodeVO.setDeviceType(device.getDeviceType());
|
|
|
+ nodeVO.setBrand(device.getBrand());
|
|
|
+ nodeVO.setVtype(device.getVtype());
|
|
|
+ nodeVO.setSw(device.getSw());
|
|
|
+ nodeVO.setAction(closeAction);
|
|
|
+ nodeVO.setStatus(status);
|
|
|
+ nodeVO.setDeviceBeginTime(time);
|
|
|
+ nodeVO.setDeviceEndTime(time);
|
|
|
+
|
|
|
+ group.getNodes().add(nodeVO);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据设备类型获取关闭动作类型
|
|
|
+ *
|
|
|
+ * @param deviceType 设备类型
|
|
|
+ * @return 关闭动作类型
|
|
|
+ */
|
|
|
+ private NodeType getCloseActionType(String deviceType) {
|
|
|
+ switch (deviceType) {
|
|
|
+ case "PUMP":
|
|
|
+ return NodeType.STOP_PUMP;
|
|
|
+ case "FERTILIZER":
|
|
|
+ return NodeType.STOP_FERTILIZER;
|
|
|
+ case "SOLENOID_VALVE":
|
|
|
+ case "BALL_VALVE":
|
|
|
+ return NodeType.CLOSE_GROUP;
|
|
|
+ default:
|
|
|
+ return NodeType.CLOSE_GROUP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 已开启设备容器
|
|
|
*/
|
|
|
private static class OpenedDevices {
|