refactor: 按机器人动态解析gRPC端点与设备

This commit is contained in:
zhanghao 2026-08-14 17:31:21 +08:00
parent 5b294f6d37
commit 92bd36c705
13 changed files with 130 additions and 212 deletions

View File

@ -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())

View File

@ -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

View File

@ -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', () => {

View File

@ -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 || ''
}

View File

@ -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')
@ -23,4 +23,4 @@ export function moveRobot(params) {
export function stopRobot(params) {
return request('/api/agv/stopRobot', params, 'POST')
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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')
}
// 必须在按钮点击的用户手势中创建,以符合浏览器自动播放策略。

View File

@ -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 {

View File

@ -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,
// })
}

View File

@ -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()
}
}, {