From 6aaed4daccbf9545e8da805d1a81c396cd62e978 Mon Sep 17 00:00:00 2001 From: lixiaolong <702156524@qq.com> Date: Fri, 17 Jul 2026 15:01:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(agv):=20=E9=87=8D=E6=9E=84=20AGV=20?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E6=8E=A5=E5=8F=A3=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E4=B8=8E=E6=95=B0=E6=8D=AE=E4=BC=A0=E8=BE=93=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一使用 VO 对象替代零散参数,提升接口一致性 - 新增多个 AGV 相关 VO 类,包括地图、导航、速度控制等场景 - 为所有 POST 接口添加 @RequestBody 注解明确请求体映射 - 优化 protobuf 对象到 Map 的转换逻辑,添加辅助方法 - 在所有查询接口中启用参数验证注解 @Validated - 为紧急停止和故障清除等操作改为 POST 方法增强安全性 - 更新 README.md 添加项目架构说明与开发约定 - 调整 ARM 控制器参数传递方式与验证机制 --- README.md | 249 ++++++++++++++ .../web/controller/api/EdgeAgvController.java | 308 +++++++++++------- .../web/controller/api/EdgeArmController.java | 13 +- .../client/model/agv/EdgeAgvMapNameVO.java | 22 ++ .../model/agv/EdgeAgvNavigateToPoseVO.java | 25 ++ .../model/agv/EdgeAgvNavigateToStationVO.java | 22 ++ .../model/agv/EdgeAgvStartMappingVO.java | 25 ++ .../client/model/agv/EdgeAgvUploadMapVO.java | 21 ++ .../client/model/agv/EdgeAgvVelocityVO.java | 25 ++ .../model/arm/EdgeArmCalibrateZeroQVO.java | 22 ++ .../client/model/arm/EdgeArmPoseQueryVO.java | 22 ++ 11 files changed, 625 insertions(+), 129 deletions(-) create mode 100644 README.md create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvMapNameVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToPoseVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToStationVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvStartMappingVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvUploadMapVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvVelocityVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmCalibrateZeroQVO.java create mode 100644 cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmPoseQueryVO.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..30fdc07 --- /dev/null +++ b/README.md @@ -0,0 +1,249 @@ +# cmvr-iot + +cmvr-iot 是一个基于 Java 8 和 Spring Boot 2.5 的多模块后端工程,提供设备管理、边缘机器人控制、测试编排、巡检、TTS、AIMA、VI/TI 等业务能力。工程入口在 `cmvr-iot-admin`,其余模块按业务和基础能力拆分。 + +## 技术栈 + +- Java 8 +- Spring Boot 2.5.15 +- Spring Security +- MyBatis Plus / MyBatis Plus Join +- MySQL / Redis +- Druid +- Quartz +- Knife4j / Swagger +- gRPC Java 1.58.0 +- Protobuf / protoc-maven-plugin +- MinIO + +## 模块说明 + +| 模块 | 说明 | +| --- | --- | +| `cmvr-iot-admin` | Web 启动模块,包含 REST Controller、启动类和运行配置 | +| `cmvr-iot-framework` | Web、安全、配置、通用框架能力 | +| `cmvr-iot-common` | 公共工具、通用响应、基础常量和通用能力 | +| `cmvr-iot-system` | 系统管理、用户、角色、菜单、字典等基础业务 | +| `cmvr-iot-quartz` | 定时任务模块 | +| `cmvr-iot-generator` | 代码生成模块 | +| `cmvr-iot-device` | 设备领域模型与设备相关能力 | +| `cmvr-iot-test` | 测试任务、编排、执行实例等业务 | +| `cmvr-iot-vi` | VI 相关业务 | +| `cmvr-iot-ti` | TI 相关业务 | +| `cmvr-iot-evaluation` | 评测相关业务 | +| `cmvr-iot-inspection` | 巡检机器人、地图、任务、告警等业务 | +| `cmvr-iot-aima` | AIMA 相关业务 | +| `cmvr-iot-tts` | TTS 语音合成相关业务 | +| `cmvr-iot-api` | API 聚合模块 | +| `cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-lib` | gRPC proto 与生成代码模块 | +| `cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client` | 边缘设备 gRPC 客户端、设备控制服务和请求 VO | + +## 目录结构 + +```text +cmvr-iot +├── cmvr-iot-admin # Spring Boot 启动模块 +├── cmvr-iot-common # 公共模块 +├── cmvr-iot-framework # 框架模块 +├── cmvr-iot-system # 系统管理模块 +├── cmvr-iot-device # 设备模块 +├── cmvr-iot-test # 测试业务模块 +├── cmvr-iot-api +│ └── cmvr-iot-edge +│ ├── cmvr-iot-grpc-lib # proto 与 gRPC 生成代码 +│ └── cmvr-iot-grpc-client # gRPC 客户端与边缘控制服务 +├── sql # 增量 SQL +├── Dockerfile +├── Jenkinsfile +└── pom.xml # Maven 父工程 +``` + +## 环境要求 + +- JDK 1.8 +- Maven 3.6+ +- MySQL 5.7+/8.x +- Redis +- 可访问的 gRPC 机器人/边缘终端服务 +- 可选:MinIO、外部大模型/TTS/评测服务 + +注意:`application-*.yml` 中包含环境相关地址、账号和密钥配置。实际部署或本地开发时,应使用本机配置、环境变量或配置中心覆盖,不要直接复用生产/测试环境敏感配置。 + +## 配置文件 + +主配置文件位于: + +```text +cmvr-iot-admin/src/main/resources/application.yml +``` + +环境配置文件位于: + +```text +cmvr-iot-admin/src/main/resources/application-dev.yml +cmvr-iot-admin/src/main/resources/application-test.yml +cmvr-iot-admin/src/main/resources/application-prod.yml +``` + +当前 `application.yml` 默认激活: + +```yaml +spring: + profiles: + active: test +``` + +如需切换环境,可以修改 `spring.profiles.active`,或启动时指定: + +```bash +java -jar cmvr-iot-admin-dev.jar --spring.profiles.active=dev +``` + +## 构建 + +在项目根目录执行: + +```bash +mvn clean package -DskipTests +``` + +只编译启动模块及其依赖: + +```bash +mvn -pl cmvr-iot-admin -am compile -DskipTests +``` + +只重新生成并编译 gRPC 代码: + +```bash +mvn -pl cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-lib -am compile -DskipTests +``` + +## 启动 + +启动类: + +```text +cmvr-iot-admin/src/main/java/com/cmvr/CmvrIotApplication.java +``` + +IDE 中直接运行 `CmvrIotApplication` 即可。 + +打包后运行: + +```bash +java -jar cmvr-iot-admin/target/cmvr-iot-admin-dev.jar --spring.profiles.active=dev +``` + +默认开发端口以当前 profile 配置为准,例如 `application-dev.yml` 中为: + +```yaml +server: + port: 13080 +``` + +## 接口文档 + +项目集成 Knife4j / Swagger。服务启动后可访问: + +```text +http://localhost:13080/doc.html +``` + +如果端口或上下文路径被 profile 覆盖,请以实际配置为准。 + +## 数据库脚本 + +增量脚本位于: + +```text +sql/ +``` + +当前包含: + +- `aima_module.sql` +- `inspection_module.sql` +- `tts_corpus_module.sql` + +初始化数据库时,需要结合目标环境的基础库结构和增量脚本执行。 + +## gRPC 与边缘设备 + +边缘设备相关代码集中在: + +```text +cmvr-iot-api/cmvr-iot-edge/ +``` + +proto 文件位于: + +```text +cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-lib/src/main/proto/ +``` + +gRPC 客户端服务位于: + +```text +cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/ +``` + +边缘控制器位于: + +```text +cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/ +``` + +新增或修改 proto 后,执行 Maven compile 会触发 `protoc-maven-plugin` 生成 Java 和 gRPC Stub 代码。业务代码应复用现有 gRPC 客户端管理工具获取 Stub,避免在业务层重复创建底层 `ManagedChannel`。 + +## 实时语音对讲 + +当前实时语音对讲链路为: + +```text +浏览器 WebSocket Binary PCM <-> Java 后端 <-> gRPC 双向流 <-> 机器人终端 +``` + +约定音频格式: + +- PCM_S16LE +- 48000 Hz +- 单声道 +- 16 bit +- 10 ms 一帧 +- 960 bytes/帧 + +相关文件: + +- WebSocket 控制器:`cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/WebRtcSignalingController.java` +- 业务会话管理:`cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/AudioService.java` +- gRPC 音频适配:`cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/adapter/RobotAudioGrpcAdapter.java` +- proto:`cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-lib/src/main/proto/cmvr/api/robot_audio.proto` + +说明:历史 WebRTC 相关类可能仍保留在源码中,但当前浏览器音频传输链路使用 WebSocket 二进制 PCM。 + +## 开发约定 + +- Controller 层只做参数接收、基础校验和响应封装。 +- POST 控制类接口优先使用单个 `@RequestBody VO` 承载完整请求参数。 +- GET 查询类接口可以使用 query 参数绑定 VO。 +- 不建议在同一个接口中同时声明通用 VO 和零散业务参数。 +- gRPC 调用统一通过现有客户端管理类获取 Stub。 +- proto 修改后必须重新编译并确认生成代码已更新。 +- 不要提交本地日志、崩溃 dump、临时音频文件和包含真实密钥的配置。 + +## 常用命令 + +```bash +# 编译全部模块 +mvn clean compile -DskipTests + +# 编译启动模块及依赖 +mvn -pl cmvr-iot-admin -am compile -DskipTests + +# 打包 +mvn clean package -DskipTests + +# 运行指定 profile +java -jar cmvr-iot-admin/target/cmvr-iot-admin-dev.jar --spring.profiles.active=dev +``` diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeAgvController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeAgvController.java index b006b69..53cc8fd 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeAgvController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeAgvController.java @@ -1,22 +1,36 @@ package com.cmvr.web.controller.api; -import cmvr.msgs.*; +import cmvr.msgs.Agv; import com.cmvr.common.core.domain.AjaxResult; import com.cmvr.edge.client.model.EdgeCommonVO; +import com.cmvr.edge.client.model.agv.EdgeAgvMapNameVO; +import com.cmvr.edge.client.model.agv.EdgeAgvNavigateToPoseVO; +import com.cmvr.edge.client.model.agv.EdgeAgvNavigateToStationVO; +import com.cmvr.edge.client.model.agv.EdgeAgvStartMappingVO; +import com.cmvr.edge.client.model.agv.EdgeAgvUploadMapVO; +import com.cmvr.edge.client.model.agv.EdgeAgvVelocityVO; import com.cmvr.edge.client.service.EdgeAgvService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** - * 边缘系统AGV控制器 + * 边缘系统 AGV 控制器。 + * + *

