feat(inspection): 新增扬声器播放音频功能并优化机器人状态同步机制
- 在 ActionEnum 中添加 SPEAKER_PLAYAUDIO 操作类型 - 配置机器人状态检查任务的启用和时间间隔参数 - 移除应用配置中的废弃TTS外部接口配置 - 实现 EdgeSpeakerOperateService 中的音频播放功能和参数验证 - 重构 FlowActionExecutorService 以支持多种操作类型的服务分发 - 更新数据库表结构中机器人状态字段的含义和注释说明 - 修改 InspectionRobot 实体类中的状态定义和业务编号描述 - 扩展 InspectionRobotMapper 接口以支持运行时状态更新和候选查询 - 实现 InspectionRobotService 中的 AGV 请求构建和状态同步逻辑 - 添加 RobotRuntimeStateRefreshTask 定时任务来同步机器人运行状态 - 修复 InspectionTaskInstanceService 中的重复机器人ID设置问题 - 优化 RobotQuicStateService 中的设备状态判断逻辑
This commit is contained in:
parent
86a8e02d85
commit
54df691ea6
@ -132,8 +132,3 @@ flowise:
|
|||||||
abort: http://192.168.0.108:3000/api/v1/chatmessage/abort/f99329e9-b33d-437d-90ed-69eaa8a05418/
|
abort: http://192.168.0.108:3000/api/v1/chatmessage/abort/f99329e9-b33d-437d-90ed-69eaa8a05418/
|
||||||
query: http://192.168.0.108:3000/api/v1/executions/
|
query: http://192.168.0.108:3000/api/v1/executions/
|
||||||
api-key: pg61JW6W_GXyqmqSoURJ4mlSRrCMRzGLBJli_w3BFkg
|
api-key: pg61JW6W_GXyqmqSoURJ4mlSRrCMRzGLBJli_w3BFkg
|
||||||
|
|
||||||
# TTS外部接口配置
|
|
||||||
tts:
|
|
||||||
external-api:
|
|
||||||
url: http://192.168.0.102:9003/generate_advanced_audio
|
|
||||||
|
|||||||
@ -124,6 +124,11 @@ xss:
|
|||||||
|
|
||||||
# Integrated QUIC gateway and in-process node synchronization.
|
# Integrated QUIC gateway and in-process node synchronization.
|
||||||
cmvr:
|
cmvr:
|
||||||
|
inspection:
|
||||||
|
robot-state:
|
||||||
|
enabled: true
|
||||||
|
initial-delay-ms: 3000
|
||||||
|
refresh-ms: 1000
|
||||||
quic:
|
quic:
|
||||||
enabled: true
|
enabled: true
|
||||||
bind-host: 0.0.0.0
|
bind-host: 0.0.0.0
|
||||||
|
|||||||
@ -38,9 +38,9 @@ public class InspectionRobot extends BaseEntity
|
|||||||
@ApiModelProperty("机器人永久唯一ID")
|
@ApiModelProperty("机器人永久唯一ID")
|
||||||
private String robotId;
|
private String robotId;
|
||||||
|
|
||||||
/** 机器人编码 */
|
/** Business display code. It is not an edge device ID. */
|
||||||
@Excel(name = "机器人编码")
|
@Excel(name = "机器人业务编号")
|
||||||
@ApiModelProperty("机器人编码")
|
@ApiModelProperty("机器人业务编号,仅用于业务展示,不作为设备ID")
|
||||||
private String robotCode;
|
private String robotCode;
|
||||||
|
|
||||||
/** 机器人名称 */
|
/** 机器人名称 */
|
||||||
@ -75,7 +75,7 @@ public class InspectionRobot extends BaseEntity
|
|||||||
|
|
||||||
/** 当前状态 */
|
/** 当前状态 */
|
||||||
@Excel(name = "当前状态")
|
@Excel(name = "当前状态")
|
||||||
@ApiModelProperty("当前状态(0在线 1离线 2充电中 3巡检中)")
|
@ApiModelProperty("AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 电量百分比 */
|
/** 电量百分比 */
|
||||||
|
|||||||
@ -34,9 +34,9 @@ public class InspectionRobotVo extends BaseEntity
|
|||||||
@ApiModelProperty("机器人永久唯一ID")
|
@ApiModelProperty("机器人永久唯一ID")
|
||||||
private String robotId;
|
private String robotId;
|
||||||
|
|
||||||
/** 机器人编码 */
|
/** Business display code. It is not an edge device ID. */
|
||||||
@Excel(name = "机器人编码")
|
@Excel(name = "机器人业务编号")
|
||||||
@ApiModelProperty("机器人编码")
|
@ApiModelProperty("机器人业务编号,仅用于业务展示,不作为设备ID")
|
||||||
private String robotCode;
|
private String robotCode;
|
||||||
|
|
||||||
/** 机器人名称 */
|
/** 机器人名称 */
|
||||||
@ -81,7 +81,7 @@ public class InspectionRobotVo extends BaseEntity
|
|||||||
|
|
||||||
/** 当前状态 */
|
/** 当前状态 */
|
||||||
@Excel(name = "当前状态")
|
@Excel(name = "当前状态")
|
||||||
@ApiModelProperty("当前状态(0在线 1离线 2充电中 3巡检中)")
|
@ApiModelProperty("AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 电量百分比 */
|
/** 电量百分比 */
|
||||||
|
|||||||
@ -27,6 +27,16 @@ public interface InspectionRobotMapper extends MPJBaseMapper<InspectionRobot>
|
|||||||
|
|
||||||
int updateQuicRuntimeState(InspectionRobot robot);
|
int updateQuicRuntimeState(InspectionRobot robot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the AGV runtime snapshot without changing business audit fields.
|
||||||
|
*/
|
||||||
|
int updateRuntimeStatus(InspectionRobot robot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select connected robots that currently report one online AGV device.
|
||||||
|
*/
|
||||||
|
List<InspectionRobot> selectRuntimeSyncCandidates();
|
||||||
|
|
||||||
int archiveInspectionRobotByIds(@Param("ids") String[] ids, @Param("updateBy") String updateBy);
|
int archiveInspectionRobotByIds(@Param("ids") String[] ids, @Param("updateBy") String updateBy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import java.util.Objects;
|
|||||||
public class RobotQuicStateService {
|
public class RobotQuicStateService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RobotQuicStateService.class);
|
private static final Logger log = LoggerFactory.getLogger(RobotQuicStateService.class);
|
||||||
|
private static final int AGV_DEVICE_KIND = 1;
|
||||||
|
|
||||||
private final InspectionRobotMapper robotMapper;
|
private final InspectionRobotMapper robotMapper;
|
||||||
private final InspectionRobotDeviceMapper deviceMapper;
|
private final InspectionRobotDeviceMapper deviceMapper;
|
||||||
@ -90,6 +91,9 @@ public class RobotQuicStateService {
|
|||||||
robot.setSoftwareVersion(node.getSoftwareVersion());
|
robot.setSoftwareVersion(node.getSoftwareVersion());
|
||||||
robot.setLastHeartbeatTime(eventTime);
|
robot.setLastHeartbeatTime(eventTime);
|
||||||
robot.setUpdateTime(DateUtils.getNowDate());
|
robot.setUpdateTime(DateUtils.getNowDate());
|
||||||
|
if (offline || !hasEnabledAgv(node)) {
|
||||||
|
robot.setStatus("1");
|
||||||
|
}
|
||||||
robotMapper.updateQuicRuntimeState(robot);
|
robotMapper.updateQuicRuntimeState(robot);
|
||||||
|
|
||||||
if (offline) {
|
if (offline) {
|
||||||
@ -162,6 +166,11 @@ public class RobotQuicStateService {
|
|||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasEnabledAgv(NodeSnapshot node) {
|
||||||
|
return node.getDeviceManager().getDevicesList().stream()
|
||||||
|
.anyMatch(device -> device.getKind() == AGV_DEVICE_KIND && device.getEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveGrpcHost(NodeSnapshot node) {
|
private String resolveGrpcHost(NodeSnapshot node) {
|
||||||
String advertised = StringUtils.trimToNull(node.getGrpcEndpoint().getHost());
|
String advertised = StringUtils.trimToNull(node.getGrpcEndpoint().getHost());
|
||||||
String observed = StringUtils.trimToNull(node.getObservedSourceIp());
|
String observed = StringUtils.trimToNull(node.getObservedSourceIp());
|
||||||
|
|||||||
@ -2,9 +2,11 @@
|
|||||||
package com.cmvr.inspection.service.impl;
|
package com.cmvr.inspection.service.impl;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import cmvr.msgs.AgvUtils;
|
import cmvr.msgs.AgvUtils;
|
||||||
|
import cmvr.quic_edge.v1.QuicEdge;
|
||||||
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
|
||||||
import com.cmvr.common.utils.SecurityUtils;
|
import com.cmvr.common.utils.SecurityUtils;
|
||||||
import com.cmvr.edge.client.model.EdgeCommonVO;
|
import com.cmvr.edge.client.model.EdgeCommonVO;
|
||||||
@ -167,6 +169,50 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
return robotId == null ? null : robotId.trim();
|
return robotId == null ? null : robotId.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an AGV request from the device snapshot reported by this robot.
|
||||||
|
* robotCode is a business label and must never be used as an edge device ID.
|
||||||
|
*/
|
||||||
|
private EdgeCommonVO buildAgvRequest(InspectionRobot robot)
|
||||||
|
{
|
||||||
|
if (robot == null || StrUtil.isBlank(robot.getRobotId())) {
|
||||||
|
throw new GlobalException("机器人永久ID不能为空");
|
||||||
|
}
|
||||||
|
List<InspectionRobotDevice> agvDevices = inspectionRobotDeviceMapper.selectByRobotIdAndKind(
|
||||||
|
robot.getRobotId(), QuicEdge.DeviceKind.DEVICE_KIND_AGV_VALUE, true);
|
||||||
|
if (agvDevices.isEmpty()) {
|
||||||
|
List<InspectionRobotDevice> onlineDevices = inspectionRobotDeviceMapper.selectByRobotIdAndKind(
|
||||||
|
robot.getRobotId(), null, true);
|
||||||
|
if (onlineDevices.isEmpty()) {
|
||||||
|
throw new GlobalException("机器人当前没有在线设备:" + robot.getRobotId());
|
||||||
|
}
|
||||||
|
String deviceSummary = onlineDevices.stream()
|
||||||
|
.map(device -> device.getDeviceId() + "(" + deviceKindName(device.getDeviceKind()) + ")")
|
||||||
|
.collect(java.util.stream.Collectors.joining("、"));
|
||||||
|
throw new GlobalException("机器人在线设备中没有AGV,当前设备:" + deviceSummary);
|
||||||
|
}
|
||||||
|
if (agvDevices.size() > 1) {
|
||||||
|
String deviceIds = agvDevices.stream()
|
||||||
|
.map(InspectionRobotDevice::getDeviceId)
|
||||||
|
.collect(java.util.stream.Collectors.joining("、"));
|
||||||
|
throw new GlobalException("机器人存在多个在线AGV设备,无法确定默认设备:" + deviceIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeCommonVO request = new EdgeCommonVO();
|
||||||
|
request.setRobotId(robot.getRobotId());
|
||||||
|
request.setDeviceId(agvDevices.get(0).getDeviceId());
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String deviceKindName(Integer deviceKind)
|
||||||
|
{
|
||||||
|
if (deviceKind == null) {
|
||||||
|
return "未知类型";
|
||||||
|
}
|
||||||
|
QuicEdge.DeviceKind kind = QuicEdge.DeviceKind.forNumber(deviceKind);
|
||||||
|
return kind == null ? "未知类型" + deviceKind : kind.name().replace("DEVICE_KIND_", "");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取机器人地图列表(从AGV获取)
|
* 获取机器人地图列表(从AGV获取)
|
||||||
*
|
*
|
||||||
@ -180,15 +226,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
if (robot == null) {
|
if (robot == null) {
|
||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(robot.getIpAddress())) {
|
return edgeAgvService.listMaps(buildAgvRequest(robot));
|
||||||
throw new GlobalException("机器人IP地址未配置");
|
|
||||||
}
|
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
return edgeAgvService.listMaps(edgeCommonVO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,11 +271,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
return edgeAgvService.downloadMap(buildAgvRequest(robot), mapName);
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
return edgeAgvService.downloadMap(edgeCommonVO, mapName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -276,22 +310,16 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. 从来源机器人下载地图JSON
|
// 5. 从来源机器人下载地图JSON
|
||||||
EdgeCommonVO sourceEdgeVO = new EdgeCommonVO();
|
String mapContent = edgeAgvService.downloadMap(
|
||||||
sourceEdgeVO.setRobotId(sourceRobot.getRobotId());
|
buildAgvRequest(sourceRobot), map.getMapSourceName());
|
||||||
sourceEdgeVO.setDeviceId(sourceRobot.getRobotCode());
|
|
||||||
|
|
||||||
String mapContent = edgeAgvService.downloadMap(sourceEdgeVO, map.getMapSourceName());
|
|
||||||
|
|
||||||
if (StrUtil.isBlank(mapContent)) {
|
if (StrUtil.isBlank(mapContent)) {
|
||||||
throw new GlobalException("下载的地图内容为空");
|
throw new GlobalException("下载的地图内容为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 上传地图到目标机器人
|
// 6. 上传地图到目标机器人
|
||||||
EdgeCommonVO targetEdgeVO = new EdgeCommonVO();
|
edgeAgvService.uploadMap(
|
||||||
targetEdgeVO.setRobotId(targetRobot.getRobotId());
|
buildAgvRequest(targetRobot), map.getMapSourceName(), mapContent);
|
||||||
targetEdgeVO.setDeviceId(targetRobot.getRobotCode());
|
|
||||||
|
|
||||||
edgeAgvService.uploadMap(targetEdgeVO, map.getMapSourceName(), mapContent);
|
|
||||||
|
|
||||||
return "上传成功";
|
return "上传成功";
|
||||||
}
|
}
|
||||||
@ -310,31 +338,37 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取运行时状态(包含电池、位置等信息)
|
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(buildAgvRequest(robot));
|
||||||
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO);
|
if (runtimeState == null) {
|
||||||
|
throw new GlobalException("AGV未返回运行状态");
|
||||||
|
}
|
||||||
|
|
||||||
// 设置电池电量
|
// 设置电池电量
|
||||||
if (runtimeState.hasBattery()) {
|
if (runtimeState.hasBattery()) {
|
||||||
robot.setBatteryLevel((int)(runtimeState.getBattery().getPercentage() * 100));
|
int percentage = (int) Math.round(runtimeState.getBattery().getPercentage() * 100D);
|
||||||
|
robot.setBatteryLevel(Math.max(0, Math.min(100, percentage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置位置信息
|
// 设置位置信息
|
||||||
if (runtimeState.hasPose()) {
|
if (runtimeState.hasPose()) {
|
||||||
AgvUtils.AgvPose2d pose = runtimeState.getPose();
|
AgvUtils.AgvPose2d pose = runtimeState.getPose();
|
||||||
robot.setCurrentPosition(pose.getX() + "," + pose.getY() + "," + pose.getTheta());
|
robot.setCurrentPosition(String.format(Locale.ROOT, "%.3f,%.3f,%.3f",
|
||||||
|
pose.getX(), pose.getY(), pose.getTheta()));
|
||||||
}
|
}
|
||||||
|
|
||||||
robot.setUpdateTime(DateUtils.getNowDate());
|
// Keep the platform status identical to the edge AgvMode value.
|
||||||
inspectionRobotMapper.updateInspectionRobot(robot);
|
robot.setStatus(String.valueOf(runtimeState.getMode()));
|
||||||
|
|
||||||
|
inspectionRobotMapper.updateRuntimeStatus(robot);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 记录日志但不抛出异常,允许部分失败
|
if (e instanceof GlobalException) {
|
||||||
e.printStackTrace();
|
throw (GlobalException) e;
|
||||||
|
}
|
||||||
|
robot.setStatus("0");
|
||||||
|
inspectionRobotMapper.updateRuntimeStatus(robot);
|
||||||
|
throw new GlobalException("获取机器人运行状态失败:" +
|
||||||
|
(StrUtil.isBlank(e.getMessage()) ? e.getClass().getSimpleName() : e.getMessage()), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return robot;
|
return robot;
|
||||||
@ -354,11 +388,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(buildAgvRequest(robot));
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
AgvUtils.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO);
|
|
||||||
return runtimeState.hasPose() ? runtimeState.getPose() : null;
|
return runtimeState.hasPose() ? runtimeState.getPose() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,12 +419,8 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
.setTheta(theta != null ? theta : 0.0)
|
.setTheta(theta != null ? theta : 0.0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
// 调用边缘端接口进行导航
|
// 调用边缘端接口进行导航
|
||||||
edgeAgvService.navigateToPose(edgeCommonVO, pose);
|
edgeAgvService.navigateToPose(buildAgvRequest(robot), pose);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -414,12 +440,9 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
AgvUtils.AgvMapDimension mapDimension = AgvUtils.AgvMapDimension.forNumber(dimension);
|
AgvUtils.AgvMapDimension mapDimension = AgvUtils.AgvMapDimension.forNumber(dimension);
|
||||||
return edgeAgvService.startMapping(edgeCommonVO, mapDimension, mapName, realTime);
|
return edgeAgvService.startMapping(
|
||||||
|
buildAgvRequest(robot), mapDimension, mapName, realTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -435,11 +458,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
|
|||||||
throw new GlobalException("机器人不存在");
|
throw new GlobalException("机器人不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
|
edgeAgvService.stopMapping(buildAgvRequest(robot));
|
||||||
edgeCommonVO.setRobotId(robot.getRobotId());
|
|
||||||
edgeCommonVO.setDeviceId(robot.getRobotCode());
|
|
||||||
|
|
||||||
edgeAgvService.stopMapping(edgeCommonVO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,7 +170,6 @@ public class InspectionTaskInstanceServiceImpl extends ServiceImpl<InspectionTas
|
|||||||
String insId = flowTaskRuntimeService.executeTask(TeTaskExecuteNormalVO.builder()
|
String insId = flowTaskRuntimeService.executeTask(TeTaskExecuteNormalVO.builder()
|
||||||
.taskId(inspectionTask.getTaskConfigId())
|
.taskId(inspectionTask.getTaskConfigId())
|
||||||
.robotId(robot.getRobotId())
|
.robotId(robot.getRobotId())
|
||||||
.robotId(robot.getRobotId())
|
|
||||||
.moduleCode(FlowExecutionModuleCodes.INSPECTION)
|
.moduleCode(FlowExecutionModuleCodes.INSPECTION)
|
||||||
.runParams(runParams)
|
.runParams(runParams)
|
||||||
.build());
|
.build());
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
package com.cmvr.inspection.task;
|
||||||
|
|
||||||
|
import com.cmvr.inspection.domain.InspectionRobot;
|
||||||
|
import com.cmvr.inspection.mapper.InspectionRobotMapper;
|
||||||
|
import com.cmvr.inspection.service.IInspectionRobotService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.core.task.TaskExecutor;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically persists AGV runtime snapshots for connected robots.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(prefix = "cmvr.inspection.robot-state", name = "enabled",
|
||||||
|
havingValue = "true", matchIfMissing = true)
|
||||||
|
public class RobotRuntimeStateRefreshTask
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RobotRuntimeStateRefreshTask.class);
|
||||||
|
|
||||||
|
private final InspectionRobotMapper inspectionRobotMapper;
|
||||||
|
private final IInspectionRobotService inspectionRobotService;
|
||||||
|
private final TaskExecutor taskExecutor;
|
||||||
|
private final Set<String> syncingRobotIds = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
public RobotRuntimeStateRefreshTask(InspectionRobotMapper inspectionRobotMapper,
|
||||||
|
IInspectionRobotService inspectionRobotService,
|
||||||
|
@Qualifier("threadPoolTaskExecutor") TaskExecutor taskExecutor)
|
||||||
|
{
|
||||||
|
this.inspectionRobotMapper = inspectionRobotMapper;
|
||||||
|
this.inspectionRobotService = inspectionRobotService;
|
||||||
|
this.taskExecutor = taskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(initialDelayString = "${cmvr.inspection.robot-state.initial-delay-ms:3000}",
|
||||||
|
fixedDelayString = "${cmvr.inspection.robot-state.refresh-ms:5000}")
|
||||||
|
public void refreshConnectedRobots()
|
||||||
|
{
|
||||||
|
for (InspectionRobot robot : inspectionRobotMapper.selectRuntimeSyncCandidates()) {
|
||||||
|
if (robot.getId() == null || !syncingRobotIds.add(robot.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
taskExecutor.execute(() -> refreshRobot(robot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshRobot(InspectionRobot robot)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
inspectionRobotService.syncRobotStatus(robot.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Failed to refresh AGV runtime state, robotId={}, reason={}",
|
||||||
|
robot.getRobotId(), e.getMessage());
|
||||||
|
} finally {
|
||||||
|
syncingRobotIds.remove(robot.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -169,6 +169,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<update id="updateQuicRuntimeState" parameterType="InspectionRobot">
|
<update id="updateQuicRuntimeState" parameterType="InspectionRobot">
|
||||||
update inspection_robot
|
update inspection_robot
|
||||||
set ip_address = #{ipAddress}, port = #{port}, connect_status = #{connectStatus},
|
set ip_address = #{ipAddress}, port = #{port}, connect_status = #{connectStatus},
|
||||||
|
status = coalesce(#{status}, status),
|
||||||
quic_node_id = #{quicNodeId}, quic_session_id = #{quicSessionId},
|
quic_node_id = #{quicNodeId}, quic_session_id = #{quicSessionId},
|
||||||
quic_boot_id = #{quicBootId}, observed_ip = #{observedIp},
|
quic_boot_id = #{quicBootId}, observed_ip = #{observedIp},
|
||||||
software_version = #{softwareVersion}, last_heartbeat_time = #{lastHeartbeatTime},
|
software_version = #{softwareVersion}, last_heartbeat_time = #{lastHeartbeatTime},
|
||||||
@ -176,6 +177,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="updateRuntimeStatus" parameterType="InspectionRobot">
|
||||||
|
update inspection_robot
|
||||||
|
set status = coalesce(#{status}, status),
|
||||||
|
battery_level = coalesce(#{batteryLevel}, battery_level),
|
||||||
|
current_position = coalesce(#{currentPosition}, current_position)
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectRuntimeSyncCandidates" resultMap="InspectionRobotResult">
|
||||||
|
select r.id, r.robot_id, r.robot_code, r.robot_name, r.robot_model, r.robot_type,
|
||||||
|
r.ip_address, r.port, r.current_map_id, r.status, r.battery_level,
|
||||||
|
r.current_position, r.connect_status, r.archived_status
|
||||||
|
from inspection_robot r
|
||||||
|
where r.connect_status = '1'
|
||||||
|
and coalesce(r.archived_status, '0') = '0'
|
||||||
|
and (select count(*)
|
||||||
|
from inspection_robot_device d
|
||||||
|
where d.robot_id = r.robot_id
|
||||||
|
and d.device_kind = 1
|
||||||
|
and d.enabled = '1'
|
||||||
|
and d.online_status = '1') = 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<update id="archiveInspectionRobotByIds" parameterType="String">
|
<update id="archiveInspectionRobotByIds" parameterType="String">
|
||||||
update inspection_robot set archived_status = '1', connect_status = '0',
|
update inspection_robot set archived_status = '1', connect_status = '0',
|
||||||
update_time = sysdate(), update_by = #{updateBy}
|
update_time = sysdate(), update_by = #{updateBy}
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import java.util.Map;
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum ActionEnum {
|
public enum ActionEnum {
|
||||||
|
SPEAKER_PLAYAUDIO("EDGE", "SPEAKER_PLAYAUDIO", "Play audio"),
|
||||||
|
|
||||||
// 平台控制类
|
// 平台控制类
|
||||||
STOPPED("NONE", "STOP", "强制终止"),
|
STOPPED("NONE", "STOP", "强制终止"),
|
||||||
PAUSED("NONE", "PAUSED", "暂停"),
|
PAUSED("NONE", "PAUSED", "暂停"),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.cmvr.test.flow.runtime.operator.edge;
|
package com.cmvr.test.flow.runtime.operator.edge;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.cmvr.common.exception.GlobalException;
|
import com.cmvr.common.exception.GlobalException;
|
||||||
import com.cmvr.edge.client.service.EdgeSpeakerService;
|
import com.cmvr.edge.client.service.EdgeSpeakerService;
|
||||||
@ -24,6 +25,17 @@ public class EdgeSpeakerOperateService implements EdgeOperateService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) {
|
public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) {
|
||||||
|
JSONObject inputParams = message.getInputParams();
|
||||||
|
String deviceId = inputParams.getString("deviceId");
|
||||||
|
String audioPath = inputParams.getString("audioPath");
|
||||||
|
String robotId = message.getRobotId();
|
||||||
|
if (StrUtil.hasBlank(robotId, deviceId, audioPath)) {
|
||||||
|
throw new GlobalException("robotId, deviceId and audioPath are required");
|
||||||
|
}
|
||||||
|
String result = edgeSpeakerService.playAudio(robotId, deviceId, audioPath);
|
||||||
|
if (result != null) {
|
||||||
|
return TaskNodeExecuteResult.success(JSONObject.of("result", result));
|
||||||
|
}
|
||||||
// ActionEnum action = message.getAction();
|
// ActionEnum action = message.getAction();
|
||||||
// JSONObject inputParams = message.getInputParams();
|
// JSONObject inputParams = message.getInputParams();
|
||||||
// String deviceId = inputParams.getString("deviceId");
|
// String deviceId = inputParams.getString("deviceId");
|
||||||
@ -64,6 +76,6 @@ public class EdgeSpeakerOperateService implements EdgeOperateService {
|
|||||||
// throw new GlobalException("不支持的扬声器操作类型: " + action);
|
// throw new GlobalException("不支持的扬声器操作类型: " + action);
|
||||||
// }
|
// }
|
||||||
// return TaskNodeExecuteResult.success();
|
// return TaskNodeExecuteResult.success();
|
||||||
return null;
|
return TaskNodeExecuteResult.success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,16 +17,25 @@ import com.cmvr.edge.client.service.EdgeAgvService;
|
|||||||
import com.cmvr.device.service.InspectionAlertListenService;
|
import com.cmvr.device.service.InspectionAlertListenService;
|
||||||
import com.cmvr.llm.service.LLMAiAgentPlatformService;
|
import com.cmvr.llm.service.LLMAiAgentPlatformService;
|
||||||
import com.cmvr.test.enums.ActionEnum;
|
import com.cmvr.test.enums.ActionEnum;
|
||||||
|
import com.cmvr.test.flow.runtime.dispatcher.FlowCodeNodeHandler;
|
||||||
|
import com.cmvr.test.flow.runtime.dispatcher.FlowHttpNodeHandler;
|
||||||
|
import com.cmvr.test.flow.runtime.dispatcher.FlowSleepNodeHandler;
|
||||||
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
|
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
|
||||||
|
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
|
||||||
|
import com.cmvr.test.flow.runtime.operator.ae.AEOperateService;
|
||||||
import com.cmvr.test.flow.runtime.operator.edge.EdgeManualInspectionOperateService;
|
import com.cmvr.test.flow.runtime.operator.edge.EdgeManualInspectionOperateService;
|
||||||
import com.cmvr.test.flow.runtime.operator.edge.EdgeDeviceCommandOperateService;
|
import com.cmvr.test.flow.runtime.operator.edge.EdgeDeviceCommandOperateService;
|
||||||
import com.cmvr.test.flow.runtime.operator.edge.EdgeArmOperateService;
|
import com.cmvr.test.flow.runtime.operator.edge.EdgeArmOperateService;
|
||||||
|
import com.cmvr.test.flow.runtime.operator.edge.EdgeOperateService;
|
||||||
import com.cmvr.test.flow.runtime.operator.llm.InspectionMeterRecognizeOperateService;
|
import com.cmvr.test.flow.runtime.operator.llm.InspectionMeterRecognizeOperateService;
|
||||||
|
import com.cmvr.test.flow.runtime.operator.llm.LLMOperateService;
|
||||||
import com.cmvr.test.model.vo.FlowActionRequestVO;
|
import com.cmvr.test.model.vo.FlowActionRequestVO;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -43,6 +52,12 @@ public class FlowActionExecutorService {
|
|||||||
private final EdgeManualInspectionOperateService edgeManualInspectionOperateService;
|
private final EdgeManualInspectionOperateService edgeManualInspectionOperateService;
|
||||||
private final EdgeDeviceCommandOperateService edgeDeviceCommandOperateService;
|
private final EdgeDeviceCommandOperateService edgeDeviceCommandOperateService;
|
||||||
private final InspectionMeterRecognizeOperateService inspectionMeterRecognizeOperateService;
|
private final InspectionMeterRecognizeOperateService inspectionMeterRecognizeOperateService;
|
||||||
|
private final List<EdgeOperateService> edgeOperateServices;
|
||||||
|
private final List<LLMOperateService> llmOperateServices;
|
||||||
|
private final List<AEOperateService> aeOperateServices;
|
||||||
|
private final FlowSleepNodeHandler flowSleepNodeHandler;
|
||||||
|
private final FlowHttpNodeHandler flowHttpNodeHandler;
|
||||||
|
private final FlowCodeNodeHandler flowCodeNodeHandler;
|
||||||
|
|
||||||
public String actionExecute(FlowActionRequestVO req) {
|
public String actionExecute(FlowActionRequestVO req) {
|
||||||
|
|
||||||
@ -54,16 +69,77 @@ public class FlowActionExecutorService {
|
|||||||
|
|
||||||
switch (action.getOperate()) {
|
switch (action.getOperate()) {
|
||||||
case "EDGE":
|
case "EDGE":
|
||||||
return executeEdgeAction(action, req);
|
return executeEdgeOperateAction(action, req);
|
||||||
|
|
||||||
case "LLM":
|
case "LLM":
|
||||||
return executeLLMAction(action, req);
|
return executeLLMOperateAction(action, req);
|
||||||
|
|
||||||
|
case "AE":
|
||||||
|
return executeAEOperateAction(action, req);
|
||||||
|
|
||||||
|
case "NONE":
|
||||||
|
return executeStandaloneAction(action, req);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new GlobalException("仅设备和大模型 Action 可调用!");
|
throw new GlobalException("仅设备和大模型 Action 可调用!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String executeEdgeOperateAction(ActionEnum action, FlowActionRequestVO req) {
|
||||||
|
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
|
||||||
|
for (EdgeOperateService service : edgeOperateServices) {
|
||||||
|
if (service.supports(action)) {
|
||||||
|
return resultToString(service.execute(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new GlobalException("Unsupported EDGE action: " + action.getAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String executeLLMOperateAction(ActionEnum action, FlowActionRequestVO req) {
|
||||||
|
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
|
||||||
|
for (LLMOperateService service : llmOperateServices) {
|
||||||
|
if (service.supports(action)) {
|
||||||
|
return resultToString(service.execute(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new GlobalException("Unsupported LLM action: " + action.getAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String executeAEOperateAction(ActionEnum action, FlowActionRequestVO req) {
|
||||||
|
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
|
||||||
|
for (AEOperateService service : aeOperateServices) {
|
||||||
|
if (service.supports(action)) {
|
||||||
|
return resultToString(service.execute(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new GlobalException("Unsupported AE action: " + action.getAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String executeStandaloneAction(ActionEnum action, FlowActionRequestVO req) {
|
||||||
|
TaskNodeExecuteMessage message = buildSingleNodeMessage(action, req.getPayload());
|
||||||
|
switch (action) {
|
||||||
|
case SLEEP:
|
||||||
|
return resultToString(flowSleepNodeHandler.handle(message));
|
||||||
|
case HTTP:
|
||||||
|
return resultToString(flowHttpNodeHandler.handle(message));
|
||||||
|
case CODE:
|
||||||
|
return resultToString(flowCodeNodeHandler.handle(message));
|
||||||
|
default:
|
||||||
|
throw new GlobalException("This action requires flow context: " + action.getAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resultToString(TaskNodeExecuteResult result) {
|
||||||
|
if (result == null) {
|
||||||
|
throw new GlobalException("Single-node execution returned no result");
|
||||||
|
}
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
throw new GlobalException(StrUtil.isBlank(result.getErrorMsg())
|
||||||
|
? "Single-node execution failed" : result.getErrorMsg());
|
||||||
|
}
|
||||||
|
return result.getOutputParams() == null ? "{}" : result.getOutputParams().toJSONString();
|
||||||
|
}
|
||||||
|
|
||||||
private String executeEdgeAction(ActionEnum action, FlowActionRequestVO req) {
|
private String executeEdgeAction(ActionEnum action, FlowActionRequestVO req) {
|
||||||
JSONObject payload = req.getPayload() == null ? new JSONObject() : req.getPayload();
|
JSONObject payload = req.getPayload() == null ? new JSONObject() : req.getPayload();
|
||||||
String robotId = payload.getString("robotId");
|
String robotId = payload.getString("robotId");
|
||||||
@ -253,9 +329,15 @@ public class FlowActionExecutorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TaskNodeExecuteMessage buildSingleNodeMessage(ActionEnum action, JSONObject payload) {
|
private TaskNodeExecuteMessage buildSingleNodeMessage(ActionEnum action, JSONObject payload) {
|
||||||
|
JSONObject inputParams = payload == null ? new JSONObject() : payload;
|
||||||
TaskNodeExecuteMessage message = new TaskNodeExecuteMessage();
|
TaskNodeExecuteMessage message = new TaskNodeExecuteMessage();
|
||||||
message.setAction(action);
|
message.setAction(action);
|
||||||
message.setInputParams(payload == null ? new JSONObject() : payload);
|
message.setNodeId("single-node");
|
||||||
|
message.setRobotId(inputParams.getString("robotId"));
|
||||||
|
message.setIterations(java.util.Arrays.asList(1, 1));
|
||||||
|
message.setLoopNum(1);
|
||||||
|
message.setLoopArray(inputParams.get("array"));
|
||||||
|
message.setInputParams(inputParams);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ CREATE TABLE `inspection_robot` (
|
|||||||
`ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址',
|
`ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址',
|
||||||
`port` int(11) DEFAULT NULL COMMENT '端口号',
|
`port` int(11) DEFAULT NULL COMMENT '端口号',
|
||||||
`current_map_id` varchar(64) DEFAULT NULL COMMENT '当前地图ID',
|
`current_map_id` varchar(64) DEFAULT NULL COMMENT '当前地图ID',
|
||||||
`status` char(1) DEFAULT '0' COMMENT '状态(0在线 1离线 2充电中 3巡检中)',
|
`status` char(1) DEFAULT '0' COMMENT 'AGV运行模式(0未知 1断开 2空闲 3手动 4自动 5充电 6暂停 7停止 8故障 9急停)',
|
||||||
`battery_level` int(3) DEFAULT NULL COMMENT '电量百分比',
|
`battery_level` int(3) DEFAULT NULL COMMENT '电量百分比',
|
||||||
`current_position` varchar(200) DEFAULT NULL COMMENT '当前位置',
|
`current_position` varchar(200) DEFAULT NULL COMMENT '当前位置',
|
||||||
`connect_status` char(1) NOT NULL DEFAULT '0' COMMENT '连接状态(0离线 1在线)',
|
`connect_status` char(1) NOT NULL DEFAULT '0' COMMENT '连接状态(0离线 1在线)',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user