175 lines
7.6 KiB
Java
175 lines
7.6 KiB
Java
|
|
package com.cmvr.resource.service;
|
|||
|
|
|
|||
|
|
import cmvr.msgs.AgvUtils;
|
|||
|
|
import cmvr.quic_edge.v1.QuicEdge;
|
|||
|
|
import com.cmvr.common.exception.GlobalException;
|
|||
|
|
import com.cmvr.edge.client.model.EdgeCommonVO;
|
|||
|
|
import com.cmvr.edge.client.service.EdgeAgvService;
|
|||
|
|
import com.cmvr.resource.domain.ResourceDevice;
|
|||
|
|
import com.cmvr.resource.domain.ResourceMap;
|
|||
|
|
import com.cmvr.resource.domain.ResourceRobot;
|
|||
|
|
import com.cmvr.resource.mapper.ResourceDeviceMapper;
|
|||
|
|
import com.cmvr.resource.mapper.ResourceMapMapper;
|
|||
|
|
import com.cmvr.resource.mapper.ResourceRobotMapper;
|
|||
|
|
import lombok.RequiredArgsConstructor;
|
|||
|
|
import org.apache.commons.lang3.StringUtils;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
import java.util.Locale;
|
|||
|
|
import java.util.stream.Collectors;
|
|||
|
|
|
|||
|
|
@Service
|
|||
|
|
@RequiredArgsConstructor
|
|||
|
|
public class ResourceRobotControlService {
|
|||
|
|
private final ResourceRobotMapper robotMapper;
|
|||
|
|
private final ResourceDeviceMapper deviceMapper;
|
|||
|
|
private final ResourceMapMapper mapMapper;
|
|||
|
|
private final ResourceRobotMapService robotMapService;
|
|||
|
|
private final EdgeAgvService edgeAgvService;
|
|||
|
|
|
|||
|
|
public List<String> listRemoteMaps(String robotRef) {
|
|||
|
|
return edgeAgvService.listMaps(buildAgvRequest(requireRobot(robotRef)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int bindMap(String robotRef, String mapId) {
|
|||
|
|
ResourceRobot robot = requireRobot(robotRef);
|
|||
|
|
if (mapMapper.selectById(mapId) == null) {
|
|||
|
|
throw new GlobalException("地图不存在:" + mapId);
|
|||
|
|
}
|
|||
|
|
return robotMapService.bind(robot.getRobotId(), mapId, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public String downloadMap(String robotRef, String mapName) {
|
|||
|
|
return edgeAgvService.downloadMap(buildAgvRequest(requireRobot(robotRef)), mapName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public String uploadActiveMap(String robotRef) {
|
|||
|
|
ResourceRobot target = requireRobot(robotRef);
|
|||
|
|
ResourceMap map = mapMapper.selectById(target.getCurrentMapId());
|
|||
|
|
if (map == null) {
|
|||
|
|
throw new GlobalException("机器人未绑定有效地图");
|
|||
|
|
}
|
|||
|
|
String sourceRobotId = StringUtils.trimToNull(map.getSourceRobotId());
|
|||
|
|
String sourceMapName = StringUtils.trimToNull(map.getSourceMapName());
|
|||
|
|
if (sourceRobotId == null || sourceMapName == null) {
|
|||
|
|
throw new GlobalException("地图未配置来源机器人或来源地图名称");
|
|||
|
|
}
|
|||
|
|
ResourceRobot source = requireRobot(sourceRobotId);
|
|||
|
|
String content = edgeAgvService.downloadMap(buildAgvRequest(source), sourceMapName);
|
|||
|
|
if (StringUtils.isBlank(content)) {
|
|||
|
|
throw new GlobalException("下载的地图内容为空");
|
|||
|
|
}
|
|||
|
|
edgeAgvService.uploadMap(buildAgvRequest(target), sourceMapName, content);
|
|||
|
|
return "上传成功";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public ResourceRobot syncRuntimeState(String robotRef) {
|
|||
|
|
ResourceRobot robot = requireRobot(robotRef);
|
|||
|
|
try {
|
|||
|
|
AgvUtils.AgvRuntimeState runtime = edgeAgvService.getRuntimeState(buildAgvRequest(robot));
|
|||
|
|
if (runtime == null) {
|
|||
|
|
throw new GlobalException("AGV未返回运行状态");
|
|||
|
|
}
|
|||
|
|
if (runtime.hasBattery()) {
|
|||
|
|
int percentage = (int) Math.round(runtime.getBattery().getPercentage() * 100D);
|
|||
|
|
robot.setBatteryLevel(Math.max(0, Math.min(100, percentage)));
|
|||
|
|
}
|
|||
|
|
if (runtime.hasPose()) {
|
|||
|
|
AgvUtils.AgvPose2d pose = runtime.getPose();
|
|||
|
|
robot.setCurrentPosition(String.format(Locale.ROOT, "%.3f,%.3f,%.3f",
|
|||
|
|
pose.getX(), pose.getY(), pose.getTheta()));
|
|||
|
|
}
|
|||
|
|
robot.setStatus(String.valueOf(runtime.getMode()));
|
|||
|
|
robotMapper.updateAgvRuntimeStatus(robot);
|
|||
|
|
return robot;
|
|||
|
|
} catch (GlobalException error) {
|
|||
|
|
throw error;
|
|||
|
|
} catch (Exception error) {
|
|||
|
|
robot.setStatus("0");
|
|||
|
|
robotMapper.updateAgvRuntimeStatus(robot);
|
|||
|
|
String detail = StringUtils.isBlank(error.getMessage())
|
|||
|
|
? error.getClass().getSimpleName() : error.getMessage();
|
|||
|
|
throw new GlobalException("获取机器人运行状态失败:" + detail, error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public AgvUtils.AgvPose2d getRealtimeLocation(String robotRef) {
|
|||
|
|
AgvUtils.AgvRuntimeState runtime = edgeAgvService.getRuntimeState(
|
|||
|
|
buildAgvRequest(requireRobot(robotRef)));
|
|||
|
|
return runtime.hasPose() ? runtime.getPose() : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void navigateToPosition(String robotRef, Double x, Double y, Double theta) {
|
|||
|
|
if (x == null || y == null) {
|
|||
|
|
throw new GlobalException("X和Y坐标不能为空");
|
|||
|
|
}
|
|||
|
|
AgvUtils.AgvPose2d pose = AgvUtils.AgvPose2d.newBuilder()
|
|||
|
|
.setX(x)
|
|||
|
|
.setY(y)
|
|||
|
|
.setTheta(theta == null ? 0D : theta)
|
|||
|
|
.build();
|
|||
|
|
edgeAgvService.navigateToPose(buildAgvRequest(requireRobot(robotRef)), pose);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public String startMapping(String robotRef, int dimension, String mapName, boolean realTime) {
|
|||
|
|
AgvUtils.AgvMapDimension mapDimension = AgvUtils.AgvMapDimension.forNumber(dimension);
|
|||
|
|
if (mapDimension == null) {
|
|||
|
|
throw new GlobalException("无效的建图维度:" + dimension);
|
|||
|
|
}
|
|||
|
|
return edgeAgvService.startMapping(
|
|||
|
|
buildAgvRequest(requireRobot(robotRef)), mapDimension, mapName, realTime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void stopMapping(String robotRef) {
|
|||
|
|
edgeAgvService.stopMapping(buildAgvRequest(requireRobot(robotRef)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ResourceRobot requireRobot(String robotRef) {
|
|||
|
|
String normalized = StringUtils.trimToNull(robotRef);
|
|||
|
|
if (normalized == null) {
|
|||
|
|
throw new GlobalException("机器人标识不能为空");
|
|||
|
|
}
|
|||
|
|
ResourceRobot robot = robotMapper.selectById(normalized);
|
|||
|
|
if (robot == null) {
|
|||
|
|
robot = robotMapper.selectByRobotIdentity(normalized);
|
|||
|
|
}
|
|||
|
|
if (robot == null || "1".equals(robot.getArchivedStatus())) {
|
|||
|
|
throw new GlobalException("机器人不存在:" + normalized);
|
|||
|
|
}
|
|||
|
|
return robot;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private EdgeCommonVO buildAgvRequest(ResourceRobot robot) {
|
|||
|
|
List<ResourceDevice> agvDevices = deviceMapper.selectByRobotId(
|
|||
|
|
robot.getRobotId(), QuicEdge.DeviceKind.DEVICE_KIND_AGV_VALUE, true);
|
|||
|
|
if (agvDevices.isEmpty()) {
|
|||
|
|
List<ResourceDevice> onlineDevices = deviceMapper.selectByRobotId(robot.getRobotId(), null, true);
|
|||
|
|
if (onlineDevices.isEmpty()) {
|
|||
|
|
throw new GlobalException("机器人当前没有在线设备:" + robot.getRobotId());
|
|||
|
|
}
|
|||
|
|
String summary = onlineDevices.stream()
|
|||
|
|
.map(device -> device.getDeviceId() + "(" + deviceKindName(device.getDeviceKind()) + ")")
|
|||
|
|
.collect(Collectors.joining("、"));
|
|||
|
|
throw new GlobalException("机器人在线设备中没有AGV,当前设备:" + summary);
|
|||
|
|
}
|
|||
|
|
if (agvDevices.size() > 1) {
|
|||
|
|
String ids = agvDevices.stream().map(ResourceDevice::getDeviceId)
|
|||
|
|
.collect(Collectors.joining("、"));
|
|||
|
|
throw new GlobalException("机器人存在多个在线AGV,无法确定默认设备:" + ids);
|
|||
|
|
}
|
|||
|
|
EdgeCommonVO request = new EdgeCommonVO();
|
|||
|
|
request.setRobotId(robot.getRobotId());
|
|||
|
|
request.setDeviceId(agvDevices.get(0).getDeviceId());
|
|||
|
|
return request;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private String deviceKindName(Integer deviceKind) {
|
|||
|
|
if (deviceKind == null) {
|
|||
|
|
return "未知类型";
|
|||
|
|
}
|
|||
|
|
QuicEdge.DeviceKind kind = QuicEdge.DeviceKind.forNumber(deviceKind);
|
|||
|
|
return kind == null ? "未知类型" + deviceKind : kind.name().replace("DEVICE_KIND_", "");
|
|||
|
|
}
|
|||
|
|
}
|