diff --git a/.env b/.env index 658fd09..14d741d 100644 --- a/.env +++ b/.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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index dacb708..77f3b00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,6 @@ services: container_name: inspection-host-computer ports: - "5175:5175" # 映射端口 - - "5173:5173" # 映射端口 environment: - NODE_ENV=production - VITE_SERVICE_PORT=5175 # 传给 Express 的环境变量 diff --git a/server/grpcClient.js b/server/grpcClient.js new file mode 100644 index 0000000..82180f9 --- /dev/null +++ b/server/grpcClient.js @@ -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 +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index 9c2e75e..cdc0429 100644 --- a/server/index.js +++ b/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; diff --git a/server/proto/cmvr/api/arm_command.proto b/server/proto/cmvr/api/arm_command.proto new file mode 100644 index 0000000..3c0d7b9 --- /dev/null +++ b/server/proto/cmvr/api/arm_command.proto @@ -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; + } +} diff --git a/server/proto/cmvr/api/arm_service.proto b/server/proto/cmvr/api/arm_service.proto new file mode 100644 index 0000000..565b493 --- /dev/null +++ b/server/proto/cmvr/api/arm_service.proto @@ -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); +} diff --git a/src/adapters/XacroAdapter.js b/src/adapters/XacroAdapter.js index 9a7275f..29cec84 100644 --- a/src/adapters/XacroAdapter.js +++ b/src/adapters/XacroAdapter.js @@ -306,9 +306,6 @@ export class XacroAdapter { * @returns {Promise} */ 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; } diff --git a/src/api/agv.js b/src/api/agv.js index 1d0111c..3b5dd0a 100644 --- a/src/api/agv.js +++ b/src/api/agv.js @@ -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') } \ No newline at end of file diff --git a/src/api/arm.js b/src/api/arm.js new file mode 100644 index 0000000..4a992b9 --- /dev/null +++ b/src/api/arm.js @@ -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') +} \ No newline at end of file diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..c1b804d --- /dev/null +++ b/src/utils/request.js @@ -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 \ No newline at end of file diff --git a/src/views/Address.vue b/src/views/Address.vue index 00b7f0e..07cdef1 100644 --- a/src/views/Address.vue +++ b/src/views/Address.vue @@ -28,8 +28,7 @@ const robotStore = useRobotStore() const ruleFormRef = ref() const ruleForm = ref({ - ip: '', - port: 80 + ip: '' }) const rules = ref({ @@ -39,21 +38,29 @@ 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) { - router.push({ - path: '/home', - query: { - mapName: result.data.current_map - } - }) + 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: 'test' } - } + }) } diff --git a/src/views/arm/MechanicalArm.vue b/src/views/arm/MechanicalArm.vue index ef0f502..0948d29 100644 --- a/src/views/arm/MechanicalArm.vue +++ b/src/views/arm/MechanicalArm.vue @@ -42,15 +42,27 @@
关节控制
- - {{ (joint.value * 180 / Math.PI).toFixed(1) }}° + + + +
速度:
- -
{{ speed }} %
+ +
{{ (jointSpeed / 9 * 100).toFixed(0) }} %
@@ -76,11 +88,17 @@
前后平移
- - + + + + - - + + + +
@@ -92,11 +110,17 @@
左右平移
- - + + + + - - + + + +
@@ -108,16 +132,27 @@
上下平移
- - + + + + - - + + + +
+
+
速度:
+ +
{{ (endSpeed / 4 * 100).toFixed(0) }} %
+
@@ -129,11 +164,17 @@
前后平移
- - + + + + - - + + + +
@@ -145,11 +186,17 @@
左右平移
- - + + + + - - + + + +
@@ -161,16 +208,27 @@
上下平移
- - + + + + - - + + + +
+
+
速度:
+ +
{{ (postureSpeed / 4 * 100).toFixed(0) }} %
+
@@ -178,9 +236,14 @@