Compare commits

...

4 Commits

Author SHA1 Message Date
707baaefb0 refactor(service): 移除未使用的LLMAiTtsService依赖
- 从FlowActionExecutorService中移除llmAiTtsService字段
- 减少不必要的服务依赖注入
- 优化代码结构和内存使用
2026-06-30 15:00:58 +08:00
0f36d8b4d1 feat(agv): 添加AGV移动到指定点功能
- 在ActionEnum中新增AGV_MOVE_TO_POINT枚举值
- 在FlowActionExecutorService中添加AGV移动逻辑处理
- 新增EdgeAgvOperateService实现AGV边缘操作服务
- 集成EdgeAgvService进行机器人导航控制
- 实现坐标参数解析和验证功能
- 添加AGV移动任务下发和结果返回机制
2026-06-30 15:00:25 +08:00
db17d50ef6 feat(agv): 添加自由导航功能支持
- 新增 robotGoTargetWithFreeGo 方法支持坐标导航
- 实现自由导航到指定坐标位置的功能
- 添加机器人实时位置查询接口
- 提供基于坐标的导航控制能力
- 支持双轮差速底盘的自由导航操作
2026-06-29 15:50:32 +08:00
6d39c4296e refactor(robot): 重构机器人地图上传功能
- 移除原有的 uploadMapToRobot 方法,简化服务接口
- 添加新的 uploadMapFromSource 方法,支持从源机器人下载并上传到目标机器人
- 更新控制器中的路径变量注解,移除冗余参数定义
- 实现跨机器人地图传输功能,包括从源机器人下载和向目标机器人上传
- 添加完整的异常处理和验证逻辑,确保机器人和地图存在性检查
- 统一错误消息处理,提高代码可维护性
2026-06-29 09:05:18 +08:00
8 changed files with 389 additions and 78 deletions

View File

@ -112,7 +112,7 @@ public class InspectionRobotController extends BaseController
@ApiOperation("获取机器人地图列表")
@PreAuthorize("@ss.hasPermi('inspection:robot:query')")
@GetMapping("/{robotId}/maps")
public AjaxResult getRobotMapList(@PathVariable("robotId") String robotId)
public AjaxResult getRobotMapList(@PathVariable String robotId)
{
AgvCommand.AgvMapStatus robotMapList = inspectionRobotService.getRobotMapList(robotId);
return success(robotMapList.getMapsList());
@ -125,26 +125,12 @@ public class InspectionRobotController extends BaseController
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "绑定机器人地图", businessType = BusinessType.UPDATE)
@PostMapping("/{robotId}/bind-map/{mapId}")
public AjaxResult bindRobotMap(@PathVariable("robotId") String robotId,
@PathVariable("mapId") String mapId)
public AjaxResult bindRobotMap(@PathVariable String robotId,
@PathVariable String mapId)
{
return toAjax(inspectionRobotService.bindRobotMap(robotId, mapId));
}
/**
* 上传地图到机器人
*/
@ApiOperation("上传地图到机器人")
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "上传地图到机器人", businessType = BusinessType.OTHER)
@PostMapping("/{robotId}/upload-map/{mapId}")
public AjaxResult uploadMapToRobot(@PathVariable("robotId") String robotId,
@PathVariable("mapId") String mapId)
{
String result = inspectionRobotService.uploadMapToRobot(robotId, mapId);
return success(result);
}
/**
* 从机器人下载地图
*/
@ -153,7 +139,7 @@ public class InspectionRobotController extends BaseController
@Log(title = "从机器人下载地图", businessType = BusinessType.INSERT)
@GetMapping("/{robotId}/download-map")
public AjaxResult downloadMapFromRobot(
@PathVariable(value = "robotId") @ApiParam("机器人ID") String robotId,
@PathVariable @ApiParam("机器人ID") String robotId,
@RequestParam(value = "mapName") @ApiParam("地图名称") String mapName
)
{
@ -161,6 +147,19 @@ public class InspectionRobotController extends BaseController
return success(mapId);
}
/**
* 上传地图从源机器人下载到目标机器人
*/
@ApiOperation("上传地图到机器人")
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "上传地图到机器人", businessType = BusinessType.OTHER)
@PostMapping("/{robotId}/upload-map-from-source")
public AjaxResult uploadMapFromSource(@PathVariable String robotId)
{
String result = inspectionRobotService.uploadMapFromSource(robotId);
return success(result);
}
/**
* 同步机器人状态
*/
@ -168,10 +167,43 @@ public class InspectionRobotController extends BaseController
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "同步机器人状态", businessType = BusinessType.UPDATE)
@PostMapping("/{robotId}/sync-status")
public AjaxResult syncRobotStatus(@PathVariable("robotId") String robotId)
public AjaxResult syncRobotStatus(@PathVariable String robotId)
{
return success(inspectionRobotService.syncRobotStatus(robotId));
}
/**
* 获取机器人实时位置
*/
@ApiOperation("获取机器人实时位置")
@PreAuthorize("@ss.hasPermi('inspection:robot:query')")
@GetMapping("/{robotId}/location")
public AjaxResult getRobotRealtimeLocation(@PathVariable String robotId)
{
AgvCommand.AgvRobotLocation location = inspectionRobotService.getRobotRealtimeLocation(robotId);
return success(location);
}
/**
* 导航到指定位置坐标
*/
@ApiOperation("导航到指定位置")
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "导航到指定位置", businessType = BusinessType.OTHER)
@PostMapping("/{robotId}/navigate-to-position")
public AjaxResult navigateToPosition(
@PathVariable @ApiParam("机器人ID") String robotId,
@RequestParam @ApiParam("X坐标") Double x,
@RequestParam @ApiParam("Y坐标") Double y,
@RequestParam(required = false) @ApiParam("朝向角(弧度)") Double theta,
@RequestParam(required = false) @ApiParam("任务ID") String taskId
)
{
AgvCommand.RobotGoTargetResData result = inspectionRobotService.navigateToPosition(
robotId, x, y, theta, taskId
);
return success(result);
}
}

