diff --git a/pom.xml b/pom.xml index 3ecfed8..b202374 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,18 @@ spring-boot-starter-websocket + + org.springframework.boot + spring-boot-starter-aop + + + + + com.google.protobuf + protobuf-java + 3.21.12 + + com.baomidou mybatis-plus-boot-starter @@ -116,6 +128,13 @@ + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + org.apache.maven.plugins @@ -144,6 +163,30 @@ + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + ${project.basedir}/src/main/proto + + ${project.build.directory}/generated-sources/protobuf/java + + com.google.protobuf:protoc:3.21.12:exe:windows-x86_64 + + true + + + + + compile + test-compile + + + + diff --git a/src/main/java/org/nl/apt15e/apt/map/dao/MapInfo.java b/src/main/java/org/nl/apt15e/apt/map/dao/MapInfo.java new file mode 100644 index 0000000..1624a7a --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/dao/MapInfo.java @@ -0,0 +1,55 @@ +package org.nl.apt15e.apt.map.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +@TableName("map_info") +public class MapInfo { + + /** + * 地图编号 + */ + @TableId + private String mapCode; + + /** + * 地图名称 + */ + private String mapName; + + /** + * 图片像素宽 + */ + private Double width; + + /** + * 图片像素高 + */ + private Double height; + + /** + * 点云图像素比例 如:一个像素0.05 就是5cm + */ + private Double resolution; + + /** + * 左下角x坐标 + */ + private Double x; + + /** + * 左下角y坐标 + */ + private Double y; + + /** + * 左下角 角度 + */ + private Double angle; +} diff --git a/src/main/java/org/nl/apt15e/apt/map/dto/ProcessMapYamlDto.java b/src/main/java/org/nl/apt15e/apt/map/dto/ProcessMapYamlDto.java new file mode 100644 index 0000000..77e46f6 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/dto/ProcessMapYamlDto.java @@ -0,0 +1,31 @@ +package org.nl.apt15e.apt.map.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +public class ProcessMapYamlDto { + + /** + * 点云图名称 + */ + private String image; + + /** + * 点云图像素比例 如:一个像素0.05 就是5cm + */ + private Double resolution; + + /** + * 地图左下角像素的坐标 [x,y,angle] + */ + private Double[] origin; + + private Double negate; + private Double occupied_thresh; + private Double free_thresh; + private Double main_normal; +} diff --git a/src/main/java/org/nl/apt15e/apt/map/rest/MapInfoController.java b/src/main/java/org/nl/apt15e/apt/map/rest/MapInfoController.java new file mode 100644 index 0000000..992d43d --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/rest/MapInfoController.java @@ -0,0 +1,28 @@ +package org.nl.apt15e.apt.map.rest; + +import org.nl.apt15e.apt.map.dao.MapInfo; +import org.nl.apt15e.apt.map.service.MapInfoService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/17 + */ +@RestController +@RequestMapping("/mapInfo") +public class MapInfoController { + + @Resource + private MapInfoService mapInfoService; + + @RequestMapping("/getMapInfoByCode") + public ResponseEntity getMapInfoByCode(@RequestParam("mapCode") String mapCode) { + return new ResponseEntity<>(mapInfoService.getMapInfoByCode(mapCode), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/apt15e/apt/map/service/MapInfoService.java b/src/main/java/org/nl/apt15e/apt/map/service/MapInfoService.java new file mode 100644 index 0000000..bcb0fc1 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/service/MapInfoService.java @@ -0,0 +1,15 @@ +package org.nl.apt15e.apt.map.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.apt15e.apt.map.dao.MapInfo; + +/** + * @author dsh + * 2025/7/17 + */ +public interface MapInfoService extends IService { + + JSONObject getMapInfoByCode(String mapCode); + +} diff --git a/src/main/java/org/nl/apt15e/apt/map/service/impl/MapInfoServiceImpl.java b/src/main/java/org/nl/apt15e/apt/map/service/impl/MapInfoServiceImpl.java new file mode 100644 index 0000000..213fd23 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/service/impl/MapInfoServiceImpl.java @@ -0,0 +1,34 @@ +package org.nl.apt15e.apt.map.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.nl.apt15e.apt.map.dao.MapInfo; +import org.nl.apt15e.apt.map.service.MapInfoService; +import org.nl.apt15e.apt.map.service.mapper.MapInfoMapper; +import org.nl.apt15e.common.BadRequestException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +/** + * @author dsh + * 2025/7/17 + */ +@Slf4j +@Service +public class MapInfoServiceImpl extends ServiceImpl implements MapInfoService { + + @Override + public JSONObject getMapInfoByCode(String mapCode) { + if (StrUtil.isEmpty(mapCode)) { + throw new BadRequestException("mapCode is null"); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", HttpStatus.OK.value()); + jsonObject.put("data", this.getOne(new LambdaQueryWrapper<>(MapInfo.class).eq(MapInfo::getMapCode,mapCode))); + return jsonObject; + } +} diff --git a/src/main/java/org/nl/apt15e/apt/map/service/mapper/MapInfoMapper.java b/src/main/java/org/nl/apt15e/apt/map/service/mapper/MapInfoMapper.java new file mode 100644 index 0000000..6a95f1c --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/map/service/mapper/MapInfoMapper.java @@ -0,0 +1,13 @@ +package org.nl.apt15e.apt.map.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.apt15e.apt.map.dao.MapInfo; + +/** + * @author dsh + * 2025/7/17 + */ +@Mapper +public interface MapInfoMapper extends BaseMapper { +} diff --git a/src/main/java/org/nl/apt15e/apt/route/dao/RouteInfo.java b/src/main/java/org/nl/apt15e/apt/route/dao/RouteInfo.java new file mode 100644 index 0000000..2d8fa3a --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/route/dao/RouteInfo.java @@ -0,0 +1,61 @@ +package org.nl.apt15e.apt.route.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +@TableName("route_info") +public class RouteInfo { + + /** + * 路径标识 + */ + @TableId + private Integer route_id; + + /** + * 起点ID + */ + private Integer start_id; + + /** + * 终点ID + */ + private Integer end_id; + + /** + * 起点x坐标 + */ + private Double start_x; + + /** + * 起点y坐标 + */ + private Double start_y; + + /** + * 终点x坐标 + */ + private Double end_x; + + /** + * 终点y坐标 + */ + private Double end_y; + + /** + * 导航模式 前进模式, 后退模式 ,无头模式 + */ + private Integer navigation_mode; + + /** + * 路线类型 直行,转弯 + */ + private Integer route_type; + +} diff --git a/src/main/java/org/nl/apt15e/apt/route/service/RouteInfoService.java b/src/main/java/org/nl/apt15e/apt/route/service/RouteInfoService.java new file mode 100644 index 0000000..a914167 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/route/service/RouteInfoService.java @@ -0,0 +1,14 @@ +package org.nl.apt15e.apt.route.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.apt15e.apt.route.dao.RouteInfo; + +/** + * @author dsh + * 2025/7/18 + */ +public interface RouteInfoService extends IService { + + JSONObject getAllRouteInfo(); +} diff --git a/src/main/java/org/nl/apt15e/apt/route/service/impl/RouteInfoServiceImpl.java b/src/main/java/org/nl/apt15e/apt/route/service/impl/RouteInfoServiceImpl.java new file mode 100644 index 0000000..5827a6f --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/route/service/impl/RouteInfoServiceImpl.java @@ -0,0 +1,33 @@ +package org.nl.apt15e.apt.route.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.apt15e.apt.route.dao.RouteInfo; +import org.nl.apt15e.apt.route.service.RouteInfoService; +import org.nl.apt15e.apt.route.service.mapper.RouteInfoMapper; +import org.nl.apt15e.apt.station.StationTypeEnum; +import org.nl.apt15e.apt.station.dao.Station; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/18 + */ +@Service +public class RouteInfoServiceImpl extends ServiceImpl implements RouteInfoService { + + @Resource + private RouteInfoMapper routeInfoMapper; + + @Override + public JSONObject getAllRouteInfo() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", HttpStatus.OK.value()); + jsonObject.put("data", routeInfoMapper.selectList(null)); + return jsonObject; + } +} diff --git a/src/main/java/org/nl/apt15e/apt/route/service/mapper/RouteInfoMapper.java b/src/main/java/org/nl/apt15e/apt/route/service/mapper/RouteInfoMapper.java new file mode 100644 index 0000000..0ae8a7e --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/route/service/mapper/RouteInfoMapper.java @@ -0,0 +1,13 @@ +package org.nl.apt15e.apt.route.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.apt15e.apt.route.dao.RouteInfo; + +/** + * @author dsh + * 2025/7/18 + */ +@Mapper +public interface RouteInfoMapper extends BaseMapper { +} diff --git a/src/main/java/org/nl/apt15e/apt/route/service/rest/RouteInfoController.java b/src/main/java/org/nl/apt15e/apt/route/service/rest/RouteInfoController.java new file mode 100644 index 0000000..003e787 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/route/service/rest/RouteInfoController.java @@ -0,0 +1,26 @@ +package org.nl.apt15e.apt.route.service.rest; + +import org.nl.apt15e.apt.route.service.RouteInfoService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/18 + */ +@RestController +@RequestMapping("/routeInfo") +public class RouteInfoController { + + @Resource + private RouteInfoService routeInfoService; + + @RequestMapping("/getRouteInfo") + public ResponseEntity getRouteInfo(){ + return new ResponseEntity<>(routeInfoService.getAllRouteInfo(), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/apt15e/apt/station/StationTypeEnum.java b/src/main/java/org/nl/apt15e/apt/station/StationTypeEnum.java index 6777482..b00725c 100644 --- a/src/main/java/org/nl/apt15e/apt/station/StationTypeEnum.java +++ b/src/main/java/org/nl/apt15e/apt/station/StationTypeEnum.java @@ -11,11 +11,12 @@ import lombok.Getter; @AllArgsConstructor public enum StationTypeEnum { - //站点类型 Station工位点 Charge充电点 Breaks休息点 Docking对接点 + //站点类型 Station工位点 Charge充电点 Breaks休息点 Docking对接点 System系统自动生成点 STATION(1, "STATION", "工位点"), CHARGE(2, "CHARGE", "充电点"), BREAKS(3, "BREAKS", "休息点"), DOCKING(4, "DOCKING", "对接点"), + SYSTEM(5, "SYSTEM", "系统自动生成点"), //动作类型 Ascend升叉 Descend降叉 Move移动 Customize自定义 diff --git a/src/main/java/org/nl/apt15e/apt/station/dao/Station.java b/src/main/java/org/nl/apt15e/apt/station/dao/Station.java index 1ced694..4c700b9 100644 --- a/src/main/java/org/nl/apt15e/apt/station/dao/Station.java +++ b/src/main/java/org/nl/apt15e/apt/station/dao/Station.java @@ -16,10 +16,12 @@ import java.io.Serializable; public class Station implements Serializable { private static final long serialVersionUID = 1L; + @TableId + private Integer station_id; + /** * 站点编码 对应地图上站点 */ - @TableId private String station_code; /** @@ -28,7 +30,7 @@ public class Station implements Serializable { private String station_name; /** - * 站点类型 (Station工位点 Charge充电点 Breaks休息点 Docking对接点) + * 站点类型 (Station工位点 Charge充电点 Breaks休息点 Docking对接点 System系统自动生成点) */ private String station_type; diff --git a/src/main/java/org/nl/apt15e/apt/station/service/impl/StationServiceImpl.java b/src/main/java/org/nl/apt15e/apt/station/service/impl/StationServiceImpl.java index f4c5f40..dd2788b 100644 --- a/src/main/java/org/nl/apt15e/apt/station/service/impl/StationServiceImpl.java +++ b/src/main/java/org/nl/apt15e/apt/station/service/impl/StationServiceImpl.java @@ -1,7 +1,9 @@ package org.nl.apt15e.apt.station.service.impl; import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.apt15e.apt.station.StationTypeEnum; import org.nl.apt15e.apt.station.dao.Station; import org.nl.apt15e.apt.station.service.StationService; import org.nl.apt15e.apt.station.service.mapper.StationMapper; @@ -25,6 +27,7 @@ public class StationServiceImpl extends ServiceImpl impl @Override public List queryAllStation() { - return stationMapper.selectList(null); + return stationMapper.selectList(stationMapper.selectList(new LambdaQueryWrapper<>(Station.class) + .eq(Station::getStation_type, StationTypeEnum.STATION.getCode()))); } } diff --git a/src/main/java/org/nl/apt15e/apt/teaching/dto/TeachingRequest.java b/src/main/java/org/nl/apt15e/apt/teaching/dto/TeachingRequest.java new file mode 100644 index 0000000..e3d45f1 --- /dev/null +++ b/src/main/java/org/nl/apt15e/apt/teaching/dto/TeachingRequest.java @@ -0,0 +1,36 @@ +package org.nl.apt15e.apt.teaching.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/14 + */ +@Data +public class TeachingRequest { + + /** + * 地图名称 + */ + private String mapName; + + /** + * 站点名称 + */ + private String stationName; + + /** + * x坐标 + */ + private Double x; + + /** + * y坐标 + */ + private Double y; + + /** + * 角度 + */ + private Double z; +} diff --git a/src/main/java/org/nl/apt15e/apt/teaching/rest/TeachingController.java b/src/main/java/org/nl/apt15e/apt/teaching/rest/TeachingController.java index 8e742b9..e65526d 100644 --- a/src/main/java/org/nl/apt15e/apt/teaching/rest/TeachingController.java +++ b/src/main/java/org/nl/apt15e/apt/teaching/rest/TeachingController.java @@ -1,5 +1,6 @@ package org.nl.apt15e.apt.teaching.rest; +import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.annotations.Param; import org.nl.apt15e.apt.teaching.service.TeachingService; @@ -51,8 +52,10 @@ public class TeachingController { } @PostMapping("/getLocalMaps") - private ResponseEntity getLocalMaps() { - return new ResponseEntity<>(teachingService.getLocalMaps(), HttpStatus.OK); +// @Log("获取地图列表") + private JSONObject getLocalMaps() { +// return new ResponseEntity<>(teachingService.getLocalMaps(), HttpStatus.OK); + return teachingService.getLocalMaps(); } @PostMapping("/getRunMapZip") @@ -74,4 +77,9 @@ public class TeachingController { private ResponseEntity relocate(@RequestParam("x") Double x, @RequestParam("y") Double y, @RequestParam("angle") Double angle) { return new ResponseEntity<>(teachingService.relocate(x, y, angle), HttpStatus.OK); } + + @PostMapping("/oneClickDeployment") + private ResponseEntity oneClickDeployment(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.oneClickDeployment(mapName), HttpStatus.OK); + } } diff --git a/src/main/java/org/nl/apt15e/apt/teaching/service/TeachingService.java b/src/main/java/org/nl/apt15e/apt/teaching/service/TeachingService.java index 3a08c32..776adba 100644 --- a/src/main/java/org/nl/apt15e/apt/teaching/service/TeachingService.java +++ b/src/main/java/org/nl/apt15e/apt/teaching/service/TeachingService.java @@ -1,5 +1,7 @@ package org.nl.apt15e.apt.teaching.service; +import com.alibaba.fastjson.JSONObject; + import java.io.File; import java.util.Map; @@ -37,7 +39,7 @@ public interface TeachingService { /** * 获取后台地图列表 */ - Map getLocalMaps(); + JSONObject getLocalMaps(); /** * 部署地图 @@ -78,4 +80,10 @@ public interface TeachingService { * 重启车辆后台程序 */ Map restart(); + + /** + * 一键部署地图 + * @return + */ + Map oneClickDeployment(String mapName); } diff --git a/src/main/java/org/nl/apt15e/apt/teaching/service/impl/TeachingServiceImpl.java b/src/main/java/org/nl/apt15e/apt/teaching/service/impl/TeachingServiceImpl.java index fc65488..9afaf1a 100644 --- a/src/main/java/org/nl/apt15e/apt/teaching/service/impl/TeachingServiceImpl.java +++ b/src/main/java/org/nl/apt15e/apt/teaching/service/impl/TeachingServiceImpl.java @@ -2,11 +2,16 @@ package org.nl.apt15e.apt.teaching.service.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; +import org.nl.apt15e.apt.station.dao.Station; +import org.nl.apt15e.apt.station.service.StationService; import org.nl.apt15e.apt.teaching.service.TeachingService; import org.nl.apt15e.apt.vehicle.ProcessZip; import org.nl.apt15e.apt.vehicle.service.impl.VehicleInfoServiceImpl; @@ -16,7 +21,9 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; +import java.util.Collections; import java.util.Map; +import java.util.stream.IntStream; /** * @author dsh @@ -29,9 +36,12 @@ public class TeachingServiceImpl implements TeachingService { @Resource private ProcessZip processZip; + @Resource + private StationService stationService; + @Override public Map startMapping(String mapName) { - if ("".equals(mapName)){ + if (StrUtil.isBlank(mapName)){ throw new BadRequestException("mapName is empty"); } JSONObject params = new JSONObject(); @@ -50,8 +60,10 @@ public class TeachingServiceImpl implements TeachingService { log.info("开始建图:{}",body); if ("200".equals(body.getString("code"))){ body =(JSONObject) this.startManual(); + body.put("message","开始建图成功"); + return body; } - return body; + } log.info("开始建图失败"); throw new BadRequestException("开始建图失败"); @@ -67,11 +79,14 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("切手动失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("切手动:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","切自动成功"); + return body; + } } log.info("切手动失败"); throw new BadRequestException("切手动失败"); @@ -87,10 +102,13 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("切自动失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("切自动:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message","切自动成功"); + } return body; } log.info("切自动失败"); @@ -107,14 +125,15 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("结束建图失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("结束建图:{}",body); if ("200".equals(body.getString("code"))){ body =(JSONObject) this.stopManual(); + body.put("message","结束建图成功"); + return body; } - return body; } log.info("结束建图失败"); throw new BadRequestException("结束建图失败"); @@ -122,7 +141,7 @@ public class TeachingServiceImpl implements TeachingService { @Override public Map setStation(String stationName) { - if ("".equals(stationName)){ + if (StrUtil.isBlank(stationName)){ throw new BadRequestException("spotCode is empty"); } JSONObject params = new JSONObject(); @@ -135,18 +154,21 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("设置站点失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("设置站点:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","设置站点成功"); + return body; + } } log.info("设置站点失败"); throw new BadRequestException("设置站点失败"); } @Override - public Map getLocalMaps() { + public JSONObject getLocalMaps() { HttpResponse response = null; try { response = HTTPUtil.get("http://192.168.100.82:9998","/tool/editor/getLocalMaps", new JSONObject()); @@ -155,11 +177,14 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("获取地图列表失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("获取地图列表:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","获取地图列表成功"); + return body; + } } log.info("获取地图列表失败"); throw new BadRequestException("获取地图列表失败"); @@ -167,7 +192,7 @@ public class TeachingServiceImpl implements TeachingService { @Override public Map deployRunMap(String mapName) { - if ("".equals(mapName)){ + if (StrUtil.isBlank(mapName)){ throw new BadRequestException("mapName is empty"); } JSONObject params = new JSONObject(); @@ -180,11 +205,14 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("部署地图失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("部署地图:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","部署地图成功"); + return body; + } } log.info("部署地图失败"); throw new BadRequestException("部署地图失败"); @@ -192,7 +220,7 @@ public class TeachingServiceImpl implements TeachingService { @Override public Map changeCurrentRunMap(String mapName) { - if ("".equals(mapName)){ + if (StrUtil.isBlank(mapName)){ throw new BadRequestException("mapName is empty"); } JSONObject params = new JSONObject(); @@ -205,11 +233,14 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("应用地图失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("应用地图:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","应用地图成功"); + return body; + } } log.info("应用地图失败"); throw new BadRequestException("应用地图失败"); @@ -217,7 +248,7 @@ public class TeachingServiceImpl implements TeachingService { @Override public byte[] getRunMapZip(String mapName) { - if ("".equals(mapName)){ + if (StrUtil.isBlank(mapName)){ throw new BadRequestException("mapName is empty"); } JSONObject params = new JSONObject(); @@ -230,12 +261,11 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("获取地图包失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取zip包 byte[] zipBytes = response.bodyBytes(); + log.info("解析地图包数据"); processZip.processZipResponse(zipBytes); - - log.info("获取地图包"); return zipBytes; } log.info("获取地图包失败"); @@ -254,7 +284,7 @@ public class TeachingServiceImpl implements TeachingService { .header("token", "admin123") .header("name", "lx-script") .header("Content-Type", "multipart/form-data;charset=UTF-8") - .form("areaId", VehicleInfoServiceImpl.vehicleInfo.getAreaId()) + .form("areaId", 1) .form("file",zipFile,mapName+".zip") .execute(); } catch (Exception e) { @@ -262,11 +292,14 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("同步地图失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("同步地图:{}",body); - return body; + if (body.getBoolean("state")){ + body.put("message","同步地图成功"); + return body; + } } log.info("同步地图失败"); throw new BadRequestException("同步地图失败"); @@ -277,6 +310,9 @@ public class TeachingServiceImpl implements TeachingService { if (ObjectUtil.isEmpty(x) || ObjectUtil.isEmpty(y) || ObjectUtil.isEmpty(angle)){ throw new BadRequestException("params is empty"); } + log.info(angle.toString()); + angle = (angle * 180) / Math.PI; + log.info(angle.toString()); JSONObject params = new JSONObject(); params.put("x", x); params.put("y", y); @@ -284,22 +320,26 @@ public class TeachingServiceImpl implements TeachingService { params.put("noisyX", 0.5); params.put("noisyY", 0.5); params.put("noisyAngle", Math.PI); + log.info(params.getString("angle")); HttpResponse response = null; try { response = HTTPUtil.post("http://192.168.100.82:9998","/tool/rob/relocate", params); } catch (Exception e) { log.info("访问车体重定位接口报错:{}",e.getMessage()); - throw new BadRequestException("重定位失败"); + throw new BadRequestException("重定位指令下发失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("重定位:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","重定位指令下发成功,正在重新定位中。"); + return body; + } } - log.info("重定位失败"); - throw new BadRequestException("重定位失败"); + log.info("重定位指令下发失败"); + throw new BadRequestException("重定位指令下发失败"); } @Override @@ -312,13 +352,61 @@ public class TeachingServiceImpl implements TeachingService { throw new BadRequestException("车体程序重启失败"); } // 检查响应状态码 - if (response.isOk()) { + if (response.isOk() && response.body() != null) { // 获取响应体内容 JSONObject body = JSON.parseObject(response.body()); log.info("车体程序重启:{}",body); - return body; + if ("200".equals(body.getString("code"))){ + body.put("message","车体程序重启成功"); + return body; + } } log.info("车体程序重启失败"); throw new BadRequestException("车体程序重启失败"); } + + @Override + public Map oneClickDeployment(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException("mapName is empty"); + } + JSONObject response = new JSONObject(); + // 部署地图 + this.deployRunMap(mapName); + // 应用地图 + this.changeCurrentRunMap(mapName); + // 解析地图包并同步到调度 + this.synchronizeMap(mapName); + // 重启本体程序 + this.restart(); + + // 重定位,需要等待一会 + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + Station station = stationService.getOne(new LambdaQueryWrapper<>(Station.class) + .eq(Station::getStation_code,"C") + ); + this.relocate(station.getX(),station.getY(),station.getAngle()); + response.put("code", 200); + response.put("message", "部署成功"); +// try { +// Thread.sleep(10000); +// } catch (InterruptedException e) { +// throw new RuntimeException(e); +// } +// Station station = stationService.getOne(new LambdaQueryWrapper<>(Station.class) +// .eq(Station::getStation_code,"C") +// ); +// log.info("station:{}",station); +// response = (JSONObject) this.relocate(station.getX(), station.getY(), station.getAngle()); +// if (response.getIntValue("code") != 200){ +// response.put("code", response.getIntValue("code")); +// response.put("message","重定位失败"); +// return response; +// } + return response; + } } diff --git a/src/main/java/org/nl/apt15e/apt/vehicle/ProcessZip.java b/src/main/java/org/nl/apt15e/apt/vehicle/ProcessZip.java index 60db64d..7d492e1 100644 --- a/src/main/java/org/nl/apt15e/apt/vehicle/ProcessZip.java +++ b/src/main/java/org/nl/apt15e/apt/vehicle/ProcessZip.java @@ -1,19 +1,32 @@ package org.nl.apt15e.apt.vehicle; +import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.apt15e.apt.map.dao.MapInfo; +import org.nl.apt15e.apt.map.dto.ProcessMapYamlDto; +import org.nl.apt15e.apt.map.service.MapInfoService; +import org.nl.apt15e.apt.route.dao.RouteInfo; +import org.nl.apt15e.apt.route.service.RouteInfoService; import org.nl.apt15e.apt.station.StationTypeEnum; import org.nl.apt15e.apt.station.dao.Station; import org.nl.apt15e.apt.station.service.StationService; import org.nl.apt15e.apt.station.service.impl.StationServiceImpl; +import org.nl.apt15e.common.BadRequestException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.yaml.snakeyaml.Yaml; import javax.annotation.Resource; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.*; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -22,31 +35,46 @@ import java.util.zip.ZipInputStream; * @author dsh * 2025/7/7 */ +@Slf4j @Service public class ProcessZip { @Resource - private StationServiceImpl stationService; + private StationService stationService; + @Resource + private MapInfoService mapInfoService; + + @Resource + private RouteInfoService routeInfoService; + + + private static final String PGM_MAGIC_NUMBER = "P5"; + private static final String OUTPUT_DIR = "/logs"; + + @Transactional(rollbackFor = Exception.class) public void processZipResponse(byte[] zipBytes) { try (ByteArrayInputStream bais = new ByteArrayInputStream(zipBytes); ZipInputStream zis = new ZipInputStream(bais, StandardCharsets.UTF_8)) { + MapInfo mapInfo = new MapInfo(); + ZipEntry entry; // 遍历ZIP文件中的每个条目 while ((entry = zis.getNextEntry()) != null) { String entryName = entry.getName(); - // 3. 筛选.lxmap文件 + // 处理.lxmap文件,获取点位信息和路径信息 if (entryName.toLowerCase().endsWith(".lxmap")) { - System.out.println("解析文件: " + entryName); + log.info("解析文件: {}", entryName); - // 4. 读取文件内容(使用BufferedReader按行处理) + // 读取文件内容(使用BufferedReader按行处理) BufferedReader reader = new BufferedReader( new InputStreamReader(zis, StandardCharsets.UTF_8)); - //存储站点之前先清除站点表中数据 + //存储站点之前先清除站点表中数据和路径表中的数据 stationService.remove(new LambdaQueryWrapper<>()); + routeInfoService.remove(new LambdaQueryWrapper<>()); String line; int lineNumber = 0; @@ -55,11 +83,93 @@ public class ProcessZip { // 5. 处理每一行数据 processLine(line, lineNumber); } + if (lineNumber == 0) { + throw new BadRequestException("地图包数据为空,扫描失败。请重新建图"); + } } + // 点云图 .pgm 文件转换 .png + if (entryName.toLowerCase().endsWith(".pgm")) { + log.info("处理PGM文件: {}",entryName); + + // 读取整个PGM文件到字节数组 + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int len; + while ((len = zis.read(buffer)) > 0) { + baos.write(buffer, 0, len); + } + byte[] pgmData = baos.toByteArray(); + + // 转换PGM为PNG + BufferedImage pngImage = convertPgmToPng(pgmData); + // 记录地图图片像素高、宽 + mapInfo.setWidth((double) pngImage.getWidth()); + mapInfo.setHeight((double) pngImage.getHeight()); + if (ObjectUtil.isEmpty(pngImage)) { + log.info("PGM转换失败: {}", entryName); + throw new BadRequestException("地图PGM转换失败: "); + } + + // 保存PNG文件 + String pngFileName = entryName.substring(0, entryName.lastIndexOf('.')) + ".png"; + // 记录地图编码、名称 + mapInfo.setMapCode(pngFileName); + mapInfo.setMapName(pngFileName); + savePngImage(pngImage, pngFileName); + } + } + // 解析.yaml点云图信息(像素比例,点云图左下角像素坐标信息) + if (entryName.toLowerCase().endsWith(".yaml")) { + log.info("处理.yaml文件地图信息: {}",entryName); + + try{ + BufferedReader reader = new BufferedReader(new InputStreamReader(zis, StandardCharsets.UTF_8)); + // 检查并处理YAML头部 + StringBuilder contentBuilder = new StringBuilder(); + String line = reader.readLine(); + // 读取内容标识 + boolean flag = false; + // 跳过以%开头的指令行 + if (line != null && line.startsWith("%")) { + log.info("跳过YAML指令: {}" , line); + // 读取下一行 + line = reader.readLine(); + } + + // 读取剩余内容 + while (line != null) { + flag = true; + contentBuilder.append(line).append("\n"); + line = reader.readLine(); + } + if (!flag){ + throw new BadRequestException(".YAML文件内容为空"); + } + // 解析YAML内容 + String yamlContent = contentBuilder.toString(); + ProcessMapYamlDto processMapYamlDto = new Yaml().loadAs(yamlContent, ProcessMapYamlDto.class); + log.info(".yaml数据: {}", processMapYamlDto); + if (ObjectUtil.isEmpty(processMapYamlDto) || processMapYamlDto.getOrigin().length<3){ + throw new BadRequestException(".yaml数据信息格式错误"); + } + // 记录像素比例和图片左下角像素坐标 + mapInfo.setResolution(processMapYamlDto.getResolution()); + mapInfo.setX(processMapYamlDto.getOrigin()[0]); + mapInfo.setY(processMapYamlDto.getOrigin()[1]); + mapInfo.setAngle(processMapYamlDto.getOrigin()[2]); + } catch (IOException e) { + throw new BadRequestException("YAML解析失败:"+ e.getMessage()); + } + } + } + // 保存地图信息 + if (ObjectUtil.isNotEmpty(mapInfo)) { + log.info("地图信息:{}",mapInfo); + mapInfoService.save(mapInfo); } } catch (Exception e) { - throw new RuntimeException("处理ZIP文件失败", e); + throw new BadRequestException("处理地图包数据失败:"+e.getMessage()); } } @@ -69,7 +179,7 @@ public class ProcessZip { return; } - // 检查是否以"Cairn: Goal"开头 + // 检查是否以"Cairn"开头 if (line.startsWith("Cairn:")) { // 截取开头后面的数据(6个字符长度) String dataPart = line.substring(6).trim(); @@ -78,20 +188,44 @@ public class ProcessZip { // 按空格分割字段(多个连续空格视为一个分隔符) String[] processData = dataPart.split("\\s+"); - //判断数据是否是点位数据 && 数据长度正常 && 点位类型是1工位点(站点) - //满足以上条件 目前只记录站点 - if ("Goal".equals(processData[0]) && processData.length > 9 && "1".equals(processData[8])) { - System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, Arrays.toString(processData)); + //判断数据是否是点位数据 && 数据长度正常 + // Goal点位标识 + if ("Goal".equals(processData[0]) && processData.length > 9) { + System.out.printf("行号 %d - 有效点位数据: %s%n", lineNumber, Arrays.toString(processData)); Station station = new Station(); + station.setStation_id(Integer.valueOf(processData[1])); station.setStation_code(processData[2]); station.setStation_name(processData[2]); station.setX(Double.valueOf(processData[5])); station.setY(Double.valueOf(processData[6])); station.setAngle(Double.valueOf(processData[7])); - station.setAction_type(StationTypeEnum.CUSTOMIZE.getCode()); - station.setStation_type(StationTypeEnum.STATION.getCode()); + // 1类型手动设置站点: 工位点、自定义 + if ("1".equals(processData[8])) { + station.setAction_type(StationTypeEnum.CUSTOMIZE.getCode()); + station.setStation_type(StationTypeEnum.STATION.getCode()); + // 0类型系统自动生成: 系统自动生成点、只移动 + }else if ("0".equals(processData[8])) { + station.setAction_type(StationTypeEnum.MOVE.getCode()); + station.setStation_type(StationTypeEnum.SYSTEM.getCode()); + } stationService.save(station); System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, station); + //Route路径标识 + }else if ("Route".equals(processData[0]) && processData.length > 23){ + System.out.printf("行号 %d - 有效路径数据: %s%n", lineNumber, Arrays.toString(processData)); + RouteInfo routeInfo = new RouteInfo(); + routeInfo.setRoute_id(Integer.valueOf(processData[1])); + routeInfo.setStart_id(Integer.valueOf(processData[2])); + routeInfo.setEnd_id(Integer.valueOf(processData[3])); + routeInfo.setStart_x(Double.valueOf(processData[4])); + routeInfo.setStart_y(Double.valueOf(processData[5])); + routeInfo.setEnd_x(Double.valueOf(processData[6])); + routeInfo.setEnd_y(Double.valueOf(processData[7])); + routeInfo.setNavigation_mode(Integer.valueOf(processData[16])); + routeInfo.setRoute_type(Integer.valueOf(processData[17])); + routeInfoService.save(routeInfo); + + System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, routeInfo); } } else { System.out.printf("行号 %d - 警告: 'Cairn: Goal'后无内容%n", lineNumber); @@ -101,4 +235,69 @@ public class ProcessZip { System.out.printf("行号 %d - 忽略: %s%n", lineNumber, line); } } + + private BufferedImage convertPgmToPng(byte[] pgmData) { + try { + // 使用字节缓冲区方便解析 + ByteBuffer buffer = ByteBuffer.wrap(pgmData); + + // 解析魔术数字 (P5) + StringBuilder magic = new StringBuilder(); + char c; + while ((c = (char) buffer.get()) != '\n') { + magic.append(c); + } + if (!magic.toString().equals(PGM_MAGIC_NUMBER)) { + throw new BadRequestException("不支持的PGM格式,仅支持P5二进制格式"); + } + + // 解析宽度和高度 + StringBuilder dimensionBuilder = new StringBuilder(); + while ((c = (char) buffer.get()) != '\n') { + dimensionBuilder.append(c); + } + String[] dimensions = dimensionBuilder.toString().trim().split("\\s+"); + if (dimensions.length < 2) { + throw new BadRequestException("无效的尺寸格式"); + } + int width = Integer.parseInt(dimensions[0]); + int height = Integer.parseInt(dimensions[1]); + + // 解析最大灰度值 (通常255) + StringBuilder maxValBuilder = new StringBuilder(); + while ((c = (char) buffer.get()) != '\n') { + maxValBuilder.append(c); + } + int maxVal = Integer.parseInt(maxValBuilder.toString().trim()); + + // 创建BufferedImage (灰度图像) + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + byte[] imageData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + + // 复制图像数据 (跳过头部) + buffer.get(imageData); + + return image; + } catch (Exception e) { + log.info("PGM解析错误: {}", e.getMessage()); + throw new BadRequestException("PGM解析错误: "+e.getMessage()); + } + } + + private void savePngImage(BufferedImage image, String fileName) { + try { + // 确保输出目录存在 + File outputDir = new File(OUTPUT_DIR); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + // 写入PNG文件 + File outputFile = new File(outputDir, fileName); + ImageIO.write(image, "png", outputFile); + log.info("PNG文件已保存: {}", outputFile.getAbsolutePath()); + } catch (IOException e) { + log.info("保存PNG失败: {}",e.getMessage()); + } + } } diff --git a/src/main/java/org/nl/apt15e/apt/vehicle/dao/VehicleInfo.java b/src/main/java/org/nl/apt15e/apt/vehicle/dao/VehicleInfo.java index 004db5f..dc92c5a 100644 --- a/src/main/java/org/nl/apt15e/apt/vehicle/dao/VehicleInfo.java +++ b/src/main/java/org/nl/apt15e/apt/vehicle/dao/VehicleInfo.java @@ -62,4 +62,18 @@ public class VehicleInfo implements Serializable { */ private VehicleException exceptionInfo; + /** + * 车辆x坐标 + */ + private Double x; + + /** + * 车辆y坐标 + */ + private Double y; + + /** + * 车辆角度 + */ + private Double theta; } diff --git a/src/main/java/org/nl/apt15e/apt/vehicle/rest/VehicleInfoController.java b/src/main/java/org/nl/apt15e/apt/vehicle/rest/VehicleInfoController.java index 043dfd2..2e236f5 100644 --- a/src/main/java/org/nl/apt15e/apt/vehicle/rest/VehicleInfoController.java +++ b/src/main/java/org/nl/apt15e/apt/vehicle/rest/VehicleInfoController.java @@ -3,6 +3,7 @@ package org.nl.apt15e.apt.vehicle.rest; import lombok.extern.slf4j.Slf4j; import org.nl.apt15e.apt.vehicle.dao.VehicleInfo; import org.nl.apt15e.apt.vehicle.service.VehicleInfoService; +import org.nl.apt15e.common.logging.annotation.Log; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -22,7 +23,7 @@ public class VehicleInfoController { private VehicleInfoService vehicleInfoService; @GetMapping("/getVehicleInfo") -// @Log("获取车辆信息") + @Log("获取车辆信息") public VehicleInfo getVehicleInfo() { return vehicleInfoService.getVehicleInfo(); } diff --git a/src/main/java/org/nl/apt15e/apt/vehicle/service/impl/VehicleInfoServiceImpl.java b/src/main/java/org/nl/apt15e/apt/vehicle/service/impl/VehicleInfoServiceImpl.java index d4b2f8f..dd824d3 100644 --- a/src/main/java/org/nl/apt15e/apt/vehicle/service/impl/VehicleInfoServiceImpl.java +++ b/src/main/java/org/nl/apt15e/apt/vehicle/service/impl/VehicleInfoServiceImpl.java @@ -58,6 +58,11 @@ public class VehicleInfoServiceImpl implements VehicleInfoService { //上报的异常信息 VehicleException vehicleException = JSONObject.toJavaObject(data.getJSONObject("amrException"), VehicleException.class); vehicleInfo.setExceptionInfo(vehicleException); + //x,y,theta + JSONObject coordinate = data.getJSONObject("coordinate"); + vehicleInfo.setX(coordinate.getDouble("x")); + vehicleInfo.setY(coordinate.getDouble("y")); + vehicleInfo.setTheta(coordinate.getDouble("theta")); System.out.println("Response Body: " + data); System.out.println("vehicleInfo: " + vehicleInfo); } diff --git a/src/main/java/org/nl/apt15e/common/logging/annotation/Log.java b/src/main/java/org/nl/apt15e/common/logging/annotation/Log.java index 64be76a..7dc2400 100644 --- a/src/main/java/org/nl/apt15e/common/logging/annotation/Log.java +++ b/src/main/java/org/nl/apt15e/common/logging/annotation/Log.java @@ -1,9 +1,6 @@ package org.nl.apt15e.common.logging.annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * @author dsh @@ -11,6 +8,7 @@ import java.lang.annotation.Target; */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@Documented public @interface Log { String value() default ""; diff --git a/src/main/java/org/nl/apt15e/common/logging/aspect/LogAspect.java b/src/main/java/org/nl/apt15e/common/logging/aspect/LogAspect.java index 3f7e751..5d38b61 100644 --- a/src/main/java/org/nl/apt15e/common/logging/aspect/LogAspect.java +++ b/src/main/java/org/nl/apt15e/common/logging/aspect/LogAspect.java @@ -4,10 +4,12 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @@ -49,18 +51,16 @@ public class LogAspect { @Around("logPointCut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // MethodSignature signature = (MethodSignature) joinPoint.getSignature(); -// Method method = signature.getMethod(); -// String className = joinPoint.getTarget().getClass().getName(); - // 方法路径 -// String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()"; + // String params = JSONObject.toJSONString(joinPoint.getArgs()); - - +// +// Object result; -// log.info("【日志注解】开始执行 -- {}:{} {}", className, "111", params); +// log.info("【日志注解】开始执行 -- {}:{} {}", className, methodName, params); result = joinPoint.proceed(); // log.info("返回参数:{}" ,JSONObject.toJSONString(result)); return result; } + } diff --git a/src/main/java/org/nl/apt15e/config/CorsConfig.java b/src/main/java/org/nl/apt15e/config/CorsConfig.java new file mode 100644 index 0000000..9801aa1 --- /dev/null +++ b/src/main/java/org/nl/apt15e/config/CorsConfig.java @@ -0,0 +1,23 @@ +package org.nl.apt15e.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author dsh + * 2025/7/14 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedHeaders(CorsConfiguration.ALL) + .allowedMethods(CorsConfiguration.ALL) + .allowCredentials(true) + .maxAge(3600); + } +} diff --git a/src/main/java/org/nl/apt15e/config/thread/ProtobufWebSocketHandler.java b/src/main/java/org/nl/apt15e/config/thread/ProtobufWebSocketHandler.java new file mode 100644 index 0000000..d28ea4a --- /dev/null +++ b/src/main/java/org/nl/apt15e/config/thread/ProtobufWebSocketHandler.java @@ -0,0 +1,73 @@ +//package org.nl.apt15e.config.thread; +// +//import comm_protocol.Robottype; +//import comm_protocol.Uidata; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.stereotype.Component; +//import org.springframework.web.socket.BinaryMessage; +//import org.springframework.web.socket.CloseStatus; +//import org.springframework.web.socket.TextMessage; +//import org.springframework.web.socket.WebSocketSession; +//import org.springframework.web.socket.handler.BinaryWebSocketHandler; +// +//import static comm_protocol.Uidata.UiDatagram.ItemCase.*; +// +///** +// * @author dsh +// * 2025/7/9 +// */ +//@Slf4j +//@Component +//public class ProtobufWebSocketHandler extends BinaryWebSocketHandler { +// +// @Override +// protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { +// try { +// // 解析Protobuf数据 +// byte[] payload = message.getPayload().array(); +// Uidata.UiDatagram datagram = Uidata.UiDatagram.parseFrom(payload); +// +// switch (datagram.getItemCase()) { +// case ONLINE_INFO: +// Uidata.UiOnline online = datagram.getOnlineInfo(); +// System.out.println("Received online_info: " + online); +// break; +// +// case HEARTBEAT: +// Uidata.UiHeartbeat heartbeat = datagram.getHeartbeat(); +// System.out.println("Received heartbeat: " + heartbeat); +// break; +// +// case LASER_SCAN: +// Robottype.LaserData laser = datagram.getLaserScan(); +// System.out.println("Received laser_scan: " + laser); +// break; +// +// case IMAGE: +// // 处理图像数据 +// break; +// +// case CONTROL_SPEED: +// // 处理速度控制 +// break; +// +// case ITEM_NOT_SET: +// System.err.println("UiDatagram item not set!"); +// break; +// } +// session.close(); +// } catch (Exception e) { +// log.error("Error processing protobuf message", e); +// } +// } +// +// @Override +// public void afterConnectionEstablished(WebSocketSession session) { +// log.info("Connected to WebSocket server: {}", session.getUri()); +// } +// +// @Override +// public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { +// log.info("Connection closed: {}", status); +// } +//} diff --git a/src/main/java/org/nl/apt15e/config/thread/WebsocketClientConfig.java b/src/main/java/org/nl/apt15e/config/thread/WebsocketClientConfig.java new file mode 100644 index 0000000..3a9296c --- /dev/null +++ b/src/main/java/org/nl/apt15e/config/thread/WebsocketClientConfig.java @@ -0,0 +1,45 @@ +//package org.nl.apt15e.config.thread; +// +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.stereotype.Component; +//import org.springframework.web.socket.client.WebSocketClient; +//import org.springframework.web.socket.client.WebSocketConnectionManager; +//import org.springframework.web.socket.client.standard.StandardWebSocketClient; +// +//import javax.websocket.ContainerProvider; +//import javax.websocket.WebSocketContainer; +// +///** +// * @author dsh +// * 2025/7/9 +// */ +//@Slf4j +//@Configuration +//public class WebsocketClientConfig { +// +// @Bean +// public WebSocketClient webSocketClient() { +// // 创建WebSocket容器并配置缓冲区大小 +// WebSocketContainer container = ContainerProvider.getWebSocketContainer(); +// // 256KB +// container.setDefaultMaxBinaryMessageBufferSize(100 * 1024); +// container.setDefaultMaxTextMessageBufferSize(100 * 1024); +// +// return new StandardWebSocketClient(container); +// } +// +// @Bean +// public WebSocketConnectionManager webSocketConnectionManager() { +// String serverUrl = "ws://192.168.100.82:9998/ws/AGVInfo"; +// +// WebSocketConnectionManager manager = new WebSocketConnectionManager( +// this.webSocketClient(), +// new ProtobufWebSocketHandler(), +// serverUrl +// ); +// manager.setAutoStartup(true); +// return manager; +// } +//} diff --git a/src/main/proto/robottype.proto b/src/main/proto/robottype.proto new file mode 100644 index 0000000..1afed7b --- /dev/null +++ b/src/main/proto/robottype.proto @@ -0,0 +1,215 @@ +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +package comm_protocol; + +message RobotShape { + double front = 1; + double back = 2; + double left = 3; + double right = 4; + double radius = 5; +} + +message Point { + double x = 1; + double y = 2; + double intensity = 3; +} +message PointXYZ { + sint32 x = 1; + sint32 y = 2; + sint32 z = 3; +} +message OrientedPoint { + double x = 1; + double y = 2; + double theta = 3; +} + +message RobotSpeed { + double vx = 1; + double vy = 2; + double w = 3; + int64 timeStamp = 4; +} + +message RobotState { + OrientedPoint pose = 1; + RobotSpeed speed = 2; + int64 timeStamp = 3; +} + +message RobotPath { + int64 timeStamp = 1; + repeated RobotState state = 2; +} + +message LaserData { + sint32 location = 1; + int64 timeStamp = 2; + repeated Point scan = 3; +} + +message ImageData { + sint32 location = 1; + sint64 timeStamp = 2; + sint32 width = 3; + sint32 height = 4; + sint32 chanel = 5; + sint32 package_index=6; + sint32 package_length=7; + sint32 imgdata_size=8; + sint32 curr_size=9; + bytes data = 10; +} +message PcloudData { + sint32 location = 1; + sint64 timeStamp = 2; + repeated PointXYZ point = 3; +} + +message ControlLimit { + double max_vel = 1; + double max_w = 2; + int32 turn_dir_limit = 3; + int32 shelf_dir_limit = 4; +} +message stReachThreshold { + double reach_angle = 1; + double reach_dist = 2; +} + +message PathProperty { + int32 move_mode = 1; + bool avoid_obstacle = 2; + double path_width = 3; + double safe_dist = 4; + double min_turn_radius = 5; + int32 route_mode = 6; + string path_extends = 7; +} + + +message RobotTask { + + message WayPoint { + // 路径信息,路径a-b,路径信息放在b点中 + message PathInfo { + enum Sensor { + NONE = 0; + DEPTH_CAMERA = 1; //深度相机 + ULTRASONIC = 2; //超声 + } + double distance2point = 1; // 到点距离阈值(单位m) + double angle2point = 2; // 到点角度阈值(单位rad) + double safe_distance = 3; // 安全距离 + bool avoid_obstacle = 4; // 是否避障 + double max_velocity = 5; // 最大速度(单位m/s) + double path_width = 6; // 路径宽度(单位m) + double min_turn_radius = 7; // 最小转弯半径 + int32 sensor = 8; // 开启laser之外的其他传感器,可以是Sensor的多个组合 + int32 move_mode = 9; + int32 route_mode = 10; + string path_extends = 11; + } + string point_name = 1; // 点名 + RobotState coordinate = 2; // 位姿 + PathInfo path_info = 3; // 路径信息 + // 所有被其他车锁着点中距离该点最近的点,这个信息告诉本体可以让本体进行灵活多车避障 + RobotState min_dist_point = 4; + bool is_lock = 6; + stReachThreshold reach_theta = 7; + string point_extends = 8; + } + + int64 command = 1; + int64 sub_command = 2; + string params = 3; + repeated WayPoint way_point = 4; + RobotState init_pose = 5; + RobotState target_pose = 6; + bool is_final = 7; + string robot_id = 8; + string robot_task_id = 9; + int64 timeStamp = 10; + bool need_response = 11; // 是否需要回复 + // 货架信息,第一位数值代表货架类型,从1开始;第二位代表车与货架长边的关系,0-平行,1-垂直 + // 位数之间采用空格隔开;第一位是0的话代表不带货架; + string shelf_info = 12; + RobotPath nav_robot_path = 13; +} + +message RobotTaskResult { + string task_id = 1; + string robot_id = 2; + int64 command = 3; + int32 result = 4; // 0-success +} + +message RobotPoseWithCov { + double x = 1; + double y = 2; + double theta = 3; + double noisy_x = 4; + double noisy_y = 5; + double noisy_theta = 6; +} + +message Relocate { + RobotPoseWithCov reloc_pose = 1; + string robot_id = 2; +} + +message SwitchMap { + string map_id = 1; + string robot_id = 2; +} + +message RobotBase { + message BatteryInfo { + double soc = 1; // 电池剩余电量百分比 + int32 status = 2; // 电池异常 + uint64 update_time = 3; + } + message DeviceInfo { + bool stop_button = 1; // 急停按钮是否拍下 + bool collision = 2; // 是否发生碰撞 + bool shut_down = 3; // 软关机 + bool inactive = 4; // 底层硬件触发手动模式 + uint64 update_time = 5; + } + message MoveInfo { + string heading_spot = 1; // 下一个目标点 + // double task_complete_percent = 2; // 导航任务完成百分比 + // RobotSpeed velocity = 3; // 小车速度 + uint64 update_time = 4; + } + message Locate { + RobotPoseWithCov current_state = 1; // 小车当前位姿 + int32 locate = 2; // 定位结果 + uint64 update_time = 3; + } + BatteryInfo battery_info = 1; // 电池信息 + DeviceInfo device_info = 2; + MoveInfo move_info = 3; + Locate locate = 4; + string robot_id = 5; + // 高32位代表设备类型,低32位数值代表异常值 + repeated uint64 exception_code = 6; + int32 payloads = 7; // 物料信息,不同应用车型有不同含义 + int32 lift_state = 8; // 顶升状态 + bool robot_inactive = 9; // 是否手动模式,软件触发 + int32 digital_status = 10; // 数字输出端口状态,不同应用车型有不同含义 + string data_pool = 11; + map runnig_fsm = 12; +} + +message RCSInfo { + int32 action = 1; + string action_params = 2; + string fsm_state = 3; + string rcs_task_id = 4; + int32 rcs_task_state = 5; +} diff --git a/src/main/proto/uidata.proto b/src/main/proto/uidata.proto new file mode 100644 index 0000000..42d289f --- /dev/null +++ b/src/main/proto/uidata.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +import "robottype.proto"; + +option optimize_for = LITE_RUNTIME; + +package comm_protocol; + +message UiOnline { + string uuid = 1; + int32 comm_type = 2; + int32 codec_pattern = 3; + string host = 4; + int32 port = 5; +} + +message UiHeartbeat { + string uuid = 1; +} + +message KVState { + string key = 1; + string value = 2; +} + +message DataPool { + map kv_state = 1; +} + +message UiDatagram { + oneof Item{ + UiOnline online_info = 1; // ui -> robot + UiHeartbeat heartbeat = 2; // ui -> robot + LaserData laser_scan = 3; + ImageData image = 4; + RobotSpeed control_speed = 5; + RobotState current_state = 6; + RobotPath current_path = 7; + RobotBase robot_base = 8; + RobotTask robot_task = 9; // ui -> robot + Relocate relocate = 10; // ui -> robot + RobotTaskResult robot_task_result = 11; + RCSInfo rcs_info = 12; + DataPool data_pool = 13; // ui -> robot + PcloudData pcloud = 14; + } +}