diff --git a/.env b/.env index 7e3fd98..987b014 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ -VITE_AGV_DEVICE_ID = 'agv_src2200' -VITE_ARM_DEVICE_ID = 'huayan_arm' +# VITE_AGV_DEVICE_ID = 'agv_src2200' +# VITE_ARM_DEVICE_ID = 'aubo_arm' # 机械臂IP 192.168.1.109:50052 VITE_SERVICE_PORT = 5175 \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 101b7d6..bbc5ea8 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -7,6 +7,7 @@ export {} declare global { const EffectScope: typeof import('vue').EffectScope + const ElMess: typeof import('element-plus/es').ElMess const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate const computed: typeof import('vue').computed const createApp: typeof import('vue').createApp diff --git a/components.d.ts b/components.d.ts index 06e1866..b668fe7 100644 --- a/components.d.ts +++ b/components.d.ts @@ -14,6 +14,7 @@ declare module 'vue' { ElB: typeof import('element-plus/es')['ElB'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] + ElCard: typeof import('element-plus/es')['ElCard'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCol: typeof import('element-plus/es')['ElCol'] ElContainer: typeof import('element-plus/es')['ElContainer'] diff --git a/server/index.js b/server/index.js index bcbbee8..eb0e11d 100644 --- a/server/index.js +++ b/server/index.js @@ -384,6 +384,35 @@ app.post('/api/arm/stopMotion', async (req, res) => { } }) +app.post('/api/arm/torqueOn', async (req, res) => { + const request = { + device_id: req.body.deviceId || 'huayan_arm', + }; + const grpcClient = createARMGrpcClient(req.body.ip) + + const resp = await new Promise((resolve, reject) => { + grpcClient.torqueOn(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服务端错误' }) + } +}) + const isProd = process.env.NODE_ENV === 'production'; const PORT = process.env.VITE_SERVICE_PORT || 3000; diff --git a/src/assets/address.png b/src/assets/address.png new file mode 100644 index 0000000..7b5ef98 Binary files /dev/null and b/src/assets/address.png differ diff --git a/src/stores/robot.js b/src/stores/robot.js index ca78500..dfd3d9c 100644 --- a/src/stores/robot.js +++ b/src/stores/robot.js @@ -3,7 +3,9 @@ import { defineStore } from 'pinia' // 唯一id:robot export const useRobotStore = defineStore('robot', { state: () => ({ - ip: '192.168.0.100:50052', + ip: '192.168.0.109:50052', + agvDeviceId:'agv_src2200', + almDeviceId: 'aubo_arm', position: { x: 0, y: 0, angle: 0 }, battery: 100, status: 'idle' @@ -21,6 +23,12 @@ export const useRobotStore = defineStore('robot', { setIp(str) { this.ip = str }, + setAgvDeviceId(str) { + this.agvDeviceId = str + }, + setAlmDeviceId(str) { + this.almDeviceId = str + }, // 更新机器人位置 setPosition(x, y, angle) { this.position.x = x diff --git a/src/utils/request.js b/src/utils/request.js index c1b804d..f8de690 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,9 +1,22 @@ 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) - }) + // 1. 创建控制器 + const controller = new AbortController(); + const { signal } = controller; + + // 2. 设置定时器,超时后调用 abort() + const timeoutId = setTimeout(() => { + controller.abort(); // 触发中止信号 + }, 5000) + + const res = await fetch(url, { + method: method, + headers: headers, + body: JSON.stringify(params), + signal + }).finally(() => { + // 4. 无论请求成功还是失败,都要清除定时器,防止内存泄漏 + clearTimeout(timeoutId); + }); const data = await res.json() return data diff --git a/src/views/Address.vue b/src/views/Address.vue index 07cdef1..008ccf8 100644 --- a/src/views/Address.vue +++ b/src/views/Address.vue @@ -1,18 +1,24 @@ @@ -21,46 +27,55 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { getCurrentMapName } from '@/api/agv.js' import { useRobotStore } from '@/stores/robot' +import { ElMessage } from 'element-plus' const router = useRouter() const robotStore = useRobotStore() +const loading = ref(false) const ruleFormRef = ref() const ruleForm = ref({ - ip: '' + ip: '', + agvDeviceId:'', + almDeviceId: '' }) const rules = ref({ ip: [ { required: true, message: '请输入机器人IP和端口', trigger: 'blur' } ], + agvDeviceId: [ + { required: true, message: '请输入底盘设备ID', trigger: 'blur' } + ], + almDeviceId: [ + { required: true, message: '请输入机械臂设备ID', trigger: 'blur' } + ], }) const testConnect = async () => { - 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' + const valid = await ruleFormRef.value.validate(); + if (valid) { + loading.value = true + robotStore.setIp(ruleForm.value.ip) + robotStore.setAgvDeviceId(ruleForm.value.agvDeviceId) + robotStore.setAlmDeviceId(ruleForm.value.almDeviceId) + const result = await getCurrentMapName({ + ip: robotStore.ip, + deviceId: ruleForm.value.agvDeviceId + }) + loading.value = false + if (result.code === 200) { + router.push({ + path: '/home', + query: { + mapName: result.data.current_map + } + }) + } else { + ElMessage.error(result?.message || '连接服务错误') } - }) + } } @@ -74,16 +89,61 @@ const testConnect = async () => { align-items: center; justify-content: center; gap: 20px; + background-image: url(../assets/address.png); + background-repeat: no-repeat; + background-size: 100% 100%; - .title { - font-size: 40px; - font-weight: bold; - color: #000; - } + .card { + padding: 40px; + box-sizing: border-box; + background: #fff; + width: 600px; + border-radius: 20px; - .secondaryTitle { - font-size: 14px; - color: #8f8f8f; + .title { + font-size: 40px; + font-weight: bold; + color: #000; + margin-bottom: 12px; + } + + .secondaryTitle { + font-size: 20px; + color: #8f8f8f; + margin-bottom: 8px; + } + + .tips { + display: inline-block; + font-size: 14px; + padding: 4px 16px; + color: #003af7; + background: #b7c5f3; + border-radius: 12px; + margin-bottom: 12px; + } + + :deep(.el-input__inner) { + height: 40px; + } + + .connect { + width: 340px; + height: 50px; + display: inline-block; + padding: 4px 20px; + color: #fff; + background: #003af7; + border-radius: 12px; + margin-top: 20px; + } + + .error-tips { + text-align: center; + font-size: 14px; + color: #8f8f8f; + margin-top: 10px; + } } } \ No newline at end of file diff --git a/src/views/arm/MechanicalArm.vue b/src/views/arm/MechanicalArm.vue index 7ca55fe..53f4d23 100644 --- a/src/views/arm/MechanicalArm.vue +++ b/src/views/arm/MechanicalArm.vue @@ -249,7 +249,7 @@ 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 deviceId = robotStore.almDeviceId const urdfViewerRef = ref() const jointControls = ref() @@ -264,6 +264,13 @@ const tcp = ref({ rz: -72.937 }) +const torqueOn = async () => { + await torqueOnApi({ + ip: robotStore.ip, + deviceId: deviceId + }) +} + const getPose = async () => { const res = await getPoseApi({ ip: robotStore.ip, @@ -407,15 +414,15 @@ const timer = ref(null) onMounted(() => { nextTick(() => { jointControls.value = urdfViewerRef.value.jointControls - stop() + torqueOn() getPose() if (timer.value) { clearInterval(timer.value) } - timer.value = setInterval(() => { + // timer.value = setInterval(() => { getPose() getJointState() - }, 100) + // }, 100) }) }) diff --git a/src/views/chassis/Chassis.vue b/src/views/chassis/Chassis.vue index e0cd8a8..b51bdf2 100644 --- a/src/views/chassis/Chassis.vue +++ b/src/views/chassis/Chassis.vue @@ -141,7 +141,7 @@ const props = defineProps({ } }) -const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID +// const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID const robotStore = useRobotStore() @@ -186,7 +186,8 @@ const onPress = async (_dir) => { moveRobotData.value.w = rotationAngle.value const res = await moveRobot({ ip: robotStore.ip, - deviceId: VITE_AGV_DEVICE_ID, + // deviceId: VITE_AGV_DEVICE_ID, + deviceId: robotStore.agvDeviceId, ...moveRobotData.value }) } @@ -196,7 +197,8 @@ function onRelease() { moveRobotData.value.vy = 0; stopRobot({ ip: robotStore.ip, - deviceId: VITE_AGV_DEVICE_ID + // deviceId: VITE_AGV_DEVICE_ID + deviceId: robotStore.agvDeviceId, }) } diff --git a/src/views/chassis/MapCanvas.vue b/src/views/chassis/MapCanvas.vue index 6c285e2..e406f74 100644 --- a/src/views/chassis/MapCanvas.vue +++ b/src/views/chassis/MapCanvas.vue @@ -40,7 +40,7 @@ const props = defineProps({ } }) -const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID +// const VITE_AGV_DEVICE_ID = import.meta.env.VITE_AGV_DEVICE_ID const robotStore = useRobotStore() @@ -380,7 +380,8 @@ const centerCanvasView = () => { const loadSourceMap = async (mapName) => { const res = await getCurrentMap({ ip: robotStore.ip, - deviceId: VITE_AGV_DEVICE_ID, + // deviceId: VITE_AGV_DEVICE_ID, + deviceId: robotStore.agvDeviceId, mapName: mapName }) if (res.code === 200) { @@ -419,7 +420,8 @@ const moveRobot = (robot, dx, dy, dtheta = 0) => { const getRobot = async () => { const res = await getRobotState({ ip: robotStore.ip, - deviceId: VITE_AGV_DEVICE_ID, + // deviceId: VITE_AGV_DEVICE_ID, + deviceId: robotStore.agvDeviceId, }) if (res.code === 200) { robotStore.setPosition(res.data.x, res.data.y, res.data.angle)