View File

@ -79,6 +79,20 @@ public interface EdgeAgvService {
AgvCommand.RobotGoTargetResData robotGoTarget(EdgeCommonVO edgeCommonVO, String sourceId, String targetId,
String taskId, String operation, Double jackHeight);
/**
* 自由导航到指定坐标位置命令码3051使用freego参数
* 直接导航到指定的XY坐标和角度不依赖站点
* 注意此命令会取消当前正在执行的任务仅支持双轮差速底盘
*
* @param edgeCommonVO 边缘通用参数
* @param freeGoPoint 自由导航目标点包含xytheta
* @param taskId 任务ID可选建议提供
* @return 导航任务下发结果
*/
AgvCommand.RobotGoTargetResData robotGoTargetWithFreeGo(EdgeCommonVO edgeCommonVO,
AgvCommand.FreeGoPoint freeGoPoint,
String taskId);
/**
* 暂停当前导航任务命令码3001
* 暂停AGV当前正在执行的导航任务AGV将减速停止

View File

@ -156,6 +156,46 @@ public class EdgeAgvServiceImpl implements EdgeAgvService {
return result;
}
@Override
public AgvCommand.RobotGoTargetResData robotGoTargetWithFreeGo(EdgeCommonVO edgeCommonVO,
AgvCommand.FreeGoPoint freeGoPoint,
String taskId) {
if (freeGoPoint == null) {
throw new GlobalException("自由导航目标点不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
// 构建请求数据使用freego参数
AgvCommand.RobotGoTargetReqData.Builder dataBuilder = AgvCommand.RobotGoTargetReqData.newBuilder()
.setSourceId("SELF_POSITION") // 从当前位置开始
.setId("SELF_POSITION") // 不使用站点ID
.setFreego(freeGoPoint); // 设置自由导航点
// 设置可选参数
if (StrUtil.isNotBlank(taskId)) {
dataBuilder.setTaskId(taskId);
}
AgvCommand.RobotGoTargetCommand.Request request = AgvCommand.RobotGoTargetCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(dataBuilder.build())
.build();
AgvCommand.RobotGoTargetResData result = executeGrpcCall(() -> stub.robotGoTarget(request)).getData();
if (result.getRetCode() != 0) {
throw new GlobalException("自由导航任务下发失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotTaskPauseCommand.Feedback.Result robotTaskPause(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(

View File

@ -73,15 +73,6 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
*/
int bindRobotMap(String robotId, String mapId);
/**
* 上传地图到机器人
*
* @param robotId 机器人ID
* @param mapId 地图ID
* @return 结果
*/
String uploadMapToRobot(String robotId, String mapId);
/**
* 从机器人下载地图
*
@ -91,6 +82,14 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
*/
String downloadMapFromRobot(String robotId, String mapName);
/**
* 上传地图从源机器人下载到目标机器人
*
* @param robotId 目标机器人ID
* @return 结果
*/
String uploadMapFromSource(String robotId);
/**
* 同步机器人状态电量位置等
*
@ -99,4 +98,25 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
*/
InspectionRobot syncRobotStatus(String robotId);
/**
* 获取机器人实时位置
*
* @param robotId 机器人ID
* @return 机器人位置信息包含XY坐标角度当前站点等
*/
AgvCommand.AgvRobotLocation getRobotRealtimeLocation(String robotId);
/**
* 导航到指定位置坐标
*
* @param robotId 机器人ID
* @param x X坐标
* @param y Y坐标
* @param theta 朝向角弧度
* @param taskId 任务ID可选
* @return 导航任务下发结果
*/
AgvCommand.RobotGoTargetResData navigateToPosition(String robotId, Double x, Double y,
Double theta, String taskId);
}

