feat: 机械臂
This commit is contained in:
parent
98031a0dcc
commit
0e12299816
2
.env
2
.env
@ -1,4 +1,4 @@
|
||||
# VITE_AGV_DEVICE_ID = 'agv_src2200'
|
||||
# VITE_AGV_DEVICE_ID = 'src1100'
|
||||
# VITE_ARM_DEVICE_ID = 'aubo_arm'
|
||||
# 机械臂IP 192.168.1.109:50052
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ app.use(express.static(path.join(__dirname, '../dist')));
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
// 获取机器人当前地图名称
|
||||
app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
// 获取agv运行时状态
|
||||
app.post('/api/agv/getRuntimeState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
@ -22,7 +22,7 @@ app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getMapStatus(request, (err, res) => {
|
||||
grpcClient.getRuntimeState(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
@ -31,7 +31,7 @@ app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.status
|
||||
data: res.state
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -44,20 +44,18 @@ app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取机器人当前地图名称
|
||||
// 获取机器人当前地图
|
||||
app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
},
|
||||
data: {
|
||||
map_name: req.body.mapName
|
||||
}
|
||||
map_name: req.body.mapName
|
||||
};
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.RobotConfigDownloadMap(request, (err, res) => {
|
||||
grpcClient.downloadMap(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
@ -66,7 +64,7 @@ app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.status
|
||||
data: res.content
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -79,83 +77,22 @@ app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取机器人状态
|
||||
app.post('/api/agv/getRobotState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
grpcClient.GetRobotLocation(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.status
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.GetBatteryStatus(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.status
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (response.code === 200 && resp.code ===200) {
|
||||
res.json({
|
||||
code: 200,
|
||||
data: {
|
||||
x: response.data.x,
|
||||
y: response.data.y,
|
||||
angle: response.data.angle,
|
||||
confidence: response.data.confidence,
|
||||
batteryLevel: resp.data.battery_level,
|
||||
batteryTemp: resp.data.battery_temp,
|
||||
charging: resp.data.charging,
|
||||
voltage: resp.data.voltage
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
// 机器人运动控制
|
||||
app.post('/api/agv/moveRobot', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const grpcClient = setVelocity(req.body.ip)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
},
|
||||
data: {
|
||||
velocity: {
|
||||
vx: req.body.vx,
|
||||
vy: req.body.vy,
|
||||
w: req.body.w,
|
||||
duration: req.body.duration
|
||||
vz: req.body.vz
|
||||
}
|
||||
};
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.RobotMotionControl(request, (err, res) => {
|
||||
grpcClient.setVelocity(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
@ -190,7 +127,7 @@ app.post('/api/agv/stopRobot', async (req, res) => {
|
||||
};
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.RobotControlStop(request, (err, res) => {
|
||||
grpcClient.stopVelocityControl(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
@ -215,6 +152,9 @@ app.post('/api/agv/stopRobot', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// ==================================================
|
||||
|
||||
// 获取机械臂末端位姿
|
||||
app.post('/api/arm/getPose', async (req, res) => {
|
||||
const request = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,74 +1,72 @@
|
||||
/**
|
||||
* @file agv_service.proto
|
||||
* @brief 定义AGV服务的gRPC接口,聚合所有AGV相关命令。
|
||||
* 服务端实现类为 gRPCAGVServiceImpl。
|
||||
*/
|
||||
syntax = "proto3";
|
||||
|
||||
import "cmvr/api/agv_command.proto";
|
||||
|
||||
package cmvr.api;
|
||||
|
||||
import "cmvr/api/common.proto";
|
||||
import "cmvr/api/agv_command.proto";
|
||||
|
||||
// AGV 通用服务。该服务只暴露控制器无关的能力,
|
||||
// 厂商协议、地图文件格式和控制器特有参数由具体 AGV 适配器内部处理。
|
||||
service AgvService {
|
||||
// 基本控制
|
||||
rpc GetStatusInfo(GetAgvStatusInfoCommand.Request) returns (GetAgvStatusInfoCommand.Feedback);
|
||||
// 获取 AGV 当前运行状态快照。
|
||||
rpc getRuntimeState(AgvRuntimeStateCommand.Request) returns (AgvRuntimeStateCommand.Feedback);
|
||||
|
||||
rpc GetBatteryStatus(RobotStatusBatteryCommand.Request) returns (RobotStatusBatteryCommand.Feedback);
|
||||
// 获取当前导航任务状态。
|
||||
rpc getNavigationStatus(AgvNavigationStatusCommand.Request) returns (AgvNavigationStatusCommand.Feedback);
|
||||
|
||||
rpc GetRobotLocation(RobotStatusLocCommand.Request) returns (RobotStatusLocCommand.Feedback);
|
||||
// 执行急停或等效安全停止动作。
|
||||
rpc emergencyStop(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
rpc RobotConfigDownloadMap(RobotConfigDownloadMapCommand.Request) returns (RobotConfigDownloadMapCommand.Feedback);
|
||||
// 清除可恢复故障或告警。
|
||||
rpc clearFault(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
// 导航到指定地图位姿。目标位姿 x/y 单位为米,theta 单位为弧度。
|
||||
rpc navigateToPose(AgvNavigateToPoseCommand.Request) returns (AgvNavigateToPoseCommand.Feedback);
|
||||
|
||||
rpc GetMapStatus(RobotStatusMapCommand.Request) returns (RobotStatusMapCommand.Feedback);
|
||||
// 导航到指定地图站点。
|
||||
rpc navigateToStation(AgvNavigateToStationCommand.Request) returns (AgvNavigateToStationCommand.Feedback);
|
||||
|
||||
// 按显式站点路径执行导航。
|
||||
rpc followPath(AgvFollowPathCommand.Request) returns (AgvFollowPathCommand.Feedback);
|
||||
|
||||
rpc RobotConfigUploadMap(RobotConfigUploadMapCommand.Request) returns (RobotConfigUploadMapCommand.Feedback);
|
||||
// 暂停当前导航任务。
|
||||
rpc pauseNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
// 抢占机器人控制权
|
||||
rpc RobotConfigLock(RobotConfigLockCommand.Request) returns (RobotConfigLockCommand.Feedback);
|
||||
// 恢复已暂停的导航任务。
|
||||
rpc resumeNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
// 查询当前控制权所有者
|
||||
rpc GetCurrentLockStatus(RobotStatusCurrentLockCommand.Request) returns (RobotStatusCurrentLockCommand.Feedback);
|
||||
// 取消当前导航任务。
|
||||
rpc cancelNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
// 开环速度运动控制
|
||||
rpc RobotMotionControl(RobotMotionControlCommand.Request) returns (RobotMotionControlCommand.Feedback);
|
||||
// 下发底盘速度控制指令。vx/vy 单位为米/秒,wz 单位为弧度/秒。
|
||||
rpc setVelocity(AgvSetVelocityCommand.Request) returns (AgvSetVelocityCommand.Feedback);
|
||||
|
||||
// 切换载入地图 命令码2022
|
||||
rpc RobotLoadMap(RobotLoadMapCommand.Request) returns (RobotLoadMapCommand.Feedback);
|
||||
// 停止底盘速度控制。该接口不等价于取消导航任务。
|
||||
rpc stopVelocityControl(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
|
||||
// 查询 AGV 控制器可用地图名称列表。
|
||||
rpc listMaps(AgvListMapsCommand.Request) returns (AgvListMapsCommand.Feedback);
|
||||
|
||||
// 查询地图载入状态 命令码1022
|
||||
rpc QueryLoadMapStatus(RobotQueryLoadMapStatusCommand.Request) returns (RobotQueryLoadMapStatusCommand.Feedback);
|
||||
// 查询当前地图中的站点列表。
|
||||
rpc listStations(AgvListStationsCommand.Request) returns (AgvListStationsCommand.Feedback);
|
||||
|
||||
// 切换当前使用地图。
|
||||
rpc switchMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
|
||||
|
||||
// 查询当前地图站点列表 命令码1301
|
||||
rpc QueryStationList(QueryStationListCommand.Request) returns (QueryStationListCommand.Feedback);
|
||||
// 上传地图内容到 AGV 控制器。地图内容字段由适配器解释。
|
||||
rpc uploadMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
|
||||
|
||||
// 下载指定地图内容。
|
||||
rpc downloadMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
|
||||
|
||||
// 指定路径导航 命令码3066
|
||||
rpc RobotGoTargetList(RobotGoTargetListCommand.Request) returns (RobotGoTargetListCommand.Feedback);
|
||||
// 开始建图/扫图会话。请求只选择 2D、3D 或二者都要,
|
||||
// 控制器特有地图格式由 AGV 适配器内部转换。
|
||||
rpc startMapping(AgvStartMappingCommand.Request) returns (AgvStartMappingCommand.Feedback);
|
||||
|
||||
// 查询当前导航状态 1020 robot_status_task_req
|
||||
// 1020 查询当前实时导航 robot_status_task_req
|
||||
rpc RobotStatusTaskCurrent(RobotStatusTaskCurrentCommand.Request) returns (RobotStatusTaskCurrentCommand.Feedback);
|
||||
// 以服务端流方式发送统一地图。resume_token 为空时应先发送全量快照;
|
||||
// 后续是否发送增量由 AGV 适配器能力决定,并通过 update_type 标记。
|
||||
rpc streamMap(AgvMapStreamCommand.Request) returns (stream AgvMapStreamCommand.Feedback);
|
||||
|
||||
// 批量查询任务状态 1110 robot_status_task_status_package_req
|
||||
rpc RobotStatusTaskPackage(RobotStatusTaskPackageCommand.Request) returns (RobotStatusTaskPackageCommand.Feedback);
|
||||
|
||||
|
||||
// 3051 单点站点自动规划导航 robot_task_gotarget_req 0x0BEB
|
||||
rpc RobotGoTarget(RobotGoTargetCommand.Request) returns (RobotGoTargetCommand.Feedback);
|
||||
|
||||
|
||||
|
||||
// 0x07D0 停止开环运动
|
||||
rpc RobotControlStop(RobotControlStopCommand.Request) returns (RobotControlStopCommand.Feedback);
|
||||
|
||||
// 暂停当前导航任务 命令码 3001 (0x0BB9)
|
||||
rpc RobotTaskPause(RobotTaskPauseCommand.Request) returns (RobotTaskPauseCommand.Feedback);
|
||||
// 继续当前导航任务 命令码 3002 (0x0BBA)
|
||||
rpc RobotTaskResume(RobotTaskResumeCommand.Request) returns (RobotTaskResumeCommand.Feedback);
|
||||
// 取消当前导航任务 命令码 3003 (0x0BBB)
|
||||
rpc RobotTaskCancel(RobotTaskCancelCommand.Request) returns (RobotTaskCancelCommand.Feedback);
|
||||
}
|
||||
// 停止当前建图/扫图会话。
|
||||
rpc stopMapping(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
}
|
||||
|
||||
310
server/proto/cmvr/msgs/agv.proto
Normal file
310
server/proto/cmvr/msgs/agv.proto
Normal file
@ -0,0 +1,310 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cmvr.msgs;
|
||||
|
||||
// AGV 在地图平面坐标系中的二维位姿。
|
||||
message AgvPose2d {
|
||||
// X 坐标,单位:米。
|
||||
double x = 1;
|
||||
// Y 坐标,单位:米。
|
||||
double y = 2;
|
||||
// 航向角,单位:弧度,逆时针为正。
|
||||
double theta = 3;
|
||||
}
|
||||
|
||||
// AGV 车体坐标系下的平面速度。
|
||||
message AgvVelocity {
|
||||
// 车体 X 方向线速度,单位:米/秒。
|
||||
double vx = 1;
|
||||
// 车体 Y 方向线速度,单位:米/秒。
|
||||
double vy = 2;
|
||||
// 绕 Z 轴角速度,单位:弧度/秒。
|
||||
double wz = 3;
|
||||
}
|
||||
|
||||
// AGV 电池状态。
|
||||
message AgvBatteryState {
|
||||
// 电量比例,范围:[0, 1],例如 0.8 表示 80%。
|
||||
double percentage = 1;
|
||||
// 电池电压,单位:伏特。
|
||||
double voltage = 2;
|
||||
// 电池电流,单位:安培;正负号含义由具体 AGV 适配器保持一致。
|
||||
double current = 3;
|
||||
// 电池温度,单位:摄氏度。
|
||||
double temperature = 4;
|
||||
// 是否正在充电。
|
||||
bool charging = 5;
|
||||
}
|
||||
|
||||
// 导航任务的通用运动约束和执行选项。
|
||||
message AgvMotionOptions {
|
||||
// 最大线速度,单位:米/秒;0 表示使用 AGV 默认值。
|
||||
double max_speed = 1;
|
||||
// 最大角速度,单位:弧度/秒;0 表示使用 AGV 默认值。
|
||||
double max_angular_speed = 2;
|
||||
// 最大线加速度,单位:米/秒^2;0 表示使用 AGV 默认值。
|
||||
double max_acceleration = 3;
|
||||
// 最大角加速度,单位:弧度/秒^2;0 表示使用 AGV 默认值。
|
||||
double max_angular_acceleration = 4;
|
||||
// 到达目标点的距离容差,单位:米;0 表示使用 AGV 默认值。
|
||||
double reach_distance = 5;
|
||||
// 到达目标角度的角度容差,单位:弧度;0 表示使用 AGV 默认值。
|
||||
double reach_angle = 6;
|
||||
// 速度比例,范围通常为 [0, 1];1 表示不降速。
|
||||
double speed_ratio = 7;
|
||||
// 是否异步执行;true 表示下发任务后立即返回。
|
||||
bool asynchronous = 8;
|
||||
}
|
||||
|
||||
// AGV 适配器扩展参数。用于传递厂商或控制器特有的参数。
|
||||
message AgvAdapterParams {
|
||||
// 参数键值表。键和值都使用字符串,具体含义由 AGV 适配器解释。
|
||||
map<string, string> values = 1;
|
||||
}
|
||||
|
||||
// AGV 当前运行状态快照。
|
||||
message AgvRuntimeState {
|
||||
// 状态采样时间,Unix 时间戳,单位:秒。
|
||||
double timestamp = 1;
|
||||
// 运行模式,取值对应服务端内部 AgvMode 枚举的整数值。
|
||||
int32 mode = 2;
|
||||
// 是否已连接 AGV 控制器。
|
||||
bool connected = 3;
|
||||
// 是否已定位成功。
|
||||
bool localized = 4;
|
||||
// AGV 是否处于运动状态。
|
||||
bool moving = 5;
|
||||
// AGV 是否处于故障状态。
|
||||
bool fault = 6;
|
||||
// AGV 是否处于急停状态。
|
||||
bool emergency_stopped = 7;
|
||||
// 当前地图坐标系下的二维位姿。
|
||||
AgvPose2d pose = 8;
|
||||
// 当前车体速度。
|
||||
AgvVelocity velocity = 9;
|
||||
// 当前电池状态。
|
||||
AgvBatteryState battery = 10;
|
||||
// 当前加载地图名称;为空表示未知或控制器未返回。
|
||||
string current_map = 11;
|
||||
// 当前或最近站点 id;为空表示未知或当前不在站点附近。
|
||||
string current_station = 12;
|
||||
// 最近一次错误信息;为空表示无错误或未知。
|
||||
string last_error = 13;
|
||||
}
|
||||
|
||||
// 地图中的站点信息。
|
||||
message AgvStation {
|
||||
// 站点唯一 id。
|
||||
string id = 1;
|
||||
// 站点类型;具体枚举由地图或控制器定义。
|
||||
string type = 2;
|
||||
// 站点在地图坐标系下的二维位姿。
|
||||
AgvPose2d pose = 3;
|
||||
// 站点描述或备注。
|
||||
string description = 4;
|
||||
}
|
||||
|
||||
// 显式站点路径中的一段路径。
|
||||
message AgvPathSegment {
|
||||
// 起点站点 id。
|
||||
string source_station = 1;
|
||||
// 目标站点 id。
|
||||
string target_station = 2;
|
||||
}
|
||||
|
||||
// 地图维度请求。这里只描述上位机想要 2D、3D 还是二者都要,
|
||||
// 不用于指定厂商文件格式;厂商格式必须在 AGV 适配器内部转换。
|
||||
enum AgvMapDimension {
|
||||
// 未指定。服务端应选择最有用的默认地图,通常是当前加载地图。
|
||||
AGV_MAP_DIMENSION_UNSPECIFIED = 0;
|
||||
// 只请求统一 2D 地图。
|
||||
AGV_MAP_2D = 1;
|
||||
// 只请求统一 3D 地图。
|
||||
AGV_MAP_3D = 2;
|
||||
// 在同一个流中请求统一 2D 和统一 3D 地图。
|
||||
AGV_MAP_2D_AND_3D = 3;
|
||||
}
|
||||
|
||||
// 地图流更新类型。上位机应根据该字段判断是全量、增量还是缓存重置。
|
||||
enum AgvMapUpdateType {
|
||||
// 未指定。
|
||||
AGV_MAP_UPDATE_UNSPECIFIED = 0;
|
||||
// 全量地图快照。首次请求或无法增量续传时应发送该类型。
|
||||
AGV_MAP_UPDATE_SNAPSHOT = 1;
|
||||
// 全量快照之后的增量更新。仅当适配器能够可靠生成差异时发送。
|
||||
AGV_MAP_UPDATE_INCREMENTAL = 2;
|
||||
// 上位机本地缓存已失效,应丢弃缓存并等待后续全量快照。
|
||||
AGV_MAP_UPDATE_RESET = 3;
|
||||
}
|
||||
|
||||
// 2D/3D 地图共用的语义对象类型。
|
||||
enum AgvMapObjectType {
|
||||
// 未指定。
|
||||
AGV_MAP_OBJECT_UNSPECIFIED = 0;
|
||||
// 导航站点或路径点。
|
||||
AGV_MAP_OBJECT_STATION = 1;
|
||||
// 路径线、禁行线、引导线等线对象。
|
||||
AGV_MAP_OBJECT_LINE = 2;
|
||||
// 多边形区域,例如禁行区、限速区、作业区。
|
||||
AGV_MAP_OBJECT_AREA = 3;
|
||||
// 二维码、天码或其他标签地标。
|
||||
AGV_MAP_OBJECT_QR_TAG = 4;
|
||||
// 反光板或反光柱地标。
|
||||
AGV_MAP_OBJECT_REFLECTOR = 5;
|
||||
// 库位、货位或储位。
|
||||
AGV_MAP_OBJECT_BIN_LOCATION = 6;
|
||||
// 门、电梯、充电桩等外部设备。
|
||||
AGV_MAP_OBJECT_EXTERNAL_DEVICE = 7;
|
||||
}
|
||||
|
||||
// 地图坐标系下的三维点。单位:米。
|
||||
message AgvMapPoint3D {
|
||||
// X 坐标,单位:米。
|
||||
double x = 1;
|
||||
// Y 坐标,单位:米。
|
||||
double y = 2;
|
||||
// Z 坐标,单位:米;纯 2D 几何可置为 0。
|
||||
double z = 3;
|
||||
}
|
||||
|
||||
// 统一 2D 地图。栅格数据按行优先排列:index = y * width + x。
|
||||
message AgvUnifiedMap2D {
|
||||
// 坐标系名称,例如 "map"。
|
||||
string frame_id = 1;
|
||||
// 地图采样或更新时间,Unix 时间戳,单位:秒。
|
||||
double timestamp = 2;
|
||||
// 栅格分辨率,单位:米/格。
|
||||
double resolution = 3;
|
||||
// 栅格宽度,单位:格。
|
||||
uint32 width = 4;
|
||||
// 栅格高度,单位:格。
|
||||
uint32 height = 5;
|
||||
// 栅格 (0, 0) 在世界/地图坐标系下的位姿;x/y 单位:米,theta 单位:弧度。
|
||||
AgvPose2d origin = 6;
|
||||
// 占据值:-1 表示未知,0 表示空闲,100 表示占据。
|
||||
repeated int32 data = 7;
|
||||
// 地图中的统一语义对象,例如站点、线、区域、标签、反光板等。
|
||||
repeated AgvMapObject objects = 8;
|
||||
}
|
||||
|
||||
// 统一 3D 点样本。坐标单位:米。
|
||||
message AgvMapPointSample3D {
|
||||
// X 坐标,单位:米。
|
||||
double x = 1;
|
||||
// Y 坐标,单位:米。
|
||||
double y = 2;
|
||||
// Z 坐标,单位:米。
|
||||
double z = 3;
|
||||
// 激光强度;无强度信息时置为 0。
|
||||
float intensity = 4;
|
||||
// 激光雷达线束/通道编号;无该信息时置为 0。
|
||||
uint32 ring = 5;
|
||||
// 相对地图时间戳的时间偏移,单位:秒;无该信息时置为 0。
|
||||
double time_offset = 6;
|
||||
}
|
||||
|
||||
// 统一 3D 体素。体素索引基于 AgvUnifiedMap3D.voxel_resolution。
|
||||
message AgvMapVoxel3D {
|
||||
// 体素 X 索引。
|
||||
int32 x = 1;
|
||||
// 体素 Y 索引。
|
||||
int32 y = 2;
|
||||
// 体素 Z 索引。
|
||||
int32 z = 3;
|
||||
// 占据概率,范围:[0, 1];未知时置为 -1。
|
||||
float probability = 4;
|
||||
}
|
||||
|
||||
// 统一 3D 平面特征。平面方程:
|
||||
// normal.x * x + normal.y * y + normal.z * z + d = 0。
|
||||
message AgvMapPlane3D {
|
||||
// 平面中心点,单位:米。
|
||||
AgvMapPoint3D center = 1;
|
||||
// 平面单位法向量。
|
||||
AgvMapPoint3D normal = 2;
|
||||
// 平面方程偏移量,单位:米。
|
||||
double d = 3;
|
||||
// 平面特征近似半径,单位:米。
|
||||
double radius = 4;
|
||||
}
|
||||
|
||||
// 统一语义对象。几何点均使用地图坐标系,单位:米。
|
||||
message AgvMapObject {
|
||||
// 对象稳定 id 或名称。
|
||||
string id = 1;
|
||||
// 对象类型。
|
||||
AgvMapObjectType type = 2;
|
||||
// 对象几何点。点对象使用 1 个点,线对象使用多个点,区域对象使用多边形顶点。
|
||||
repeated AgvMapPoint3D points = 3;
|
||||
// 朝向角,单位:弧度;不适用时置为 0。
|
||||
double heading = 4;
|
||||
// 额外归一化属性。为兼容不同 AGV,属性值统一使用字符串。
|
||||
map<string, string> properties = 5;
|
||||
}
|
||||
|
||||
// 统一 3D 地图。虽然内部包含点、体素、平面和语义对象,
|
||||
// 但对上位机来说它仍然是唯一的 3D 地图格式。
|
||||
message AgvUnifiedMap3D {
|
||||
// 坐标系名称,例如 "map"。
|
||||
string frame_id = 1;
|
||||
// 地图采样或更新时间,Unix 时间戳,单位:秒。
|
||||
double timestamp = 2;
|
||||
// 体素分辨率,单位:米/体素;没有体素数据时置为 0。
|
||||
double voxel_resolution = 3;
|
||||
// 统一点云点样本。
|
||||
repeated AgvMapPointSample3D points = 4;
|
||||
// 统一占据体素。
|
||||
repeated AgvMapVoxel3D voxels = 5;
|
||||
// 统一平面特征。
|
||||
repeated AgvMapPlane3D planes = 6;
|
||||
// 统一 3D 语义对象,例如站点、区域、标签、地标等。
|
||||
repeated AgvMapObject objects = 7;
|
||||
}
|
||||
|
||||
// 地图流中的单条更新。AGV 适配器必须先把厂商地图转换成 map_2d 或 map_3d,
|
||||
// 再通过该消息发送给上位机。
|
||||
message AgvUnifiedMapUpdate {
|
||||
// 实际发送的地图 id 或名称。请求 map_name 为空时,AGV 可选择当前或最近地图。
|
||||
string map_id = 1;
|
||||
// 建图或地图流会话 id。
|
||||
string session_id = 2;
|
||||
// 会话内单调递增序号,从 0 或 1 开始均可,但同一会话内必须保持递增。
|
||||
uint64 sequence = 3;
|
||||
// 用于断点续传或增量订阅的不透明令牌。
|
||||
string resume_token = 4;
|
||||
// 本条更新的数据维度。
|
||||
AgvMapDimension dimension = 5;
|
||||
// 本条更新是全量、增量还是缓存重置。
|
||||
AgvMapUpdateType update_type = 6;
|
||||
// 坐标系名称,例如 "map"。
|
||||
string frame_id = 7;
|
||||
// 本条更新产生时间,Unix 时间戳,单位:秒。
|
||||
double timestamp = 8;
|
||||
// 是否为全量快照的第一条消息。
|
||||
bool snapshot_begin = 9;
|
||||
// 是否为全量快照的最后一条消息。
|
||||
bool snapshot_end = 10;
|
||||
// 大地图分片发送时的分片序号,从 0 开始。
|
||||
uint32 chunk_index = 11;
|
||||
// 大地图分片总数;0 表示未知或连续流。
|
||||
uint32 chunk_count = 12;
|
||||
|
||||
oneof payload {
|
||||
// 统一 2D 地图或 2D 地图增量。
|
||||
AgvUnifiedMap2D map_2d = 20;
|
||||
// 统一 3D 地图或 3D 地图增量。
|
||||
AgvUnifiedMap3D map_3d = 21;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前导航任务状态。
|
||||
message AgvNavigationStatus {
|
||||
// 导航状态,取值对应服务端内部 AgvTaskState 枚举的整数值。
|
||||
int32 state = 1;
|
||||
// 导航任务类型,取值对应服务端内部 AgvTaskType 枚举的整数值。
|
||||
int32 type = 2;
|
||||
// 当前任务进度,范围:[0, 1];未知时置为 0。
|
||||
double progress = 3;
|
||||
// 状态描述或错误信息。
|
||||
string message = 4;
|
||||
}
|
||||
@ -5,8 +5,8 @@ import request from '@/utils/request'
|
||||
* @param {Object} params
|
||||
* @param {string} [params.deviceId] - 设备ID,默认 agv_src1100
|
||||
*/
|
||||
export function getCurrentMapName(params) {
|
||||
return request('/api/agv/getCurrentMapName', params, 'POST')
|
||||
export function getRuntimeState(params) {
|
||||
return request('/api/agv/getRuntimeState', params, 'POST')
|
||||
}
|
||||
|
||||
export function getCurrentMap(params) {
|
||||
|
||||
@ -3,12 +3,18 @@ import { defineStore } from 'pinia'
|
||||
// 唯一id:robot
|
||||
export const useRobotStore = defineStore('robot', {
|
||||
state: () => ({
|
||||
ip: '192.168.0.109:50052',
|
||||
agvDeviceId:'agv_src2200',
|
||||
ip: '192.168.1.110:50052',
|
||||
agvDeviceId:'src1100',
|
||||
almDeviceId: 'aubo_arm',
|
||||
position: { x: 0, y: 0, angle: 0 },
|
||||
battery: 100,
|
||||
status: 'idle'
|
||||
position: { x: 0, y: 0, theta: 0 },
|
||||
battery: {
|
||||
percentage: 0.99,
|
||||
voltage: -0,
|
||||
current: -0,
|
||||
temperature: -0,
|
||||
charging: false
|
||||
},
|
||||
current_map: ''
|
||||
}),
|
||||
getters: {
|
||||
// 获取机器人坐标
|
||||
@ -30,14 +36,15 @@ export const useRobotStore = defineStore('robot', {
|
||||
this.almDeviceId = str
|
||||
},
|
||||
// 更新机器人位置
|
||||
setPosition(x, y, angle) {
|
||||
this.position.x = x
|
||||
this.position.y = y
|
||||
this.position.angle = angle
|
||||
setPosition(data = {}) {
|
||||
this.position = data
|
||||
},
|
||||
// 修改状态
|
||||
setStatus(val) {
|
||||
this.status = val
|
||||
setBattery(data = {}) {
|
||||
this.battery = data
|
||||
},
|
||||
setCurrentMap(str = '') {
|
||||
this.current_map = str
|
||||
},
|
||||
// 重置仓库
|
||||
resetRobot() {
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getCurrentMapName } from '@/api/agv.js'
|
||||
import { getRuntimeState } from '@/api/agv.js'
|
||||
import { useRobotStore } from '@/stores/robot'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
@ -60,17 +60,17 @@ const testConnect = async () => {
|
||||
robotStore.setIp(ruleForm.value.ip)
|
||||
robotStore.setAgvDeviceId(ruleForm.value.agvDeviceId)
|
||||
robotStore.setAlmDeviceId(ruleForm.value.almDeviceId)
|
||||
const result = await getCurrentMapName({
|
||||
const result = await getRuntimeState({
|
||||
ip: robotStore.ip,
|
||||
deviceId: ruleForm.value.agvDeviceId
|
||||
})
|
||||
loading.value = false
|
||||
if (result.code === 200) {
|
||||
robotStore.setPosition(result.data.pose)
|
||||
robotStore.setBattery(result.data.battery)
|
||||
robotStore.setCurrentMap(result.data.current_map)
|
||||
router.push({
|
||||
path: '/home',
|
||||
query: {
|
||||
mapName: result.data.current_map
|
||||
}
|
||||
path: '/home'
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(result?.message || '连接服务错误')
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<Chassis :mapName="mapName" v-if="activePage === 'chassis'" />
|
||||
<Chassis v-if="activePage === 'chassis'" />
|
||||
<MechanicalArm v-if="activePage === 'mechanicalArm'" />
|
||||
<Camera v-if="activePage === 'camera'" />
|
||||
</el-main>
|
||||
@ -34,13 +34,12 @@
|
||||
import Chassis from './chassis/Chassis.vue';
|
||||
import MechanicalArm from './arm/MechanicalArm.vue';
|
||||
import Camera from './camera/index.vue'
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import autofit from 'autofit.js'
|
||||
autofit.init()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const currentTime = ref('')
|
||||
const weekText = ref('')
|
||||
@ -79,15 +78,12 @@ const back = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const mapName = ref('')
|
||||
|
||||
let clockTimer, binaryTimer;
|
||||
onMounted(() => {
|
||||
updateClock();
|
||||
clockTimer = setInterval(updateClock, 1000);
|
||||
animateBinary()
|
||||
binaryTimer = setInterval(animateBinary, 400);
|
||||
mapName.value = route.query.mapName
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
<div class="map-body">
|
||||
<!-- <canvas ref="mapCanvas" /> -->
|
||||
<MapCanvas :mapName="mapName" :isDragging="!isDragging" />
|
||||
<MapCanvas :mapName="robotStore.current_map" :isDragging="!isDragging" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -134,12 +134,12 @@ import { useRobotStore } from '@/stores/robot'
|
||||
import { moveRobot, stopRobot } from '@/api/agv.js'
|
||||
import MapCanvas from './MapCanvas.vue'
|
||||
|
||||
const props = defineProps({
|
||||
mapName: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
// const props = defineProps({
|
||||
// mapName: {
|
||||
// type: String,
|
||||
// default: ''
|
||||
// }
|
||||
// })
|
||||
|
||||
// const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID
|
||||
|
||||
@ -168,8 +168,7 @@ const isDragging = ref(false)
|
||||
const moveRobotData = ref({
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
w: 0,
|
||||
duration: -1
|
||||
vz: 0
|
||||
})
|
||||
|
||||
/* long-press stubs */
|
||||
@ -183,10 +182,9 @@ const onPress = async (_dir) => {
|
||||
} else {
|
||||
moveRobotData.value.vy = -maxSpeed.value
|
||||
}
|
||||
moveRobotData.value.w = rotationAngle.value
|
||||
moveRobotData.value.vz = rotationAngle.value
|
||||
const res = await moveRobot({
|
||||
ip: robotStore.ip,
|
||||
// deviceId: VITE_AGV_DEVICE_ID,
|
||||
deviceId: robotStore.agvDeviceId,
|
||||
...moveRobotData.value
|
||||
})
|
||||
|
||||
@ -386,12 +386,11 @@ const loadSourceMap = async (mapName) => {
|
||||
})
|
||||
if (res.code === 200) {
|
||||
try {
|
||||
const rawJsonObject = JSON.parse(res.data.map_content); // 解析 JSON
|
||||
const rawJsonObject = JSON.parse(res.data); // 解析 JSON
|
||||
const parsedMap = parseSmapJson(rawJsonObject); // 转换为内部结构
|
||||
await loadMapIntoApplication(parsedMap); // 加载到应用
|
||||
setTimeout(() => {
|
||||
bindEvent()
|
||||
getRobot()
|
||||
}, 300)
|
||||
} catch (parseError) {
|
||||
ElMessage.error('Parse error: ' + parseError.message); // 显示错误信息
|
||||
@ -417,31 +416,21 @@ const moveRobot = (robot, dx, dy, dtheta = 0) => {
|
||||
robot.angle += dtheta;
|
||||
}
|
||||
|
||||
const getRobot = async () => {
|
||||
const res = await getRobotState({
|
||||
ip: robotStore.ip,
|
||||
// deviceId: VITE_AGV_DEVICE_ID,
|
||||
deviceId: robotStore.agvDeviceId,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
robotStore.setPosition(res.data.x, res.data.y, res.data.angle)
|
||||
robotStore.battery = res.data.batteryLevel
|
||||
addRobot(robotStore.ip, robotStore.ip, res.data.x, res.data.y, res.data.angle, '#00D4FF')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
watch(() => props.mapName,
|
||||
(newVal) => {
|
||||
if (props.mapName) {
|
||||
loadSourceMap(props.mapName)
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
await loadSourceMap(newVal)
|
||||
addRobot(robotStore.ip, robotStore.ip, robotStore.position.x, robotStore.position.y, robotStore.position.theta, '#00D4FF')
|
||||
requestAnimationFrame(renderFrame);
|
||||
window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化
|
||||
resizeCanvasToContainer(); // 初始调用一次
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user