feat: 机械臂

This commit is contained in:
zhanghao 2026-07-10 13:44:07 +08:00
parent 98031a0dcc
commit 0e12299816
11 changed files with 539 additions and 995 deletions

2
.env
View File

@ -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

View File

@ -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

View File

@ -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);
// / 2D3D
// 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);
}

View 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;
// 线/^20 使 AGV
double max_acceleration = 3;
// /^20 使 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;
}
// 2D3D
// 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;
}

View File

@ -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) {

View File

@ -3,12 +3,18 @@ import { defineStore } from 'pinia'
// 唯一idrobot
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() {

View File

@ -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 || '连接服务错误')

View File

@ -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(() => {

View File

@ -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
})

View File

@ -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
}
)