View File

@ -1,6 +1,7 @@
package com.cmvr.inspection.service.impl;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@ -180,49 +181,6 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
return inspectionRobotMapper.updateInspectionRobot(robot);
}
/**
* 上传地图到机器人
*
* @param robotId 机器人ID
* @param mapId 地图ID
* @return 结果
*/
@Override
public String uploadMapToRobot(String robotId, String mapId)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
throw new GlobalException("机器人不存在");
}
InspectionMap map = inspectionMapMapper.selectInspectionMapById(mapId);
if (map == null) {
throw new GlobalException("地图不存在");
}
if (StrUtil.isBlank(map.getMapFilePath())) {
throw new GlobalException("地图文件路径不存在");
}
// 从文件路径读取地图内容
String mapContent = readMapFile(map.getMapFilePath());
if (StrUtil.isBlank(mapContent)) {
throw new GlobalException("地图文件内容为空");
}
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
AgvCommand.AgvUploadMapResult result = edgeAgvService.robotConfigUploadMap(edgeCommonVO, mapContent);
if (result.getRetCode() != 0) {
throw new GlobalException("上传地图失败: " + result.getErrMsg());
}
return "上传成功";
}
/**
* 从机器人下载地图
*
@ -264,6 +222,75 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
return result.getMapContent();
}
/**
* 上传地图从源机器人下载到目标机器人
*
* @param robotId 目标机器人ID
* @return 结果
*/
@Override
public String uploadMapFromSource(String robotId)
{
// 1. 获取目标机器人信息
InspectionRobot targetRobot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (targetRobot == null) {
throw new GlobalException("目标机器人不存在");
}
// 2. 获取地图信息
InspectionMap map = inspectionMapMapper.selectInspectionMapById(targetRobot.getCurrentMapId());
if (map == null) {
throw new GlobalException("地图不存在");
}
// 3. 检查地图是否有来源机器人ID和地图名称
if (StrUtil.isBlank(map.getMapSourceRobotId())) {
throw new GlobalException("地图未配置来源机器人ID");
}
if (StrUtil.isBlank(map.getMapSourceName())) {
throw new GlobalException("地图未配置来源地图名称");
}
// 4. 获取来源机器人信息
InspectionRobot sourceRobot = inspectionRobotMapper.selectInspectionRobotById(map.getMapSourceRobotId());
if (sourceRobot == null) {
throw new GlobalException("来源机器人不存在");
}
// 5. 从来源机器人下载地图JSON
EdgeCommonVO sourceEdgeVO = new EdgeCommonVO();
sourceEdgeVO.setTerminalId(sourceRobot.getTerminalId());
sourceEdgeVO.setDeviceId(sourceRobot.getRobotCode());
AgvCommand.AgvDownloadMapResult downloadResult = edgeAgvService.robotConfigDownloadMap(
sourceEdgeVO, map.getMapSourceName()
);
if (downloadResult.getRetCode() != 0) {
throw new GlobalException("从来源机器人下载地图失败: " + downloadResult.getErrMsg());
}
String mapContent = downloadResult.getMapContent();
if (StrUtil.isBlank(mapContent)) {
throw new GlobalException("下载的地图内容为空");
}
// 6. 上传地图到目标机器人
EdgeCommonVO targetEdgeVO = new EdgeCommonVO();
targetEdgeVO.setTerminalId(targetRobot.getTerminalId());
targetEdgeVO.setDeviceId(targetRobot.getRobotCode());
AgvCommand.AgvUploadMapResult uploadResult = edgeAgvService.robotConfigUploadMap(
targetEdgeVO, mapContent
);
if (uploadResult.getRetCode() != 0) {
throw new GlobalException("上传地图到目标机器人失败: " + uploadResult.getErrMsg());
}
return "上传成功";
}
/**
* 同步机器人状态电量位置等
*
@ -306,15 +333,62 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
}
/**
* 读取地图文件内容
* 获取机器人实时位置
*
* @param filePath 文件路径
* @return 文件内容
* @param robotId 机器人ID
* @return 机器人位置信息包含XY坐标角度当前站点等
*/
private String readMapFile(String filePath)
@Override
public AgvCommand.AgvRobotLocation getRobotRealtimeLocation(String robotId)
{
// TODO: 实现文件读取逻辑
return "";
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
throw new GlobalException("机器人不存在");
}
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
return edgeAgvService.getRobotLocation(edgeCommonVO);
}
/**
* 导航到指定位置坐标
*
* @param robotId 机器人ID
* @param x X坐标
* @param y Y坐标
* @param theta 朝向角弧度
* @param taskId 任务ID可选
* @return 导航任务下发结果
*/
@Override
public AgvCommand.RobotGoTargetResData navigateToPosition(String robotId, Double x, Double y,
Double theta, String taskId)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
throw new GlobalException("机器人不存在");
}
if (x == null || y == null) {
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
AgvCommand.FreeGoPoint freeGoPoint = AgvCommand.FreeGoPoint.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
.build();
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
// 调用边缘端接口使用freego参数进行自由导航
return edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
}
}

