feat: 上位机首页
This commit is contained in:
parent
162a9b6e58
commit
98031a0dcc
4
.env
4
.env
@ -1,5 +1,5 @@
|
|||||||
VITE_AGV_DEVICE_ID = 'agv_src2200'
|
# VITE_AGV_DEVICE_ID = 'agv_src2200'
|
||||||
VITE_ARM_DEVICE_ID = 'huayan_arm'
|
# VITE_ARM_DEVICE_ID = 'aubo_arm'
|
||||||
# 机械臂IP 192.168.1.109:50052
|
# 机械臂IP 192.168.1.109:50052
|
||||||
|
|
||||||
VITE_SERVICE_PORT = 5175
|
VITE_SERVICE_PORT = 5175
|
||||||
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@ -7,6 +7,7 @@
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue').EffectScope
|
const EffectScope: typeof import('vue').EffectScope
|
||||||
|
const ElMess: typeof import('element-plus/es').ElMess
|
||||||
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||||
const computed: typeof import('vue').computed
|
const computed: typeof import('vue').computed
|
||||||
const createApp: typeof import('vue').createApp
|
const createApp: typeof import('vue').createApp
|
||||||
|
|||||||
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
ElB: typeof import('element-plus/es')['ElB']
|
ElB: typeof import('element-plus/es')['ElB']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||||
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
|
|||||||
@ -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 isProd = process.env.NODE_ENV === 'production';
|
||||||
const PORT = process.env.VITE_SERVICE_PORT || 3000;
|
const PORT = process.env.VITE_SERVICE_PORT || 3000;
|
||||||
|
|
||||||
|
|||||||
BIN
src/assets/address.png
Normal file
BIN
src/assets/address.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
@ -3,7 +3,9 @@ import { defineStore } from 'pinia'
|
|||||||
// 唯一id:robot
|
// 唯一id:robot
|
||||||
export const useRobotStore = defineStore('robot', {
|
export const useRobotStore = defineStore('robot', {
|
||||||
state: () => ({
|
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 },
|
position: { x: 0, y: 0, angle: 0 },
|
||||||
battery: 100,
|
battery: 100,
|
||||||
status: 'idle'
|
status: 'idle'
|
||||||
@ -21,6 +23,12 @@ export const useRobotStore = defineStore('robot', {
|
|||||||
setIp(str) {
|
setIp(str) {
|
||||||
this.ip = str
|
this.ip = str
|
||||||
},
|
},
|
||||||
|
setAgvDeviceId(str) {
|
||||||
|
this.agvDeviceId = str
|
||||||
|
},
|
||||||
|
setAlmDeviceId(str) {
|
||||||
|
this.almDeviceId = str
|
||||||
|
},
|
||||||
// 更新机器人位置
|
// 更新机器人位置
|
||||||
setPosition(x, y, angle) {
|
setPosition(x, y, angle) {
|
||||||
this.position.x = x
|
this.position.x = x
|
||||||
|
|||||||
@ -1,9 +1,22 @@
|
|||||||
const request = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
|
const request = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
|
||||||
|
// 1. 创建控制器
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
|
||||||
|
// 2. 设置定时器,超时后调用 abort()
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
controller.abort(); // 触发中止信号
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: JSON.stringify(params)
|
body: JSON.stringify(params),
|
||||||
})
|
signal
|
||||||
|
}).finally(() => {
|
||||||
|
// 4. 无论请求成功还是失败,都要清除定时器,防止内存泄漏
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
return data
|
return data
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
|
<div class="card">
|
||||||
<div class="title">设备连接登录</div>
|
<div class="title">设备连接登录</div>
|
||||||
<div class="secondaryTitle">请输入IP和端口连接后进入上位机页面</div>
|
<div class="secondaryTitle">填写设备通讯参数,建立与AGV底盘和机械臂的连接</div>
|
||||||
|
<div class="tips">请操持设备与上位机在同一局域网内</div>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto">
|
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
|
||||||
<el-form-item label="" prop="ip">
|
<el-form-item label="IP和端口" prop="ip">
|
||||||
<el-input v-model="ruleForm.ip" style="width: 240px" placeholder="请输入IP" >
|
<el-input v-model="ruleForm.ip" placeholder="请输入IP和端口" />
|
||||||
<template #append>
|
</el-form-item>
|
||||||
<el-button @click="testConnect">➡</el-button>
|
<el-form-item label="底盘设备ID" prop="agvDeviceId">
|
||||||
</template>
|
<el-input v-model="ruleForm.agvDeviceId" placeholder="请输入底盘设备ID" />
|
||||||
</el-input>
|
</el-form-item>
|
||||||
<!-- <el-button @click="testConnect">➡</el-button> -->
|
<el-form-item label="机械臂设备ID" prop="almDeviceId">
|
||||||
|
<el-input v-model="ruleForm.almDeviceId" placeholder="请输入机械臂设备ID" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<el-button :loading="loading" @click="testConnect" class="connect">进入上位机控制页面</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="error-tips">连接异常排查:检查机器人状态、局域网联通、设备ID调谐是否匹配</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -21,46 +27,55 @@ import { ref } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { getCurrentMapName } from '@/api/agv.js'
|
import { getCurrentMapName } from '@/api/agv.js'
|
||||||
import { useRobotStore } from '@/stores/robot'
|
import { useRobotStore } from '@/stores/robot'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const robotStore = useRobotStore()
|
const robotStore = useRobotStore()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
const ruleFormRef = ref()
|
const ruleFormRef = ref()
|
||||||
const ruleForm = ref({
|
const ruleForm = ref({
|
||||||
ip: ''
|
ip: '',
|
||||||
|
agvDeviceId:'',
|
||||||
|
almDeviceId: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = ref({
|
const rules = ref({
|
||||||
ip: [
|
ip: [
|
||||||
{ required: true, message: '请输入机器人IP和端口', trigger: 'blur' }
|
{ required: true, message: '请输入机器人IP和端口', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
|
agvDeviceId: [
|
||||||
|
{ required: true, message: '请输入底盘设备ID', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
almDeviceId: [
|
||||||
|
{ required: true, message: '请输入机械臂设备ID', trigger: 'blur' }
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const testConnect = async () => {
|
const testConnect = async () => {
|
||||||
|
const valid = await ruleFormRef.value.validate();
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true
|
||||||
robotStore.setIp(ruleForm.value.ip)
|
robotStore.setIp(ruleForm.value.ip)
|
||||||
// const valid = await ruleFormRef.value.validate();
|
robotStore.setAgvDeviceId(ruleForm.value.agvDeviceId)
|
||||||
// if (valid) {
|
robotStore.setAlmDeviceId(ruleForm.value.almDeviceId)
|
||||||
// const result = await getCurrentMapName({
|
const result = await getCurrentMapName({
|
||||||
// ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
// deviceId: VITE_AGV_DEVICE_ID,
|
deviceId: ruleForm.value.agvDeviceId
|
||||||
// })
|
})
|
||||||
// if (result.code === 200) {
|
loading.value = false
|
||||||
// router.push({
|
if (result.code === 200) {
|
||||||
// path: '/home',
|
|
||||||
// query: {
|
|
||||||
// mapName: result.data.current_map
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
path: '/home',
|
path: '/home',
|
||||||
query: {
|
query: {
|
||||||
mapName: 'test'
|
mapName: result.data.current_map
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage.error(result?.message || '连接服务错误')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -74,16 +89,61 @@ const testConnect = async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
background-image: url(../assets/address.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #fff;
|
||||||
|
width: 600px;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #000;
|
color: #000;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondaryTitle {
|
.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;
|
font-size: 14px;
|
||||||
color: #8f8f8f;
|
color: #8f8f8f;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -249,7 +249,7 @@ import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
|||||||
import { useRobotStore } from '@/stores/robot'
|
import { useRobotStore } from '@/stores/robot'
|
||||||
|
|
||||||
const robotStore = useRobotStore()
|
const robotStore = useRobotStore()
|
||||||
const deviceId = import.meta.env.VITE_ARM_DEVICE_ID
|
const deviceId = robotStore.almDeviceId
|
||||||
|
|
||||||
const urdfViewerRef = ref()
|
const urdfViewerRef = ref()
|
||||||
const jointControls = ref()
|
const jointControls = ref()
|
||||||
@ -264,6 +264,13 @@ const tcp = ref({
|
|||||||
rz: -72.937
|
rz: -72.937
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const torqueOn = async () => {
|
||||||
|
await torqueOnApi({
|
||||||
|
ip: robotStore.ip,
|
||||||
|
deviceId: deviceId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const getPose = async () => {
|
const getPose = async () => {
|
||||||
const res = await getPoseApi({
|
const res = await getPoseApi({
|
||||||
ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
@ -407,15 +414,15 @@ const timer = ref(null)
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
jointControls.value = urdfViewerRef.value.jointControls
|
jointControls.value = urdfViewerRef.value.jointControls
|
||||||
stop()
|
torqueOn()
|
||||||
getPose()
|
getPose()
|
||||||
if (timer.value) {
|
if (timer.value) {
|
||||||
clearInterval(timer.value)
|
clearInterval(timer.value)
|
||||||
}
|
}
|
||||||
timer.value = setInterval(() => {
|
// timer.value = setInterval(() => {
|
||||||
getPose()
|
getPose()
|
||||||
getJointState()
|
getJointState()
|
||||||
}, 100)
|
// }, 100)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
const robotStore = useRobotStore()
|
||||||
|
|
||||||
@ -186,7 +186,8 @@ const onPress = async (_dir) => {
|
|||||||
moveRobotData.value.w = rotationAngle.value
|
moveRobotData.value.w = rotationAngle.value
|
||||||
const res = await moveRobot({
|
const res = await moveRobot({
|
||||||
ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
deviceId: VITE_AGV_DEVICE_ID,
|
// deviceId: VITE_AGV_DEVICE_ID,
|
||||||
|
deviceId: robotStore.agvDeviceId,
|
||||||
...moveRobotData.value
|
...moveRobotData.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -196,7 +197,8 @@ function onRelease() {
|
|||||||
moveRobotData.value.vy = 0;
|
moveRobotData.value.vy = 0;
|
||||||
stopRobot({
|
stopRobot({
|
||||||
ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
deviceId: VITE_AGV_DEVICE_ID
|
// deviceId: VITE_AGV_DEVICE_ID
|
||||||
|
deviceId: robotStore.agvDeviceId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
const robotStore = useRobotStore()
|
||||||
|
|
||||||
@ -380,7 +380,8 @@ const centerCanvasView = () => {
|
|||||||
const loadSourceMap = async (mapName) => {
|
const loadSourceMap = async (mapName) => {
|
||||||
const res = await getCurrentMap({
|
const res = await getCurrentMap({
|
||||||
ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
deviceId: VITE_AGV_DEVICE_ID,
|
// deviceId: VITE_AGV_DEVICE_ID,
|
||||||
|
deviceId: robotStore.agvDeviceId,
|
||||||
mapName: mapName
|
mapName: mapName
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -419,7 +420,8 @@ const moveRobot = (robot, dx, dy, dtheta = 0) => {
|
|||||||
const getRobot = async () => {
|
const getRobot = async () => {
|
||||||
const res = await getRobotState({
|
const res = await getRobotState({
|
||||||
ip: robotStore.ip,
|
ip: robotStore.ip,
|
||||||
deviceId: VITE_AGV_DEVICE_ID,
|
// deviceId: VITE_AGV_DEVICE_ID,
|
||||||
|
deviceId: robotStore.agvDeviceId,
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
robotStore.setPosition(res.data.x, res.data.y, res.data.angle)
|
robotStore.setPosition(res.data.x, res.data.y, res.data.angle)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user