feat: 机械臂联调
This commit is contained in:
parent
2d12fc3f89
commit
e4058c6475
3
.env
3
.env
@ -1,3 +1,4 @@
|
||||
VITE_DEVICE_ID = 'agv_src2200'
|
||||
VITE_AGV_DEVICE_ID = 'agv_src2200'
|
||||
VITE_ARM_DEVICE_ID = 'huayan_arm'
|
||||
|
||||
VITE_SERVICE_PORT = 5175
|
||||
@ -6,7 +6,6 @@ services:
|
||||
container_name: inspection-host-computer
|
||||
ports:
|
||||
- "5175:5175" # 映射端口
|
||||
- "5173:5173" # 映射端口
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- VITE_SERVICE_PORT=5175 # 传给 Express 的环境变量
|
||||
|
||||
56
server/grpcClient.js
Normal file
56
server/grpcClient.js
Normal file
@ -0,0 +1,56 @@
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import grpc from '@grpc/grpc-js'
|
||||
import protoLoader from '@grpc/proto-loader'
|
||||
|
||||
export const __filename = fileURLToPath(import.meta.url)
|
||||
export const __dirname = path.dirname(__filename)
|
||||
|
||||
|
||||
// ========== 加载 Proto ==========
|
||||
const PROTO_ROOT = path.resolve(__dirname, './proto') // 指向 server/proto/
|
||||
const AGV_PROTO_ENTRY = path.join(PROTO_ROOT, 'cmvr/api/agv_service.proto')
|
||||
const ARM_PROTO_ENTRY = path.join(PROTO_ROOT, 'cmvr/api/arm_service.proto')
|
||||
|
||||
const agvPackageDefinition = protoLoader.loadSync(
|
||||
AGV_PROTO_ENTRY,
|
||||
{
|
||||
includeDirs: [PROTO_ROOT , path.join(PROTO_ROOT, 'cmvr/api')], // 关键:让加载器从 PROTO_ROOT 查找 import
|
||||
keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
)
|
||||
|
||||
const armPackageDefinition = protoLoader.loadSync(
|
||||
ARM_PROTO_ENTRY,
|
||||
{
|
||||
includeDirs: [PROTO_ROOT , path.join(PROTO_ROOT, 'cmvr/api')], // 关键:让加载器从 PROTO_ROOT 查找 import
|
||||
keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
)
|
||||
|
||||
const agvProto = grpc.loadPackageDefinition(agvPackageDefinition).cmvr.api
|
||||
const armProto = grpc.loadPackageDefinition(armPackageDefinition).cmvr.api
|
||||
|
||||
export const createAGVGrpcClient = (address) => {
|
||||
const grpcClient = new agvProto.AgvService(
|
||||
address,
|
||||
grpc.credentials.createInsecure()
|
||||
)
|
||||
return grpcClient
|
||||
}
|
||||
|
||||
export const createARMGrpcClient = (address) => {
|
||||
const grpcClient = new armProto.ArmService(
|
||||
address,
|
||||
grpc.credentials.createInsecure()
|
||||
)
|
||||
return grpcClient
|
||||
}
|
||||
216
server/index.js
216
server/index.js
@ -1,46 +1,16 @@
|
||||
// server/index.js
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import grpc from '@grpc/grpc-js'
|
||||
import protoLoader from '@grpc/proto-loader'
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import 'dotenv/config';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
import 'dotenv/config';
|
||||
import { createAGVGrpcClient, createARMGrpcClient, __dirname } from './grpcClient.js';
|
||||
|
||||
const app = express()
|
||||
app.use(express.static(path.join(__dirname, '../dist')));
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
// ========== 加载 Proto ==========
|
||||
const PROTO_ROOT = path.resolve(__dirname, './proto') // 指向 server/proto/
|
||||
const PROTO_ENTRY = path.join(PROTO_ROOT, 'cmvr/api/agv_service.proto')
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_ENTRY,
|
||||
{
|
||||
includeDirs: [PROTO_ROOT , path.join(PROTO_ROOT, 'cmvr/api')], // 关键:让加载器从 PROTO_ROOT 查找 import
|
||||
keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
)
|
||||
|
||||
const cmvrProto = grpc.loadPackageDefinition(packageDefinition).cmvr.api
|
||||
|
||||
const createGrpcClient = (address) => {
|
||||
const grpcClient = new cmvrProto.AgvService(
|
||||
address,
|
||||
grpc.credentials.createInsecure()
|
||||
)
|
||||
return grpcClient
|
||||
}
|
||||
|
||||
// 获取机器人当前地图名称
|
||||
app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
const request = {
|
||||
@ -49,7 +19,7 @@ app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getMapStatus(request, (err, res) => {
|
||||
@ -76,7 +46,7 @@ app.post('/api/agv/getCurrentMapName', async (req, res) => {
|
||||
|
||||
// 获取机器人当前地图名称
|
||||
app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
const grpcClient = createGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.device_id || 'agv_src1100'
|
||||
@ -109,6 +79,7 @@ app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取机器人状态
|
||||
app.post('/api/agv/getRobotState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
@ -116,7 +87,7 @@ app.post('/api/agv/getRobotState', async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
grpcClient.GetRobotLocation(request, (err, res) => {
|
||||
@ -168,8 +139,9 @@ app.post('/api/agv/getRobotState', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 机器人运动控制
|
||||
app.post('/api/agv/moveRobot', async (req, res) => {
|
||||
const grpcClient = createGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.device_id || 'agv_src1100'
|
||||
@ -208,8 +180,9 @@ app.post('/api/agv/moveRobot', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 机器人停止运动
|
||||
app.post('/api/agv/stopRobot', async (req, res) => {
|
||||
const grpcClient = createGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.device_id || 'agv_src1100'
|
||||
@ -242,6 +215,175 @@ app.post('/api/agv/stopRobot', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取机械臂末端位姿
|
||||
app.post('/api/arm/getPose', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getPose(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.pose
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (resp.code === 200) {
|
||||
res.json(resp)
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
// 获取机械臂节点状态
|
||||
app.post('/api/arm/getJointState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getJointState(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.state
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (resp.code === 200) {
|
||||
res.json(resp)
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
// 机械臂关节运动
|
||||
app.post('/api/arm/speedJ', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
},
|
||||
velocity: {
|
||||
velocity: req.body.velocities
|
||||
},
|
||||
acceleration: req.body.acceleration,
|
||||
duration: req.body.duration
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.speedJ(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (resp.code === 200) {
|
||||
res.json(resp)
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/arm/speedL', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
},
|
||||
velocity: req.body.velocity,
|
||||
acceleration: req.body.acceleration,
|
||||
duration: req.body.duration
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.speedL(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (resp.code === 200) {
|
||||
res.json(resp)
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
// 机械臂停止运动
|
||||
app.post('/api/arm/stopMotion', async (req, res) => {
|
||||
const request = {
|
||||
// header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm',
|
||||
// },
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.stopMotion(request, (err, res) => {
|
||||
if (err) {
|
||||
resolve({
|
||||
code: 500,
|
||||
data: `grpc服务端错误`
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
code: 200,
|
||||
data: res.success
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (resp.code === 200) {
|
||||
res.json(resp)
|
||||
} else {
|
||||
res.status(500).json({ error: 'grpc服务端错误' })
|
||||
}
|
||||
})
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const PORT = process.env.VITE_SERVICE_PORT || 3000;
|
||||
|
||||
|
||||
185
server/proto/cmvr/api/arm_command.proto
Normal file
185
server/proto/cmvr/api/arm_command.proto
Normal file
@ -0,0 +1,185 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cmvr.api;
|
||||
|
||||
import "cmvr/api/common.proto";
|
||||
|
||||
enum ArmFrameType {
|
||||
ARM_FRAME_BASE = 0;
|
||||
ARM_FRAME_TOOL = 1;
|
||||
ARM_FRAME_WORLD = 2;
|
||||
ARM_FRAME_USER = 3;
|
||||
}
|
||||
|
||||
message JointPositionCommand {
|
||||
repeated double position = 1;
|
||||
}
|
||||
|
||||
message JointVelocityCommand {
|
||||
repeated double velocity = 1;
|
||||
}
|
||||
|
||||
message MotionOptions {
|
||||
double velocity = 1;
|
||||
double acceleration = 2;
|
||||
double blend_radius = 3;
|
||||
double jerk = 4;
|
||||
repeated double joint_velocity_limits = 5;
|
||||
bool asynchronous = 6;
|
||||
}
|
||||
|
||||
message CartesianPose {
|
||||
double x = 1;
|
||||
double y = 2;
|
||||
double z = 3;
|
||||
double rx = 4;
|
||||
double ry = 5;
|
||||
double rz = 6;
|
||||
}
|
||||
|
||||
message CartesianVelocity {
|
||||
double vx = 1;
|
||||
double vy = 2;
|
||||
double vz = 3;
|
||||
double wx = 4;
|
||||
double wy = 5;
|
||||
double wz = 6;
|
||||
}
|
||||
|
||||
message TransformMatrix4x4 {
|
||||
double m00 = 1; double m01 = 2; double m02 = 3; double m03 = 4;
|
||||
double m10 = 5; double m11 = 6; double m12 = 7; double m13 = 8;
|
||||
double m20 = 9; double m21 = 10; double m22 = 11; double m23 = 12;
|
||||
double m30 = 13; double m31 = 14; double m32 = 15; double m33 = 16;
|
||||
}
|
||||
|
||||
message MoveJ {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
JointPositionCommand target = 2;
|
||||
MotionOptions options = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message MoveL {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
CartesianPose target = 2;
|
||||
MotionOptions options = 3;
|
||||
ArmFrameType frame = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message SpeedJ {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
JointVelocityCommand velocity = 2;
|
||||
double acceleration = 3;
|
||||
double duration = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message SpeedL {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
CartesianVelocity velocity = 2;
|
||||
double acceleration = 3;
|
||||
double duration = 4;
|
||||
ArmFrameType frame = 5;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ServoJ {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
JointPositionCommand target = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message JointState {
|
||||
repeated string name = 1;
|
||||
repeated double position = 2;
|
||||
repeated double velocity = 3;
|
||||
repeated double effort = 4;
|
||||
double timestamp = 5;
|
||||
}
|
||||
|
||||
message JointRequest {
|
||||
CommandHeader.Request header = 1;
|
||||
}
|
||||
|
||||
message JointResponse {
|
||||
CommandHeader.Feedback header = 1;
|
||||
JointState state = 2;
|
||||
}
|
||||
|
||||
message GetPose {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
string base_link = 2;
|
||||
string ee_link = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
CartesianPose pose = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message CalibrateZeroQ {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
string joint_name = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GetPoseMatrix {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
string base_link = 2;
|
||||
string ee_link = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
TransformMatrix4x4 matrix = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ComputeForwardKinematics {
|
||||
message Request {
|
||||
CommandHeader.Request header = 1;
|
||||
string base_link = 2;
|
||||
string ee_link = 3;
|
||||
JointPositionCommand joints = 4;
|
||||
}
|
||||
|
||||
message Response {
|
||||
CommandHeader.Feedback header = 1;
|
||||
TransformMatrix4x4 matrix = 2;
|
||||
}
|
||||
}
|
||||
22
server/proto/cmvr/api/arm_service.proto
Normal file
22
server/proto/cmvr/api/arm_service.proto
Normal file
@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cmvr.api;
|
||||
|
||||
import "cmvr/api/common.proto";
|
||||
import "cmvr/api/arm_command.proto";
|
||||
|
||||
service ArmService {
|
||||
rpc torqueOff(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
rpc torqueOn(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
rpc moveJ(MoveJ.Request) returns (MoveJ.Response);
|
||||
rpc moveL(MoveL.Request) returns (MoveL.Response);
|
||||
rpc speedJ(SpeedJ.Request) returns (SpeedJ.Response);
|
||||
rpc speedL(SpeedL.Request) returns (SpeedL.Response);
|
||||
rpc servoJ(ServoJ.Request) returns (ServoJ.Response);
|
||||
rpc stopMotion(CommandHeader.Request) returns (CommandHeader.Feedback);
|
||||
rpc getJointState(JointRequest) returns (JointResponse);
|
||||
rpc getPose(GetPose.Request) returns (GetPose.Response);
|
||||
rpc calibrateZeroQ(CalibrateZeroQ.Request) returns (CalibrateZeroQ.Response);
|
||||
rpc getPoseMatrix(GetPoseMatrix.Request) returns (GetPoseMatrix.Response);
|
||||
rpc computeForwardKinematics(ComputeForwardKinematics.Request) returns (ComputeForwardKinematics.Response);
|
||||
}
|
||||
@ -306,9 +306,6 @@ export class XacroAdapter {
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
static async loadFileFromMap(path, fileMap, workingPath) {
|
||||
console.log('path', path)
|
||||
console.log('fileMap', fileMap)
|
||||
console.log('workingPath', workingPath)
|
||||
// Clean path - remove leading slash if present
|
||||
let cleanPath = path;
|
||||
// if (cleanPath.startsWith('/')) {
|
||||
@ -318,7 +315,6 @@ export class XacroAdapter {
|
||||
// // Remove leading ./
|
||||
// cleanPath = cleanPath.replace(/^\.\//, '');
|
||||
cleanPath = cleanPath.split('///')[1];
|
||||
console.log('cleanPath', cleanPath)
|
||||
// Try different path combinations
|
||||
const possiblePaths = [
|
||||
cleanPath,
|
||||
@ -328,13 +324,10 @@ export class XacroAdapter {
|
||||
// Also try without the first directory component
|
||||
cleanPath.includes('/') ? cleanPath.substring(cleanPath.indexOf('/') + 1) : cleanPath,
|
||||
];
|
||||
console.log('possiblePaths', possiblePaths)
|
||||
// Try each path
|
||||
for (const tryPath of possiblePaths) {
|
||||
console.log('tryPath', tryPath)
|
||||
const file = fileMap.get(tryPath);
|
||||
if (file) {
|
||||
console.log(12345)
|
||||
const content = await file.text();
|
||||
return content;
|
||||
}
|
||||
|
||||
@ -1,13 +1,4 @@
|
||||
const fetchFn = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
|
||||
const res = await fetch(url, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 机器人前往目标站点
|
||||
@ -15,21 +6,21 @@ const fetchFn = async (url, params = {}, method = 'GET', headers = { 'Content-Ty
|
||||
* @param {string} [params.deviceId] - 设备ID,默认 agv_src1100
|
||||
*/
|
||||
export function getCurrentMapName(params) {
|
||||
return fetchFn('/api/agv/getCurrentMapName', params, 'POST')
|
||||
return request('/api/agv/getCurrentMapName', params, 'POST')
|
||||
}
|
||||
|
||||
export function getCurrentMap(params) {
|
||||
return fetchFn('/api/agv/getCurrentMap', params, 'POST')
|
||||
return request('/api/agv/getCurrentMap', params, 'POST')
|
||||
}
|
||||
|
||||
export function getRobotState(params) {
|
||||
return fetchFn('/api/agv/getRobotState', params, 'POST')
|
||||
return request('/api/agv/getRobotState', params, 'POST')
|
||||
}
|
||||
|
||||
export function moveRobot(params) {
|
||||
return fetchFn('/api/agv/moveRobot', params, 'POST')
|
||||
return request('/api/agv/moveRobot', params, 'POST')
|
||||
}
|
||||
|
||||
export function stopRobot(params) {
|
||||
return fetchFn('/api/agv/stopRobot', params, 'POST')
|
||||
return request('/api/agv/stopRobot', params, 'POST')
|
||||
}
|
||||
29
src/api/arm.js
Normal file
29
src/api/arm.js
Normal file
@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getPoseApi(params) {
|
||||
return request('/api/arm/getPose', params, 'POST')
|
||||
}
|
||||
|
||||
export function getJointStateApi(params) {
|
||||
return request('/api/arm/getJointState', params, 'POST')
|
||||
}
|
||||
|
||||
export function speedLApi(data) {
|
||||
return request('/api/arm/speedL', data, 'POST')
|
||||
}
|
||||
|
||||
export function moveJApi(data) {
|
||||
return request('/api/arm/moveJ', data, 'POST')
|
||||
}
|
||||
|
||||
export function stopMotionApi(params) {
|
||||
return request('/api/arm/stopMotion', params, 'POST')
|
||||
}
|
||||
|
||||
export function torqueOnApi(params) {
|
||||
return request('/api/arm/torqueOn', params, 'POST')
|
||||
}
|
||||
|
||||
export function speedJApi(data) {
|
||||
return request('/api/arm/speedJ', data, 'POST')
|
||||
}
|
||||
12
src/utils/request.js
Normal file
12
src/utils/request.js
Normal file
@ -0,0 +1,12 @@
|
||||
const request = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
|
||||
const res = await fetch(url, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
export default request
|
||||
@ -28,8 +28,7 @@ const robotStore = useRobotStore()
|
||||
|
||||
const ruleFormRef = ref()
|
||||
const ruleForm = ref({
|
||||
ip: '',
|
||||
port: 80
|
||||
ip: ''
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
@ -39,22 +38,30 @@ const rules = ref({
|
||||
})
|
||||
|
||||
const testConnect = async () => {
|
||||
const valid = await ruleFormRef.value.validate();
|
||||
if (valid) {
|
||||
const result = await getCurrentMapName({
|
||||
ip: robotStore.ip,
|
||||
deviceId: VITE_DEVICE_ID,
|
||||
})
|
||||
if (result.code === 200) {
|
||||
robotStore.setIp(ruleForm.value.ip)
|
||||
// const valid = await ruleFormRef.value.validate();
|
||||
// if (valid) {
|
||||
// const result = await getCurrentMapName({
|
||||
// ip: robotStore.ip,
|
||||
// deviceId: VITE_AGV_DEVICE_ID,
|
||||
// })
|
||||
// if (result.code === 200) {
|
||||
// router.push({
|
||||
// path: '/home',
|
||||
// query: {
|
||||
// mapName: result.data.current_map
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
router.push({
|
||||
path: '/home',
|
||||
query: {
|
||||
mapName: result.data.current_map
|
||||
mapName: 'test'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -42,15 +42,27 @@
|
||||
<div class="title">关节控制</div>
|
||||
<div v-for="joint in jointControls" :key="joint.name" class="joint-item">
|
||||
<label :for="joint.name">{{ joint.name }}</label>
|
||||
<el-slider v-model="joint.value" :min="joint.min" :max="joint.max" :step="0.01" @input="updateJoint(joint.name, joint.value)"/>
|
||||
<span class="angle">{{ (joint.value * 180 / Math.PI).toFixed(1) }}°</span>
|
||||
<el-input v-model="joint.value" readonly>
|
||||
<template #prepend>
|
||||
<el-button @mousedown="updateJoint(joint.name, '-')" @mouseup="stop"
|
||||
@touchstart.prevent="updateJoint(joint.name, '-')" @touchend.prevent="stop">
|
||||
-
|
||||
</el-button>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @mousedown="updateJoint(joint.name, '+')" @mouseup="stop"
|
||||
@touchstart.prevent="updateJoint(joint.name, '+')" @touchend.prevent="stop">
|
||||
+
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="speed-container">
|
||||
<div class="label">速度:</div>
|
||||
<el-slider v-model="speed" :min="1" :max="100" />
|
||||
<div class="proportion">{{ speed }} %</div>
|
||||
<el-slider v-model="jointSpeed" :min="0.1" :step="0.1" :max="9" />
|
||||
<div class="proportion">{{ (jointSpeed / 9 * 100).toFixed(0) }} %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -76,11 +88,17 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">前后平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('x', '-', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('x', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('x', '+', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('x', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,11 +110,17 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">左右平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('y', '-', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('y', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('y', '+', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('y', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,16 +132,27 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">上下平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('z', '-', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('z', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('z', '+', 'end')" @mouseup="stop"
|
||||
@touchstart.prevent="move('z', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="speed-container">
|
||||
<div class="label">速度:</div>
|
||||
<el-slider v-model="endSpeed" :min="0.1" :step="0.1" :max="4" />
|
||||
<div class="proportion">{{ (endSpeed / 4 * 100).toFixed(0) }} %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controller">
|
||||
@ -129,11 +164,17 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">前后平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('x', '-', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('x', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('x', '+', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('x', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -145,11 +186,17 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">左右平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('y', '-', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('y', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('y', '+', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('y', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,16 +208,27 @@
|
||||
<div class="axis-slider-wrap">
|
||||
<div class="axis-name">上下平移</div>
|
||||
<div>
|
||||
<el-button type="primary" circle>
|
||||
<el-icon size="24"><Remove /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('z', '-', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('z', '-')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="primary" circle >
|
||||
<el-icon size="24"><CirclePlus /></el-icon>
|
||||
<el-button type="primary" circle @mousedown="move('z', '+', 'posture')" @mouseup="stop"
|
||||
@touchstart.prevent="move('z', '+')" @touchend.prevent="stop">
|
||||
<el-icon size="24">
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="speed-container">
|
||||
<div class="label">速度:</div>
|
||||
<el-slider v-model="postureSpeed" :min="0.1" :step="0.1" :max="4" />
|
||||
<div class="proportion">{{ (postureSpeed / 4 * 100).toFixed(0) }} %</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -178,9 +236,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import UrdfView from './UrdfView.vue'
|
||||
import { getPoseApi, getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, speedLApi } from '@/api/arm'
|
||||
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
||||
import { useRobotStore } from '@/stores/robot'
|
||||
|
||||
const robotStore = useRobotStore()
|
||||
const deviceId = import.meta.env.VITE_ARM_DEVICE_ID
|
||||
|
||||
const urdfViewerRef = ref()
|
||||
const jointControls = ref()
|
||||
@ -195,18 +258,166 @@ const tcp = ref({
|
||||
rz: -72.937
|
||||
})
|
||||
|
||||
// 速度
|
||||
const speed = ref(56)
|
||||
|
||||
const updateJoint = (jointName, angle) => {
|
||||
urdfViewerRef.value.updateJoint(jointName, angle)
|
||||
const getPose = async () => {
|
||||
const res = await getPoseApi({
|
||||
ip: robotStore.ip,
|
||||
deviceId: deviceId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
const { x, y, z, rx, ry, rz } = res.data
|
||||
tcp.value = { x, y, z, rx, ry, rz }
|
||||
}
|
||||
}
|
||||
|
||||
const jointNamsMap = {
|
||||
'joint_1': 'elfin_joint1',
|
||||
'joint_2': 'elfin_joint2',
|
||||
'joint_3': 'elfin_joint3',
|
||||
'joint_4': 'elfin_joint4',
|
||||
'joint_5': 'elfin_joint5',
|
||||
'joint_6': 'elfin_joint6',
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关节状态
|
||||
* @param robot
|
||||
*/
|
||||
const getJointState = async (robot) => {
|
||||
try {
|
||||
const res = await getJointStateApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
setTimeout(() => {
|
||||
const names = res.data.name
|
||||
const positions = res.data.position
|
||||
names.forEach((name, index) => {
|
||||
urdfViewerRef.value.updateJoint(jointNamsMap[name] || name, positions[index])
|
||||
})
|
||||
}, 500)
|
||||
} else {
|
||||
console.error('获取关节状态失败:', res.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取关节状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 速度
|
||||
const jointSpeed = ref(0.8)
|
||||
const updateJoint = async (jointName, _dir) => {
|
||||
const velocities = jointControls.value.map(joint => {
|
||||
return 0
|
||||
})
|
||||
const index = jointControls.value.findIndex(joint => joint.name === jointName)
|
||||
velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1)
|
||||
const res = await speedJApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip,
|
||||
acceleration: jointSpeed.value * (_dir === '+' ? 1 : -1) + 0.1,
|
||||
duration: 60,
|
||||
velocities: velocities
|
||||
})
|
||||
}
|
||||
|
||||
const postureSpeed = ref(0.8)
|
||||
const endSpeed = ref(0.8)
|
||||
|
||||
const speedData = reactive({
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
vz: 0,
|
||||
wx: 0,
|
||||
wy: 0,
|
||||
wz: 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 机械臂移动
|
||||
* @param axis 坐标轴
|
||||
* @param direction 方向
|
||||
*/
|
||||
const move = async (axis, direction, type) => {
|
||||
speedData.vx = 0
|
||||
speedData.vy = 0
|
||||
speedData.vz = 0
|
||||
speedData.wx = 0
|
||||
speedData.wy = 0
|
||||
speedData.wz = 0
|
||||
if (type === 'end') {
|
||||
// 末端移动
|
||||
const speedValue = endSpeed.value
|
||||
switch (axis) {
|
||||
case 'x':
|
||||
speedData.vx = direction === '+' ? speedValue : -speedValue
|
||||
break
|
||||
case 'y':
|
||||
speedData.vy = direction === '+' ? speedValue : -speedValue
|
||||
break
|
||||
case 'z':
|
||||
speedData.vz = direction === '+' ? speedValue : -speedValue
|
||||
break
|
||||
}
|
||||
} else if (type === 'posture') {
|
||||
// 关节移动
|
||||
const jointSpeed = postureSpeed.value
|
||||
switch (axis) {
|
||||
case 'x':
|
||||
speedData.wx = direction === '+' ? jointSpeed : -jointSpeed
|
||||
break
|
||||
case 'y':
|
||||
speedData.wy = direction === '+' ? jointSpeed : -jointSpeed
|
||||
break
|
||||
case 'z':
|
||||
speedData.wz = direction === '+' ? jointSpeed : -jointSpeed
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
speedLApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip,
|
||||
duration: 60,
|
||||
acceleration: type === 'posture' ? postureSpeed.value + 0.1 : endSpeed.value + 0.1,
|
||||
velocity: {
|
||||
...speedData
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止机械臂移动
|
||||
*/
|
||||
const stop = async () => {
|
||||
await stopMotionApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip
|
||||
})
|
||||
}
|
||||
|
||||
const timer = ref(null)
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
jointControls.value = urdfViewerRef.value.jointControls
|
||||
stop()
|
||||
getPose()
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value)
|
||||
}
|
||||
timer.value = setInterval(() => {
|
||||
getJointState()
|
||||
}, 100)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -290,17 +501,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.speed-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label,
|
||||
.proportion {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 中间3D区域
|
||||
@ -389,6 +590,18 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.speed-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label,
|
||||
.proportion {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -141,7 +141,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const VITE_DEVICE_ID = import.meta.env.VITE_DEVICE_ID
|
||||
const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID
|
||||
|
||||
const robotStore = useRobotStore()
|
||||
|
||||
@ -186,7 +186,7 @@ const onPress = async (_dir) => {
|
||||
moveRobotData.value.w = rotationAngle.value
|
||||
const res = await moveRobot({
|
||||
ip: robotStore.ip,
|
||||
deviceId: VITE_DEVICE_ID,
|
||||
deviceId: VITE_AGV_DEVICE_ID,
|
||||
...moveRobotData.value
|
||||
})
|
||||
}
|
||||
@ -196,7 +196,7 @@ function onRelease() {
|
||||
moveRobotData.value.vy = 0;
|
||||
stopRobot({
|
||||
ip: robotStore.ip,
|
||||
deviceId: VITE_DEVICE_ID
|
||||
deviceId: VITE_AGV_DEVICE_ID
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const VITE_DEVICE_ID = import.meta.env.VITE_DEVICE_ID
|
||||
const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID
|
||||
|
||||
const robotStore = useRobotStore()
|
||||
|
||||
@ -380,7 +380,7 @@ const centerCanvasView = () => {
|
||||
const loadSourceMap = async (mapName) => {
|
||||
const res = await getCurrentMap({
|
||||
ip: robotStore.ip,
|
||||
deviceId: VITE_DEVICE_ID,
|
||||
deviceId: VITE_AGV_DEVICE_ID,
|
||||
mapName: mapName
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -419,7 +419,7 @@ const moveRobot = (robot, dx, dy, dtheta = 0) => {
|
||||
const getRobot = async () => {
|
||||
const res = await getRobotState({
|
||||
ip: robotStore.ip,
|
||||
deviceId: VITE_DEVICE_ID,
|
||||
deviceId: VITE_AGV_DEVICE_ID,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
robotStore.setPosition(res.data.x, res.data.y, res.data.angle)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user