View File

@ -63,6 +63,9 @@ public enum ActionEnum {
BIO_HEAD_SPEAK_STOP("EDGE", "BIO_HEAD_SPEAK_STOP", "停止说话"),
BIO_HEAD_SPECIAL_EXPRESSION("EDGE", "BIO_HEAD_SPECIAL_EXPRESSION", "表情1:高兴,2:惊讶,3:疲惫"),
// --------------- agv ---------------
AGV_MOVE_TO_POINT("EDGE", "AGV_MOVE_TO_POINT", "移动到指定点"),
// 大模型行为
// ---------------获取触控坐标---------------
TOUCH_COORDINATES("LLM", "TOUCH_COORDINATES", "获取触控坐标"),

View File

@ -0,0 +1,89 @@
package com.cmvr.test.flow.runtime.operator.edge;
import cmvr.api.AgvCommand;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.edge.client.model.EdgeCommonVO;
import com.cmvr.edge.client.service.EdgeAgvService;
import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* AGV边缘操作服务
* 处理AGV相关的设备行为如移动到指定位置
*
* @author cmvr-iot
* @since 2026-06-29
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class EdgeAgvOperateService implements EdgeOperateService {
private final EdgeAgvService edgeAgvService;
@Override
public boolean supports(ActionEnum action) {
return action.name().startsWith("AGV_");
}
@Override
public TaskNodeExecuteResult execute(TaskNodeExecuteMessage message) {
ActionEnum action = message.getAction();
JSONObject inputParams = message.getInputParams();
String terminalId = message.getTerminalId();
// 从输入参数中获取机器人ID
String deviceId = inputParams.getString("deviceId");
if (deviceId == null || deviceId.isEmpty()) {
throw new GlobalException("机器人ID(deviceId)不能为空");
}
switch (action) {
case AGV_MOVE_TO_POINT: {
log.info("执行AGV移动到指定位置操作机器人ID: {}", deviceId);
// 从输入参数中获取坐标信息
Double x = inputParams.getDouble("x");
Double y = inputParams.getDouble("y");
Double theta = inputParams.getDouble("theta");
String taskId = inputParams.getString("taskId");
// 参数验证
if (x == null || y == null) {
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
AgvCommand.FreeGoPoint freeGoPoint = AgvCommand.FreeGoPoint.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
.build();
// 构建边缘端通用参数
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(terminalId);
edgeCommonVO.setDeviceId(deviceId);
// 直接调用边缘端AGV服务执行导航
AgvCommand.RobotGoTargetResData result =
edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
log.info("AGV移动任务下发成功返回码: {}", result.getRetCode());
break;
}
default:
throw new GlobalException("不支持的AGV操作类型: " + action);
}
return TaskNodeExecuteResult.success();
}
}

View File

@ -12,6 +12,7 @@ import com.cmvr.edge.client.service.EdgeCameraService;
import com.cmvr.edge.client.service.EdgeHlcService;
import com.cmvr.edge.client.service.EdgeMicrophoneService;
import com.cmvr.edge.client.service.EdgeSpeakerService;
import com.cmvr.edge.client.service.EdgeAgvService;
import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.llm.service.LLMAiTtsService;
import com.cmvr.test.enums.ActionEnum;
@ -29,8 +30,8 @@ public class FlowActionExecutorService {
private final EdgeMicrophoneService edgeMicrophoneService;
private final EdgeSpeakerService edgeSpeakerService;
private final EdgeHlcService edgeHlcService;
private final LLMAiTtsService llmAiTtsService;
private final LLMAiAgentPlatformService llmAiAgentPlatformService;
private final EdgeAgvService edgeAgvService;
public String actionExecute(FlowActionRequestVO req) {
@ -104,6 +105,44 @@ public class FlowActionExecutorService {
edgeFacialExpressionVO.setDeviceId(deviceId);
return edgeBioHeadService.setExpression(edgeFacialExpressionVO);
// ==== AGV ====
case AGV_MOVE_TO_POINT: {
log.info("执行AGV移动到指定位置操作");
// 从payload中获取机器人ID和坐标信息
String deviceId1 = payload.getString("deviceId");
if (StrUtil.isEmpty(deviceId1)) {
throw new GlobalException("机器人ID(deviceId)不能为空");
}
Double x = payload.getDouble("x");
Double y = payload.getDouble("y");
Double theta = payload.getDouble("theta");
String taskId = payload.getString("taskId");
// 参数验证
if (x == null || y == null) {
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
cmvr.api.AgvCommand.FreeGoPoint freeGoPoint = cmvr.api.AgvCommand.FreeGoPoint.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
.build();
// 直接调用边缘端AGV服务
cmvr.api.AgvCommand.RobotGoTargetResData result =
edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
log.info("AGV移动任务下发成功返回码: {}", result.getRetCode());
// 返回结果字符串
return String.format("AGV移动任务下发成功, retCode=%d, errMsg=%s",
result.getRetCode(), result.getErrMsg());
}
default:
throw new UnsupportedOperationException("未实现的 EDGE Action: " + action.name());
}