接口入参约定: + * GET 查询类接口使用 query 参数绑定 VO;POST 控制类接口只接收一个 JSON 请求体 VO, + * 避免一个接口同时出现通用 VO 和零散业务参数,保证前端调用、Swagger 文档和后端绑定规则一致。

* * @author cmvr-iot * @since 2026-07-02 @@ -29,62 +43,24 @@ public class EdgeAgvController { private final EdgeAgvService edgeAgvService; + /** + * 获取 AGV 运行时状态。 + */ @ApiOperation("获取AGV运行时状态") @GetMapping("/getRuntimeState") - public AjaxResult getRuntimeState(EdgeCommonVO vo) { + public AjaxResult getRuntimeState(@Validated EdgeCommonVO vo) { Agv.AgvRuntimeState state = edgeAgvService.getRuntimeState(vo); - // 转换为Map返回 - java.util.Map resultMap = new java.util.HashMap<>(); - resultMap.put("timestamp", state.getTimestamp()); - resultMap.put("mode", state.getMode()); - resultMap.put("connected", state.getConnected()); - resultMap.put("localized", state.getLocalized()); - resultMap.put("moving", state.getMoving()); - resultMap.put("fault", state.getFault()); - resultMap.put("emergencyStopped", state.getEmergencyStopped()); - - // 位置信息 - if (state.hasPose()) { - java.util.Map poseMap = new java.util.HashMap<>(); - poseMap.put("x", state.getPose().getX()); - poseMap.put("y", state.getPose().getY()); - poseMap.put("theta", state.getPose().getTheta()); - resultMap.put("pose", poseMap); - } - - // 速度信息 - if (state.hasVelocity()) { - java.util.Map velocityMap = new java.util.HashMap<>(); - velocityMap.put("vx", state.getVelocity().getVx()); - velocityMap.put("vy", state.getVelocity().getVy()); - velocityMap.put("wz", state.getVelocity().getWz()); - resultMap.put("velocity", velocityMap); - } - - // 电池信息 - if (state.hasBattery()) { - java.util.Map batteryMap = new java.util.HashMap<>(); - batteryMap.put("percentage", state.getBattery().getPercentage()); - batteryMap.put("voltage", state.getBattery().getVoltage()); - batteryMap.put("current", state.getBattery().getCurrent()); - batteryMap.put("temperature", state.getBattery().getTemperature()); - batteryMap.put("charging", state.getBattery().getCharging()); - resultMap.put("battery", batteryMap); - } - - resultMap.put("currentMap", state.getCurrentMap()); - resultMap.put("currentStation", state.getCurrentStation()); - resultMap.put("lastError", state.getLastError()); - - return AjaxResult.ok(resultMap); + return AjaxResult.ok(toRuntimeStateMap(state)); } + /** + * 获取 AGV 当前导航状态。 + */ @ApiOperation("获取导航状态") @GetMapping("/getNavigationStatus") - public AjaxResult getNavigationStatus(EdgeCommonVO vo) { + public AjaxResult getNavigationStatus(@Validated EdgeCommonVO vo) { Agv.AgvNavigationStatus status = edgeAgvService.getNavigationStatus(vo); - // 转换为Map返回 - java.util.Map resultMap = new java.util.HashMap<>(); + Map resultMap = new HashMap<>(); resultMap.put("state", status.getState()); resultMap.put("type", status.getType()); resultMap.put("progress", status.getProgress()); @@ -92,162 +68,248 @@ public class EdgeAgvController { return AjaxResult.ok(resultMap); } + /** + * AGV 紧急停止。 + */ @ApiOperation("紧急停止") - @GetMapping("/emergencyStop") - public AjaxResult emergencyStop(EdgeCommonVO vo) { + @PostMapping("/emergencyStop") + public AjaxResult emergencyStop(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.emergencyStop(vo); return AjaxResult.ok(); } + /** + * 清除 AGV 故障。 + */ @ApiOperation("清除故障") - @GetMapping("/clearFault") - public AjaxResult clearFault(EdgeCommonVO vo) { + @PostMapping("/clearFault") + public AjaxResult clearFault(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.clearFault(vo); return AjaxResult.ok(); } + /** + * 导航到指定坐标。 + */ @ApiOperation("导航到指定位置") @PostMapping("/navigateToPose") - public AjaxResult navigateToPose(EdgeCommonVO vo, - @ApiParam("X坐标") double x, - @ApiParam("Y坐标") double y, - @ApiParam("角度") double theta) { + public AjaxResult navigateToPose(@RequestBody @Validated EdgeAgvNavigateToPoseVO vo) { Agv.AgvPose2d pose = Agv.AgvPose2d.newBuilder() - .setX(x) - .setY(y) - .setTheta(theta) - .build(); + .setX(vo.getX()) + .setY(vo.getY()) + .setTheta(vo.getTheta()) + .build(); edgeAgvService.navigateToPose(vo, pose); return AjaxResult.ok(); } + /** + * 导航到指定站点。 + */ @ApiOperation("导航到站点") - @GetMapping("/navigateToStation") - public AjaxResult navigateToStation(EdgeCommonVO vo, - @ApiParam("站点ID") String stationId) { - edgeAgvService.navigateToStation(vo, stationId); + @PostMapping("/navigateToStation") + public AjaxResult navigateToStation(@RequestBody @Validated EdgeAgvNavigateToStationVO vo) { + edgeAgvService.navigateToStation(vo, vo.getStationId()); return AjaxResult.ok(); } + /** + * 暂停当前导航任务。 + */ @ApiOperation("暂停导航") - @GetMapping("/pauseNavigation") - public AjaxResult pauseNavigation(EdgeCommonVO vo) { + @PostMapping("/pauseNavigation") + public AjaxResult pauseNavigation(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.pauseNavigation(vo); return AjaxResult.ok(); } + /** + * 恢复当前导航任务。 + */ @ApiOperation("恢复导航") - @GetMapping("/resumeNavigation") - public AjaxResult resumeNavigation(EdgeCommonVO vo) { + @PostMapping("/resumeNavigation") + public AjaxResult resumeNavigation(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.resumeNavigation(vo); return AjaxResult.ok(); } + /** + * 取消当前导航任务。 + */ @ApiOperation("取消导航") - @GetMapping("/cancelNavigation") - public AjaxResult cancelNavigation(EdgeCommonVO vo) { + @PostMapping("/cancelNavigation") + public AjaxResult cancelNavigation(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.cancelNavigation(vo); return AjaxResult.ok(); } + /** + * 设置 AGV 速度。 + */ @ApiOperation("设置速度") @PostMapping("/setVelocity") - public AjaxResult setVelocity(EdgeCommonVO vo, - @ApiParam("线速度X") double vx, - @ApiParam("线速度Y") double vy, - @ApiParam("角速度") double wz) { + public AjaxResult setVelocity(@RequestBody @Validated EdgeAgvVelocityVO vo) { Agv.AgvVelocity velocity = Agv.AgvVelocity.newBuilder() - .setVx(vx) - .setVy(vy) - .setWz(wz) - .build(); + .setVx(vo.getVx()) + .setVy(vo.getVy()) + .setWz(vo.getWz()) + .build(); edgeAgvService.setVelocity(vo, velocity); return AjaxResult.ok(); } + /** + * 停止速度控制。 + */ @ApiOperation("停止速度控制") - @GetMapping("/stopVelocityControl") - public AjaxResult stopVelocityControl(EdgeCommonVO vo) { + @PostMapping("/stopVelocityControl") + public AjaxResult stopVelocityControl(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.stopVelocityControl(vo); return AjaxResult.ok(); } + /** + * 查询机器人本地所有地图。 + */ @ApiOperation("列出所有地图") @GetMapping("/listMaps") - public AjaxResult listMaps(EdgeCommonVO vo) { + public AjaxResult listMaps(@Validated EdgeCommonVO vo) { List maps = edgeAgvService.listMaps(vo); return AjaxResult.ok(maps); } - - + /** + * 查询机器人本地所有站点。 + */ @ApiOperation("列出所有站点") @GetMapping("/listStations") - public AjaxResult listStations(EdgeCommonVO vo) { + public AjaxResult listStations(@Validated EdgeCommonVO vo) { List stations = edgeAgvService.listStations(vo); - // 转换为List返回 - List> stationList = new java.util.ArrayList<>(); + List> stationList = new ArrayList<>(); for (Agv.AgvStation station : stations) { - java.util.Map stationMap = new java.util.HashMap<>(); - stationMap.put("id", station.getId()); - stationMap.put("type", station.getType()); - stationMap.put("description", station.getDescription()); - - if (station.hasPose()) { - java.util.Map poseMap = new java.util.HashMap<>(); - poseMap.put("x", station.getPose().getX()); - poseMap.put("y", station.getPose().getY()); - poseMap.put("theta", station.getPose().getTheta()); - stationMap.put("pose", poseMap); - } - - stationList.add(stationMap); + stationList.add(toStationMap(station)); } return AjaxResult.ok(stationList); } + /** + * 切换机器人当前地图。 + */ @ApiOperation("切换地图") - @GetMapping("/switchMap") - public AjaxResult switchMap(EdgeCommonVO vo, - @ApiParam("地图名称") String mapName) { - edgeAgvService.switchMap(vo, mapName); + @PostMapping("/switchMap") + public AjaxResult switchMap(@RequestBody @Validated EdgeAgvMapNameVO vo) { + edgeAgvService.switchMap(vo, vo.getMapName()); return AjaxResult.ok(); } + /** + * 上传地图内容到机器人。 + */ @ApiOperation("上传地图") @PostMapping("/uploadMap") - public AjaxResult uploadMap(EdgeCommonVO vo, - @ApiParam("地图名称") String mapName, - @ApiParam("地图内容") String content) { - edgeAgvService.uploadMap(vo, mapName, content); + public AjaxResult uploadMap(@RequestBody @Validated EdgeAgvUploadMapVO vo) { + edgeAgvService.uploadMap(vo, vo.getMapName(), vo.getContent()); return AjaxResult.ok(); } + /** + * 下载指定地图内容。 + */ @ApiOperation("下载地图") @GetMapping("/downloadMap") - public AjaxResult downloadMap(EdgeCommonVO vo, - @ApiParam("地图名称") String mapName) { - String content = edgeAgvService.downloadMap(vo, mapName); + public AjaxResult downloadMap(@Validated EdgeAgvMapNameVO vo) { + String content = edgeAgvService.downloadMap(vo, vo.getMapName()); return AjaxResult.ok(content); } + /** + * 开始建图。 + */ @ApiOperation("开始建图") @PostMapping("/startMapping") - public AjaxResult startMapping(EdgeCommonVO vo, - @ApiParam("建图维度(0:未指定 1:2D 2:3D 3:2D+3D)") int dimension, - @ApiParam("地图名称(可选)") String mapName, - @ApiParam("是否实时建图") boolean realTime) { - Agv.AgvMapDimension mapDimension = Agv.AgvMapDimension.forNumber(dimension); - String sessionId = edgeAgvService.startMapping(vo, mapDimension, mapName, realTime); - java.util.Map result = new java.util.HashMap<>(); + public AjaxResult startMapping(@RequestBody @Validated EdgeAgvStartMappingVO vo) { + Agv.AgvMapDimension mapDimension = Agv.AgvMapDimension.forNumber(vo.getDimension()); + if (mapDimension == null) { + return AjaxResult.error("建图维度不合法,允许值:0=未指定,1=2D,2=3D,3=2D+3D"); + } + + String sessionId = edgeAgvService.startMapping(vo, mapDimension, vo.getMapName(), vo.isRealTime()); + Map result = new HashMap<>(); result.put("sessionId", sessionId); return AjaxResult.ok(result); } + /** + * 停止建图。 + */ @ApiOperation("停止建图") - @GetMapping("/stopMapping") - public AjaxResult stopMapping(EdgeCommonVO vo) { + @PostMapping("/stopMapping") + public AjaxResult stopMapping(@RequestBody @Validated EdgeCommonVO vo) { edgeAgvService.stopMapping(vo); return AjaxResult.ok(); } + + /** + * 将 protobuf 运行时状态转换为前端友好的 Map,避免直接暴露 protobuf 对象结构。 + */ + private Map toRuntimeStateMap(Agv.AgvRuntimeState state) { + Map resultMap = new HashMap<>(); + resultMap.put("timestamp", state.getTimestamp()); + resultMap.put("mode", state.getMode()); + resultMap.put("connected", state.getConnected()); + resultMap.put("localized", state.getLocalized()); + resultMap.put("moving", state.getMoving()); + resultMap.put("fault", state.getFault()); + resultMap.put("emergencyStopped", state.getEmergencyStopped()); + + if (state.hasPose()) { + resultMap.put("pose", toPoseMap(state.getPose())); + } + if (state.hasVelocity()) { + Map velocityMap = new HashMap<>(); + velocityMap.put("vx", state.getVelocity().getVx()); + velocityMap.put("vy", state.getVelocity().getVy()); + velocityMap.put("wz", state.getVelocity().getWz()); + resultMap.put("velocity", velocityMap); + } + if (state.hasBattery()) { + Map batteryMap = new HashMap<>(); + batteryMap.put("percentage", state.getBattery().getPercentage()); + batteryMap.put("voltage", state.getBattery().getVoltage()); + batteryMap.put("current", state.getBattery().getCurrent()); + batteryMap.put("temperature", state.getBattery().getTemperature()); + batteryMap.put("charging", state.getBattery().getCharging()); + resultMap.put("battery", batteryMap); + } + + resultMap.put("currentMap", state.getCurrentMap()); + resultMap.put("currentStation", state.getCurrentStation()); + resultMap.put("lastError", state.getLastError()); + return resultMap; + } + + /** + * 将 protobuf 站点对象转换为普通 Map。 + */ + private Map toStationMap(Agv.AgvStation station) { + Map stationMap = new HashMap<>(); + stationMap.put("id", station.getId()); + stationMap.put("type", station.getType()); + stationMap.put("description", station.getDescription()); + if (station.hasPose()) { + stationMap.put("pose", toPoseMap(station.getPose())); + } + return stationMap; + } + + /** + * 将 protobuf 二维位姿转换为普通 Map。 + */ + private Map toPoseMap(Agv.AgvPose2d pose) { + Map poseMap = new HashMap<>(); + poseMap.put("x", pose.getX()); + poseMap.put("y", pose.getY()); + poseMap.put("theta", pose.getTheta()); + return poseMap; + } } diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeArmController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeArmController.java index 867d02c..3fb468e 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeArmController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/api/EdgeArmController.java @@ -8,6 +8,7 @@ import com.cmvr.edge.client.service.EdgeArmService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -139,8 +140,8 @@ public class EdgeArmController { @ApiOperation("获取末端位姿") @GetMapping("/getPose") - public AjaxResult getPose(EdgeCommonVO vo, String baseLink, String eeLink) { - ArmCommand.GetPose.Response result = edgeArmService.getPose(vo, baseLink, eeLink); + public AjaxResult getPose(@Validated EdgeArmPoseQueryVO vo) { + ArmCommand.GetPose.Response result = edgeArmService.getPose(vo, vo.getBaseLink(), vo.getEeLink()); ArmCommand.CartesianPose pose = result.getPose(); // 转换为VO @@ -157,15 +158,15 @@ public class EdgeArmController { @ApiOperation("标定零点") @GetMapping("/calibrateZeroQ") - public AjaxResult calibrateZeroQ(EdgeCommonVO vo, String jointName) { - edgeArmService.calibrateZeroQ(vo, jointName); + public AjaxResult calibrateZeroQ(@Validated EdgeArmCalibrateZeroQVO vo) { + edgeArmService.calibrateZeroQ(vo, vo.getJointName()); return AjaxResult.ok(); } @ApiOperation("获取位姿矩阵") @GetMapping("/getPoseMatrix") - public AjaxResult getPoseMatrix(EdgeCommonVO vo, String baseLink, String eeLink) { - ArmCommand.GetPoseMatrix.Response result = edgeArmService.getPoseMatrix(vo, baseLink, eeLink); + public AjaxResult getPoseMatrix(@Validated EdgeArmPoseQueryVO vo) { + ArmCommand.GetPoseMatrix.Response result = edgeArmService.getPoseMatrix(vo, vo.getBaseLink(), vo.getEeLink()); // 将TransformMatrix4x4转换为Map ArmCommand.TransformMatrix4x4 matrix = result.getMatrix(); java.util.Map matrixMap = new java.util.HashMap<>(); diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvMapNameVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvMapNameVO.java new file mode 100644 index 0000000..4099eae --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvMapNameVO.java @@ -0,0 +1,22 @@ +package com.cmvr.edge.client.model.agv; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +/** + * AGV 地图名称请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV地图名称参数") +public class EdgeAgvMapNameVO extends EdgeCommonVO { + + @NotEmpty(message = "地图名称不能为空") + @ApiModelProperty(value = "地图名称", required = true) + private String mapName; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToPoseVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToPoseVO.java new file mode 100644 index 0000000..6e1a116 --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToPoseVO.java @@ -0,0 +1,25 @@ +package com.cmvr.edge.client.model.agv; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AGV 导航到指定坐标的请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV导航到指定坐标参数") +public class EdgeAgvNavigateToPoseVO extends EdgeCommonVO { + + @ApiModelProperty(value = "目标 X 坐标,单位:米", required = true) + private double x; + + @ApiModelProperty(value = "目标 Y 坐标,单位:米", required = true) + private double y; + + @ApiModelProperty(value = "目标朝向角,单位:弧度", required = true) + private double theta; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToStationVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToStationVO.java new file mode 100644 index 0000000..1ab2c78 --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvNavigateToStationVO.java @@ -0,0 +1,22 @@ +package com.cmvr.edge.client.model.agv; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +/** + * AGV 导航到站点的请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV导航到站点参数") +public class EdgeAgvNavigateToStationVO extends EdgeCommonVO { + + @NotEmpty(message = "站点ID不能为空") + @ApiModelProperty(value = "站点ID", required = true) + private String stationId; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvStartMappingVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvStartMappingVO.java new file mode 100644 index 0000000..428e3ad --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvStartMappingVO.java @@ -0,0 +1,25 @@ +package com.cmvr.edge.client.model.agv; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AGV 开始建图请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV开始建图参数") +public class EdgeAgvStartMappingVO extends EdgeCommonVO { + + @ApiModelProperty(value = "建图维度:0=未指定,1=2D,2=3D,3=2D+3D", required = true) + private int dimension; + + @ApiModelProperty("地图名称,可为空") + private String mapName; + + @ApiModelProperty(value = "是否实时建图", required = true) + private boolean realTime; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvUploadMapVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvUploadMapVO.java new file mode 100644 index 0000000..38a079b --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvUploadMapVO.java @@ -0,0 +1,21 @@ +package com.cmvr.edge.client.model.agv; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +/** + * AGV 上传地图请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV上传地图参数") +public class EdgeAgvUploadMapVO extends EdgeAgvMapNameVO { + + @NotEmpty(message = "地图内容不能为空") + @ApiModelProperty(value = "地图内容", required = true) + private String content; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvVelocityVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvVelocityVO.java new file mode 100644 index 0000000..83e24a4 --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/agv/EdgeAgvVelocityVO.java @@ -0,0 +1,25 @@ +package com.cmvr.edge.client.model.agv; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AGV 速度控制请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("AGV速度控制参数") +public class EdgeAgvVelocityVO extends EdgeCommonVO { + + @ApiModelProperty(value = "X 方向线速度,单位:m/s", required = true) + private double vx; + + @ApiModelProperty(value = "Y 方向线速度,单位:m/s", required = true) + private double vy; + + @ApiModelProperty(value = "角速度,单位:rad/s", required = true) + private double wz; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmCalibrateZeroQVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmCalibrateZeroQVO.java new file mode 100644 index 0000000..a023803 --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmCalibrateZeroQVO.java @@ -0,0 +1,22 @@ +package com.cmvr.edge.client.model.arm; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +/** + * 机械臂标定零点参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("机械臂标定零点参数") +public class EdgeArmCalibrateZeroQVO extends EdgeCommonVO { + + @NotEmpty(message = "关节名称不能为空") + @ApiModelProperty(value = "关节名称", required = true) + private String jointName; +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmPoseQueryVO.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmPoseQueryVO.java new file mode 100644 index 0000000..202056b --- /dev/null +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/model/arm/EdgeArmPoseQueryVO.java @@ -0,0 +1,22 @@ +package com.cmvr.edge.client.model.arm; + +import com.cmvr.edge.client.model.EdgeCommonVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 机械臂位姿查询参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("机械臂位姿查询参数") +public class EdgeArmPoseQueryVO extends EdgeCommonVO { + + @ApiModelProperty("基座连杆名称,为空时由机器人端使用默认值") + private String baseLink; + + @ApiModelProperty("末端执行器连杆名称,为空时由机器人端使用默认值") + private String eeLink; +}