refactor: 按机器人动态解析gRPC端点与设备
This commit is contained in:
parent
5b294f6d37
commit
92bd36c705
@ -23,7 +23,7 @@ function waitForGrpcReady(client, timeoutMs = 5000) {
|
||||
})
|
||||
}
|
||||
|
||||
export function attachAudioWebSocket(httpServer) {
|
||||
export function attachAudioWebSocket(httpServer, { resolveRobotAddress, resolveRobotDeviceId } = {}) {
|
||||
const wss = new WebSocketServer({ server: httpServer, path: '/audio' })
|
||||
|
||||
wss.on('connection', (socket) => {
|
||||
@ -101,14 +101,27 @@ export function attachAudioWebSocket(httpServer) {
|
||||
}
|
||||
if (message.type !== 'init' || initialized) return
|
||||
|
||||
const { microphoneId, robotAddress } = message
|
||||
speakerId = message.speakerId?.trim()
|
||||
if (![microphoneId, speakerId, robotAddress].every((value) => typeof value === 'string' && value.trim())) {
|
||||
const { robotId } = message
|
||||
if (typeof robotId !== 'string' || !robotId.trim()) {
|
||||
sendJson(socket, { type: 'error', message: '机器人地址、麦克风 ID 和扬声器 ID 不能为空' })
|
||||
socket.close(1008, 'invalid init')
|
||||
return
|
||||
}
|
||||
|
||||
const robotAddress = resolveRobotAddress?.(robotId.trim())
|
||||
if (!robotAddress) {
|
||||
sendJson(socket, { type: 'error', message: `机器人 ${robotId} 不在线或尚未注册` })
|
||||
socket.close(1008, 'robot unavailable')
|
||||
return
|
||||
}
|
||||
const microphoneId = resolveRobotDeviceId?.(robotId.trim(), 'microphone')
|
||||
speakerId = resolveRobotDeviceId?.(robotId.trim(), 'speaker')
|
||||
if (!microphoneId || !speakerId) {
|
||||
sendJson(socket, { type: 'error', message: `机器人 ${robotId} 缺少麦克风或扬声器设备` })
|
||||
socket.close(1008, 'audio device unavailable')
|
||||
return
|
||||
}
|
||||
|
||||
initialized = true
|
||||
microphoneClient = createMicrophoneGrpcClient(robotAddress.trim())
|
||||
speakerClient = createSpeakerGrpcClient(robotAddress.trim())
|
||||
|
||||
@ -84,14 +84,9 @@ function message(error) {
|
||||
}
|
||||
|
||||
export function streamCameraVideo(req, res) {
|
||||
const ip = typeof req.query.ip === 'string' ? req.query.ip.trim() : ''
|
||||
const deviceId = typeof req.query.deviceId === 'string' ? req.query.deviceId.trim() : ''
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: 'ip 和 deviceId 不能为空' })
|
||||
return
|
||||
}
|
||||
const deviceId = req.deviceId
|
||||
|
||||
const client = createCameraGrpcClient(ip)
|
||||
const client = createCameraGrpcClient(req.robotAddress)
|
||||
const grpcStream = client.getRgbImageStream()
|
||||
let transcoder
|
||||
let closed = false
|
||||
|
||||
115
server/index.js
115
server/index.js
@ -17,7 +17,7 @@ import {
|
||||
import { attachAudioWebSocket } from './audioWebSocket.js';
|
||||
import { streamCameraVideo } from './cameraVideoStream.js';
|
||||
import { startQuicServer } from './quicServer.js';
|
||||
import { consumeQuicEvent, listRobots } from './robotRegistry.js';
|
||||
import { consumeQuicEvent, getRobotAddress, getRobotDeviceId, listRobots } from './robotRegistry.js';
|
||||
|
||||
const app = express()
|
||||
app.use(express.static(path.join(__dirname, '../dist')));
|
||||
@ -28,19 +28,49 @@ app.get('/api/quic/robots', (req, res) => {
|
||||
res.json({ code: 200, data: listRobots() })
|
||||
})
|
||||
|
||||
app.use('/api', (req, res, next) => {
|
||||
const robotId = String(req.body?.robotId || req.query?.robotId || '').trim()
|
||||
if (!robotId) {
|
||||
res.status(400).json({ code: 400, message: 'robotId 不能为空' })
|
||||
return
|
||||
}
|
||||
const address = getRobotAddress(robotId)
|
||||
if (!address) {
|
||||
res.status(404).json({ code: 404, message: `机器人 ${robotId} 不在线或尚未注册` })
|
||||
return
|
||||
}
|
||||
req.robotId = robotId
|
||||
req.robotAddress = address
|
||||
next()
|
||||
})
|
||||
|
||||
app.use('/api', (req, res, next) => {
|
||||
let kind = ''
|
||||
if (req.path.startsWith('/agv/')) kind = 'agv'
|
||||
else if (req.path.startsWith('/arm/')) kind = 'arm'
|
||||
else if (req.path.startsWith('/audio/microphone/')) kind = 'microphone'
|
||||
else if (req.path.startsWith('/audio/speaker/')) kind = 'speaker'
|
||||
else if (req.path.startsWith('/camera/')) kind = 'camera'
|
||||
if (!kind) return next()
|
||||
|
||||
const deviceId = getRobotDeviceId(req.robotId, kind)
|
||||
if (!deviceId) {
|
||||
res.status(404).json({ code: 404, message: `机器人 ${req.robotId} 没有可用的 ${kind} 设备` })
|
||||
return
|
||||
}
|
||||
req.deviceId = deviceId
|
||||
next()
|
||||
})
|
||||
|
||||
// 将相机逐帧 gRPC 数据转码为浏览器可直接播放的 fragmented MP4。
|
||||
// 示例:/api/camera/video?ip=192.168.0.28%3A50052&deviceId=camera1
|
||||
// 示例:/api/camera/video?robotId=robot-1
|
||||
app.get('/api/camera/video', streamCameraVideo)
|
||||
|
||||
// 获取机器人麦克风音量(0-100)。
|
||||
app.post('/api/audio/microphone/volume/get', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
const deviceId = req.deviceId
|
||||
|
||||
const grpcClient = createMicrophoneGrpcClient(ip)
|
||||
const grpcClient = createMicrophoneGrpcClient(req.robotAddress)
|
||||
grpcClient.getVolume(
|
||||
{ header: { device_id: deviceId } },
|
||||
{ deadline: Date.now() + 5000 },
|
||||
@ -65,18 +95,14 @@ app.post('/api/audio/microphone/volume/get', (req, res) => {
|
||||
|
||||
// 设置机器人麦克风音量(0-100)。
|
||||
app.post('/api/audio/microphone/volume/set', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
const deviceId = req.deviceId
|
||||
const volume = Number(req.body.volume)
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
if (!Number.isInteger(volume) || volume < 0 || volume > 100) {
|
||||
res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' })
|
||||
return
|
||||
}
|
||||
|
||||
const grpcClient = createMicrophoneGrpcClient(ip)
|
||||
const grpcClient = createMicrophoneGrpcClient(req.robotAddress)
|
||||
grpcClient.setVolume({
|
||||
header: { device_id: deviceId },
|
||||
volume
|
||||
@ -100,13 +126,9 @@ app.post('/api/audio/microphone/volume/set', (req, res) => {
|
||||
|
||||
// 获取机器人扬声器音量(0-100)。
|
||||
app.post('/api/audio/speaker/volume/get', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
const deviceId = req.deviceId
|
||||
|
||||
const grpcClient = createSpeakerGrpcClient(ip)
|
||||
const grpcClient = createSpeakerGrpcClient(req.robotAddress)
|
||||
grpcClient.getVolume(
|
||||
{ header: { device_id: deviceId } },
|
||||
{ deadline: Date.now() + 5000 },
|
||||
@ -131,18 +153,14 @@ app.post('/api/audio/speaker/volume/get', (req, res) => {
|
||||
|
||||
// 设置机器人扬声器音量(0-100)。
|
||||
app.post('/api/audio/speaker/volume/set', (req, res) => {
|
||||
const { ip, deviceId } = req.body
|
||||
const deviceId = req.deviceId
|
||||
const volume = Number(req.body.volume)
|
||||
if (!ip || !deviceId) {
|
||||
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
||||
return
|
||||
}
|
||||
if (!Number.isInteger(volume) || volume < 0 || volume > 100) {
|
||||
res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' })
|
||||
return
|
||||
}
|
||||
|
||||
const grpcClient = createSpeakerGrpcClient(ip)
|
||||
const grpcClient = createSpeakerGrpcClient(req.robotAddress)
|
||||
grpcClient.setVolume({
|
||||
header: { device_id: deviceId },
|
||||
volume
|
||||
@ -168,11 +186,11 @@ app.post('/api/audio/speaker/volume/set', (req, res) => {
|
||||
app.post('/api/agv/getRuntimeState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
device_id: req.deviceId
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getRuntimeState(request, (err, res) => {
|
||||
@ -199,10 +217,10 @@ app.post('/api/agv/getRuntimeState', async (req, res) => {
|
||||
|
||||
// 获取机器人当前地图
|
||||
app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.robotAddress)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
device_id: req.deviceId
|
||||
},
|
||||
map_name: req.body.mapName
|
||||
};
|
||||
@ -232,10 +250,10 @@ app.post('/api/agv/getCurrentMap', async (req, res) => {
|
||||
|
||||
// 机器人运动控制
|
||||
app.post('/api/agv/moveRobot', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.robotAddress)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
device_id: req.deviceId
|
||||
},
|
||||
velocity: {
|
||||
vx: req.body.vx,
|
||||
@ -272,10 +290,10 @@ app.post('/api/agv/moveRobot', async (req, res) => {
|
||||
|
||||
// 机器人停止运动
|
||||
app.post('/api/agv/stopRobot', async (req, res) => {
|
||||
const grpcClient = createAGVGrpcClient(req.body.ip)
|
||||
const grpcClient = createAGVGrpcClient(req.robotAddress)
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'agv_src1100'
|
||||
device_id: req.deviceId
|
||||
}
|
||||
};
|
||||
|
||||
@ -312,11 +330,11 @@ 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'
|
||||
device_id: req.deviceId
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getPose(request, (err, res) => {
|
||||
@ -345,11 +363,11 @@ app.post('/api/arm/getPose', async (req, res) => {
|
||||
app.post('/api/arm/getJointState', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
device_id: req.deviceId
|
||||
}
|
||||
};
|
||||
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.getJointState(request, (err, res) => {
|
||||
@ -378,7 +396,7 @@ app.post('/api/arm/getJointState', async (req, res) => {
|
||||
app.post('/api/arm/speedJ', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
device_id: req.deviceId
|
||||
},
|
||||
velocity: {
|
||||
velocity: req.body.velocities
|
||||
@ -386,7 +404,7 @@ app.post('/api/arm/speedJ', async (req, res) => {
|
||||
acceleration: req.body.acceleration,
|
||||
duration: req.body.duration
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.speedJ(request, (err, res) => {
|
||||
@ -414,13 +432,13 @@ app.post('/api/arm/speedJ', async (req, res) => {
|
||||
app.post('/api/arm/speedL', async (req, res) => {
|
||||
const request = {
|
||||
header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm'
|
||||
device_id: req.deviceId
|
||||
},
|
||||
velocity: req.body.velocity,
|
||||
acceleration: req.body.acceleration,
|
||||
duration: req.body.duration
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.speedL(request, (err, res) => {
|
||||
@ -449,10 +467,10 @@ app.post('/api/arm/speedL', async (req, res) => {
|
||||
app.post('/api/arm/stopMotion', async (req, res) => {
|
||||
const request = {
|
||||
// header: {
|
||||
device_id: req.body.deviceId || 'huayan_arm',
|
||||
device_id: req.deviceId,
|
||||
// },
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.stopMotion(request, (err, res) => {
|
||||
@ -479,9 +497,9 @@ 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',
|
||||
device_id: req.deviceId,
|
||||
};
|
||||
const grpcClient = createARMGrpcClient(req.body.ip)
|
||||
const grpcClient = createARMGrpcClient(req.robotAddress)
|
||||
|
||||
const resp = await new Promise((resolve, reject) => {
|
||||
grpcClient.torqueOn(request, (err, res) => {
|
||||
@ -551,7 +569,10 @@ if (httpsEnabled) {
|
||||
httpServer = http.createServer(app);
|
||||
}
|
||||
|
||||
attachAudioWebSocket(httpServer)
|
||||
attachAudioWebSocket(httpServer, {
|
||||
resolveRobotAddress: getRobotAddress,
|
||||
resolveRobotDeviceId: getRobotDeviceId
|
||||
})
|
||||
startQuicServer({ onEvent: consumeQuicEvent })
|
||||
|
||||
httpServer.listen(PORT, '0.0.0.0', () => {
|
||||
|
||||
@ -82,3 +82,12 @@ export function listRobots() {
|
||||
return [...robots.values()].sort((a, b) => a.robotId.localeCompare(b.robotId))
|
||||
}
|
||||
|
||||
export function getRobotAddress(robotId) {
|
||||
if (!robotId) return ''
|
||||
return robots.get(robotId)?.ip || ''
|
||||
}
|
||||
|
||||
export function getRobotDeviceId(robotId, kind) {
|
||||
const devices = robots.get(robotId)?.devices?.[kind] || []
|
||||
return (devices.find(device => device.enabled) || devices[0])?.deviceId || ''
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import request from '@/utils/request'
|
||||
/**
|
||||
* 机器人前往目标站点
|
||||
* @param {Object} params
|
||||
* @param {string} [params.deviceId] - 设备ID,默认 agv_src1100
|
||||
* robotId 由统一请求层从 Pinia 自动附加,设备 ID 由后端注册表解析。
|
||||
*/
|
||||
export function getRuntimeState(params) {
|
||||
return request('/api/agv/getRuntimeState', params, 'POST')
|
||||
|
||||
@ -3,13 +3,8 @@ import { defineStore } from 'pinia'
|
||||
// 唯一id:robot
|
||||
export const useRobotStore = defineStore('robot', {
|
||||
state: () => ({
|
||||
ip: '192.168.0.28:50052',
|
||||
robotId: '',
|
||||
robotType: 'inspection',
|
||||
agvDeviceId:'src1100',
|
||||
almDeviceId: 'huayan_arm',
|
||||
spkDeviceId: 'spk1',
|
||||
micDeviceId: 'mic1',
|
||||
cameraDeviceId: 'real_cam1',
|
||||
position: { x: 0, y: 0, theta: 0 },
|
||||
battery: {
|
||||
percentage: 0.99,
|
||||
@ -30,27 +25,12 @@ export const useRobotStore = defineStore('robot', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setIp(str) {
|
||||
this.ip = str
|
||||
setRobotId(str) {
|
||||
this.robotId = str
|
||||
},
|
||||
setRobotType(str) {
|
||||
this.robotType = str
|
||||
},
|
||||
setAgvDeviceId(str) {
|
||||
this.agvDeviceId = str
|
||||
},
|
||||
setAlmDeviceId(str) {
|
||||
this.almDeviceId = str
|
||||
},
|
||||
setSpkDeviceId(str) {
|
||||
this.spkDeviceId = str
|
||||
},
|
||||
setMicDeviceId(str) {
|
||||
this.micDeviceId = str
|
||||
},
|
||||
setCameraDeviceId(str) {
|
||||
this.cameraDeviceId = str
|
||||
},
|
||||
// 更新机器人位置
|
||||
setPosition(data = {}) {
|
||||
this.position = data
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRobotStore } from '@/stores/robot'
|
||||
|
||||
const request = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
|
||||
const controller = new AbortController()
|
||||
@ -9,13 +10,15 @@ const request = async (url, params = {}, method = 'GET', headers = { 'Content-Ty
|
||||
}, 5000)
|
||||
|
||||
try {
|
||||
const robotId = useRobotStore().robotId
|
||||
const requestParams = robotId && !params.robotId ? { ...params, robotId } : params
|
||||
const options = {
|
||||
method,
|
||||
headers,
|
||||
signal
|
||||
}
|
||||
if (!['GET', 'HEAD'].includes(method.toUpperCase())) {
|
||||
options.body = JSON.stringify(params)
|
||||
options.body = JSON.stringify(requestParams)
|
||||
}
|
||||
const res = await fetch(url, options)
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<el-option
|
||||
v-for="robot in robots"
|
||||
:key="robot.robotId"
|
||||
:label="`${robot.robotId}(${robot.ip})`"
|
||||
:label="robot.robotId"
|
||||
:value="robot.robotId"
|
||||
/>
|
||||
</el-select>
|
||||
@ -28,30 +28,12 @@
|
||||
<el-table-column prop="health" label="健康状态" width="190" />
|
||||
</el-table>
|
||||
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
|
||||
<el-form-item label="IP和端口" prop="ip">
|
||||
<el-input v-model="ruleForm.ip" placeholder="请输入IP和端口" />
|
||||
</el-form-item>
|
||||
<el-form-item label="机器人类型" prop="robotType">
|
||||
<el-select v-model="ruleForm.robotType" placeholder="请选择机器人类型">
|
||||
<el-option label="巡检机器人" value="inspection"></el-option>
|
||||
<el-option label="讲解机器人" value="explanation"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="底盘设备ID" prop="agvDeviceId">
|
||||
<el-input v-model="ruleForm.agvDeviceId" placeholder="请输入底盘设备ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="机械臂设备ID" prop="almDeviceId">
|
||||
<el-input v-model="ruleForm.almDeviceId" placeholder="请输入机械臂设备ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="扬声器设备ID" prop="spkDeviceId">
|
||||
<el-input v-model="ruleForm.spkDeviceId" placeholder="请输入扬声器设备ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="麦克风设备ID" prop="micDeviceId">
|
||||
<el-input v-model="ruleForm.micDeviceId" placeholder="请输入麦克风设备ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="相机设备ID" prop="cameraDeviceId">
|
||||
<el-input v-model="ruleForm.cameraDeviceId" placeholder="请输入相机设备ID" />
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
<el-button :loading="loading" @click="testConnect" class="connect">进入上位机控制页面</el-button>
|
||||
</div>
|
||||
@ -74,7 +56,7 @@ const robotStore = useRobotStore()
|
||||
const loading = ref(false)
|
||||
const robotListLoading = ref(false)
|
||||
const robots = ref([])
|
||||
const selectedRobotId = ref('')
|
||||
const selectedRobotId = ref(robotStore.robotId || '')
|
||||
const selectedRobot = computed(() => robots.value.find(robot => robot.robotId === selectedRobotId.value))
|
||||
const selectedDevices = computed(() => Object.entries(selectedRobot.value?.devices || {}).flatMap(
|
||||
([kind, devices]) => devices.map(device => ({ kind, ...device }))
|
||||
@ -82,51 +64,19 @@ const selectedDevices = computed(() => Object.entries(selectedRobot.value?.devic
|
||||
let refreshTimer
|
||||
const ruleFormRef = ref()
|
||||
const ruleForm = ref({
|
||||
ip: '',
|
||||
robotType: 'inspection',
|
||||
// agvDeviceId:'src1100',
|
||||
// almDeviceId: 'huayan_arm',
|
||||
// spkDeviceId: 'spk1',
|
||||
// micDeviceId: 'mic1',
|
||||
// cameraDeviceId: 'real_cam1'
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
ip: [
|
||||
{ required: true, message: '请输入机器人IP和端口', trigger: 'blur' }
|
||||
],
|
||||
robotType: [
|
||||
{ required: true, message: '请选择机器人类型', trigger: 'blur' }
|
||||
],
|
||||
// agvDeviceId: [
|
||||
// { required: true, message: '请输入底盘设备ID', trigger: 'blur' }
|
||||
// ],
|
||||
// almDeviceId: [
|
||||
// { required: true, message: '请输入机械臂设备ID', trigger: 'blur' }
|
||||
// ],
|
||||
// spkDeviceId: [
|
||||
// { required: true, message: '请输入扬声器设备ID', trigger: 'blur' }
|
||||
// ],
|
||||
// micDeviceId: [
|
||||
// { required: true, message: '请输入麦克风设备ID', trigger: 'blur' }
|
||||
// ],
|
||||
// cameraDeviceId: [
|
||||
// { required: true, message: '请输入相机设备ID', trigger: 'blur' }
|
||||
// ],
|
||||
})
|
||||
|
||||
const firstDeviceId = (robot, kind, fallback) => robot?.devices?.[kind]?.[0]?.deviceId || fallback
|
||||
|
||||
const selectRobot = () => {
|
||||
const robot = selectedRobot.value
|
||||
if (!robot) return
|
||||
ruleForm.value.ip = robot.ip
|
||||
robotStore.setIp(robot.ip)
|
||||
robotStore.setAgvDeviceId(firstDeviceId(robot, 'agv', 'src1100'))
|
||||
robotStore.setAlmDeviceId(firstDeviceId(robot, 'arm', 'huayan_arm'))
|
||||
robotStore.setSpkDeviceId(firstDeviceId(robot, 'speaker', 'spk1'))
|
||||
robotStore.setMicDeviceId(firstDeviceId(robot, 'microphone', 'mic1'))
|
||||
robotStore.setCameraDeviceId(firstDeviceId(robot, 'camera', 'real_cam1'))
|
||||
robotStore.setRobotId(robot.robotId)
|
||||
}
|
||||
|
||||
const loadRobots = async () => {
|
||||
@ -147,32 +97,19 @@ onMounted(() => {
|
||||
onUnmounted(() => clearInterval(refreshTimer))
|
||||
|
||||
const testConnect = async () => {
|
||||
if (!selectedRobot.value) {
|
||||
ElMessage.warning('请先选择已连接机器人')
|
||||
return
|
||||
}
|
||||
const valid = await ruleFormRef.value.validate();
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
robotStore.setIp(ruleForm.value.ip)
|
||||
robotStore.setRobotId(selectedRobot.value.robotId)
|
||||
robotStore.setRobotType(ruleForm.value.robotType)
|
||||
if (selectedRobot.value) {
|
||||
selectRobot()
|
||||
} else if (ruleForm.value.robotType === 'inspection') {
|
||||
robotStore.setAgvDeviceId('src1100')
|
||||
robotStore.setAlmDeviceId('huayan_arm')
|
||||
robotStore.setSpkDeviceId('spk1')
|
||||
robotStore.setMicDeviceId('mic1')
|
||||
robotStore.setCameraDeviceId('real_cam1')
|
||||
} else if (ruleForm.value.robotType === 'explanation') {
|
||||
robotStore.setAgvDeviceId('src1100')
|
||||
robotStore.setAlmDeviceId('huayan_arm')
|
||||
robotStore.setSpkDeviceId('spk1')
|
||||
robotStore.setMicDeviceId('mic1')
|
||||
robotStore.setCameraDeviceId('real_cam1')
|
||||
}
|
||||
selectRobot()
|
||||
|
||||
try {
|
||||
const result = await getRuntimeState({
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.agvDeviceId
|
||||
})
|
||||
const result = await getRuntimeState()
|
||||
loading.value = false
|
||||
if (result.code === 200) {
|
||||
robotStore.setPosition(result.data.pose)
|
||||
|
||||
@ -303,7 +303,6 @@ import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
||||
import { useRobotStore } from '@/stores/robot'
|
||||
|
||||
const robotStore = useRobotStore()
|
||||
const deviceId = robotStore.almDeviceId
|
||||
|
||||
const urdfViewerRef = ref()
|
||||
|
||||
@ -343,17 +342,11 @@ const tcp = ref({
|
||||
})
|
||||
|
||||
const torqueOn = async () => {
|
||||
await torqueOnApi({
|
||||
ip: robotStore.ip,
|
||||
deviceId: deviceId
|
||||
})
|
||||
await torqueOnApi()
|
||||
}
|
||||
|
||||
const getPose = async () => {
|
||||
const res = await getPoseApi({
|
||||
ip: robotStore.ip,
|
||||
deviceId: deviceId
|
||||
})
|
||||
const res = await getPoseApi()
|
||||
if (res.code === 200) {
|
||||
const { x, y, z, rx, ry, rz } = res.data
|
||||
tcp.value = { x, y, z, rx, ry, rz }
|
||||
@ -375,10 +368,7 @@ const jointNamsMap = {
|
||||
*/
|
||||
const getJointState = async (robot) => {
|
||||
try {
|
||||
const res = await getJointStateApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip,
|
||||
})
|
||||
const res = await getJointStateApi()
|
||||
if (res.code === 200) {
|
||||
setTimeout(() => {
|
||||
const names = res.data.name
|
||||
@ -404,8 +394,6 @@ const updateJoint = async (jointName, _dir) => {
|
||||
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
|
||||
@ -475,8 +463,6 @@ const move = async (axis, direction, type) => {
|
||||
}
|
||||
|
||||
speedLApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip,
|
||||
duration: 60,
|
||||
acceleration: type === 'posture' ? postureSpeed.value + 0.1 : endSpeed.value + 0.1,
|
||||
velocity: {
|
||||
@ -489,10 +475,7 @@ const move = async (axis, direction, type) => {
|
||||
* 停止机械臂移动
|
||||
*/
|
||||
const stop = async () => {
|
||||
await stopMotionApi({
|
||||
deviceId: deviceId,
|
||||
ip: robotStore.ip
|
||||
})
|
||||
await stopMotionApi()
|
||||
}
|
||||
|
||||
const activePointerIds = new Set()
|
||||
|
||||
@ -151,14 +151,11 @@ const statusMeta = computed(() => {
|
||||
})
|
||||
|
||||
function microphoneVolumeRequestParams() {
|
||||
return {
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.micDeviceId
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
async function loadMicrophoneVolume({ silent = false } = {}) {
|
||||
if (!robotStore.ip || !robotStore.micDeviceId) return
|
||||
if (!robotStore.robotId) return
|
||||
isMicrophoneVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getMicrophoneVolume(microphoneVolumeRequestParams())
|
||||
@ -194,14 +191,11 @@ async function updateMicrophoneVolume(volume) {
|
||||
}
|
||||
|
||||
function speakerVolumeRequestParams() {
|
||||
return {
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.spkDeviceId
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
async function loadSpeakerVolume({ silent = false } = {}) {
|
||||
if (!robotStore.ip || !robotStore.spkDeviceId) return
|
||||
if (!robotStore.robotId) return
|
||||
isSpeakerVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getSpeakerVolume(speakerVolumeRequestParams())
|
||||
@ -391,9 +385,7 @@ function connectAudioServer(sessionToken) {
|
||||
const timeout = window.setTimeout(() => reject(new Error('连接音频服务超时')), JOIN_TIMEOUT)
|
||||
socket.onopen = () => sendControlMessage({
|
||||
type: 'init',
|
||||
microphoneId: robotStore.micDeviceId,
|
||||
speakerId: robotStore.spkDeviceId,
|
||||
robotAddress: robotStore.ip
|
||||
robotId: robotStore.robotId
|
||||
})
|
||||
socket.onmessage = (event) => {
|
||||
if (typeof event.data !== 'string') {
|
||||
@ -455,7 +447,7 @@ async function startCall() {
|
||||
errorMessage.value = ''
|
||||
const sessionToken = ++callSessionId
|
||||
try {
|
||||
if (!robotStore.ip || !robotStore.micDeviceId || !robotStore.spkDeviceId) {
|
||||
if (!robotStore.robotId) {
|
||||
throw new Error('请先配置机器人地址、麦克风 ID 和扬声器 ID')
|
||||
}
|
||||
// 必须在按钮点击的用户手势中创建,以符合浏览器自动播放策略。
|
||||
|
||||
@ -14,20 +14,10 @@ import VoiceConversation from './VoiceConversation.vue';
|
||||
import { computed } from 'vue';
|
||||
import { useRobotStore } from '@/stores/robot';
|
||||
|
||||
const VIDEO_SERVER_HOST = import.meta.env.VITE_VIDEO_SERVER_HOST || 'http://localhost:8080/'
|
||||
const TerminalId = import.meta.env.VITE_TERMINAL_ID || 'c2f8a06826baf03843925c1a2a13bcfd'
|
||||
|
||||
const robotStore = useRobotStore()
|
||||
const videoUrl = `${VIDEO_SERVER_HOST}/api/edge/camera/rgb-stream/public?terminalId=${TerminalId}&deviceId=${robotStore.cameraDeviceId}`
|
||||
|
||||
// const videoUrl = computed(() => {
|
||||
// if (!robotStore.ip || !robotStore.cameraDeviceId) return ''
|
||||
// const params = new URLSearchParams({
|
||||
// ip: robotStore.ip,
|
||||
// deviceId: robotStore.cameraDeviceId
|
||||
// })
|
||||
// return `/api/camera/video?${params}`
|
||||
// })
|
||||
const videoUrl = computed(() => robotStore.robotId
|
||||
? `/api/camera/video?robotId=${encodeURIComponent(robotStore.robotId)}`
|
||||
: '')
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
|
||||
@ -194,8 +194,6 @@ const run = async (data) => {
|
||||
return
|
||||
}
|
||||
const res = await moveRobot({
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.agvDeviceId,
|
||||
...data
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -210,8 +208,7 @@ function onRelease() {
|
||||
moveRobotData.value.vx = 0;
|
||||
moveRobotData.value.vy = 0;
|
||||
// stopRobot({
|
||||
// ip: robotStore.ip,
|
||||
// deviceId: robotStore.agvDeviceId,
|
||||
// robotId: robotStore.robotId,
|
||||
// })
|
||||
}
|
||||
|
||||
|
||||
@ -487,8 +487,6 @@ const loadSourceMap = async (mapName) => {
|
||||
resizeCanvasToContainer()
|
||||
|
||||
const res = await getCurrentMap({
|
||||
ip: robotStore.ip,
|
||||
deviceId: robotStore.agvDeviceId,
|
||||
mapName: mapName
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -540,7 +538,7 @@ watch(() => props.mapName,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
await loadSourceMap(newVal)
|
||||
addRobot(robotStore.ip, robotStore.ip, robotStore.position.x, robotStore.position.y, robotStore.position.theta, getThemeColor('--color-canvas-robot'))
|
||||
addRobot(robotStore.robotId, robotStore.robotId, robotStore.position.x, robotStore.position.y, robotStore.position.theta, getThemeColor('--color-canvas-robot'))
|
||||
startRendering()
|
||||
}
|
||||
}, {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user