Compare commits

...

2 Commits

6 changed files with 216 additions and 20 deletions

View File

@ -12,5 +12,8 @@ VITE_WS_URL = ws://192.168.0.222:13080/ws
# explanation # explanation
VITE_INSPECTION_TYPE = "inspection" VITE_INSPECTION_TYPE = "inspection"
VITE_INSPECTION_ARM_DEVICEID = "huayan_arm" VITE_INSPECTION_ARM_DEVICE_ID = "huayan_arm"
VITE_INSPECTION_AGV_DEVICEID = "huayan_arm" VITE_INSPECTION_AGV_DEVICE_ID = "src1100"
VITE_AUDIO_SERVER_URL = "ws://192.168.0.222:13080/ws/audio"
VITE_SPEAKER_DEVICE_ID = "spk1"
VITE_MIC_DEVICE_ID = "mic1"

View File

@ -54,4 +54,44 @@ export function speedJApi(data) {
method: 'post', method: 'post',
data: data data: data
}) })
}
export function clearFault(params) {
return request({
url: '/api/arm/clearFault',
method: 'get',
params: params
})
}
export function getMicVolume(params) {
return request({
url: '/api/Microphone/volume',
method: 'get',
params: params
})
}
export function setMicVolume(data) {
return request({
url: '/api/Microphone/volume',
method: 'post',
data: data
})
}
export function getSpeakerVolume(params) {
return request({
url: '/api/Speaker/volume',
method: 'get',
params: params
})
}
export function setSpeakerVolume(data) {
return request({
url: '/api/Speaker/volume',
method: 'post',
data: data
})
} }

View File

@ -11,7 +11,7 @@
</el-button> </el-button>
</template> </template>
<div class="form__container"> <div class="form__container">
<el-button type="primary" v-if="['AGV_MOVE_TO_POINT', 'ALM'].includes(props.data.properties.action)" size="small" @click="openRobotForm(props.data.properties.action)">获取位置</el-button> <el-button type="primary" v-if="['AGV_MOVE_TO_POINT', 'ARM_MOVE_TO_POINT'].includes(props.data.properties.action)" size="small" @click="openRobotForm(props.data.properties.action)">获取位置</el-button>
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicFormRef" label-position="top" <el-form :inline="true" :model="formData" :rules="rules" ref="dynamicFormRef" label-position="top"
label-width="auto"> label-width="auto">
<div v-for="(property, index) in formData.nodeParams" :key="index"> <div v-for="(property, index) in formData.nodeParams" :key="index">

View File

@ -135,8 +135,8 @@
<div class="speed-container"> <div class="speed-container">
<div class="label">速度</div> <div class="label">速度</div>
<el-slider v-model="speed" :min="0.1" :step="0.1" :max="4" /> <el-slider v-model="speed" :min="0.01" :step="0.01" :max="1" />
<div class="proportion">{{( speed / 4 * 100).toFixed(0) }} %</div> <div class="proportion">{{( speed / 1 * 100).toFixed(0) }} %</div>
</div> </div>
</div> </div>
</template> </template>
@ -174,7 +174,7 @@ const errorNum = ref(0)
const getPose = async (robot) => { const getPose = async (robot) => {
try { try {
const res = await getPoseApi({ const res = await getPoseApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: robot.terminalId terminalId: robot.terminalId
}) })
if (res.code === 200) { if (res.code === 200) {
@ -249,7 +249,7 @@ const move = async (axis, direction) => {
} }
speedLApi({ speedLApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: props.robot.terminalId, terminalId: props.robot.terminalId,
duration: 60, duration: 60,
acceleration: speed.value + 0.1, acceleration: speed.value + 0.1,
@ -262,7 +262,7 @@ const move = async (axis, direction) => {
*/ */
const stop = async () => { const stop = async () => {
await stopMotionApi({ await stopMotionApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: props.robot.terminalId terminalId: props.robot.terminalId
}) })
} }

View File

@ -52,6 +52,34 @@
</el-button> </el-button>
</div> </div>
<div class="robot-volume">
<span class="robot-volume__label">机器人麦克风音量</span>
<el-slider
v-model="microphoneVolume"
class="robot-volume__slider"
:min="0"
:max="100"
:disabled="isMicrophoneVolumeLoading"
:show-tooltip="true"
@change="updateMicrophoneVolume"
/>
<span class="robot-volume__value">{{ microphoneVolume }}%</span>
</div>
<div class="robot-volume">
<span class="robot-volume__label">机器人扬声器音量</span>
<el-slider
v-model="speakerVolume"
class="robot-volume__slider"
:min="0"
:max="100"
:disabled="isSpeakerVolumeLoading"
:show-tooltip="true"
@change="updateSpeakerVolume"
/>
<span class="robot-volume__value">{{ speakerVolume }}%</span>
</div>
<section class="status-card"> <section class="status-card">
<div class="status-copy"> <div class="status-copy">
<p>当前状态<strong>{{ statusMeta.label }}</strong></p> <p>当前状态<strong>{{ statusMeta.label }}</strong></p>
@ -72,12 +100,13 @@
<script setup> <script setup>
import { computed, onBeforeUnmount, ref } from 'vue' import { computed, onBeforeUnmount, ref } from 'vue'
import { ElMessage, ElNotification } from 'element-plus' import { ElMessage, ElNotification } from 'element-plus'
import { setMicVolume, getMicVolume, setSpeakerVolume, getSpeakerVolume } from '@/api/inspection/cockpit'
// WebSocket JSON PCM使 WebRTCSDP ICE // WebSocket JSON PCM使 WebRTCSDP ICE
const AUDIO_SERVER_URL = 'ws://192.168.1.222:13080/ws/audio' const AUDIO_SERVER_URL = import.meta.env.VITE_AUDIO_SERVER_URL
const TERMINAL_ID = 'c2f8a06826baf03843925c1a2a13bcfd' const TERMINAL_ID = 'c2f8a06826baf03843925c1a2a13bcfd'
const DEVICE_ID = '1234' const speakerDeviceId = import.meta.env.VITE_SPEAKER_DEVICE_ID
const OPERATOR_ID = 'user001' const micDeviceId = import.meta.env.VITE_MIC_DEVICE_ID
// PCM S16LE48kHz10ms // PCM S16LE48kHz10ms
const PCM_SAMPLE_RATE = 48000 const PCM_SAMPLE_RATE = 48000
@ -296,8 +325,8 @@ function connectAudioServer(sessionToken) {
sendControlMessage({ sendControlMessage({
type: 'join', type: 'join',
terminalId: TERMINAL_ID, terminalId: TERMINAL_ID,
deviceId: DEVICE_ID, speakerDeviceId: speakerDeviceId,
operatorId: OPERATOR_ID micDeviceId: micDeviceId
}) })
} }
@ -613,6 +642,96 @@ function endCall() {
ElMessage.success('通话已结束') ElMessage.success('通话已结束')
} }
const microphoneVolume = ref(50)
const speakerVolume = ref(50)
const isMicrophoneVolumeLoading = ref(false)
const isSpeakerVolumeLoading = ref(false)
let confirmedMicrophoneVolume = 50
let confirmedSpeakerVolume = 50
async function updateMicrophoneVolume(volume) {
const previousVolume = confirmedMicrophoneVolume
isMicrophoneVolumeLoading.value = true
try {
const response = await setMicVolume({
deviceId: micDeviceId,
terminalId: TERMINAL_ID,
volume: Math.round(volume)
})
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
microphoneVolume.value = volume
confirmedMicrophoneVolume = volume
ElMessage.success(`机器人麦克风音量已设置为 ${volume}%`)
} catch (error) {
microphoneVolume.value = previousVolume
ElMessage.error(error.message || '设置机器人音量失败')
} finally {
isMicrophoneVolumeLoading.value = false
}
}
async function updateSpeakerVolume(volume) {
const previousVolume = confirmedSpeakerVolume
isSpeakerVolumeLoading.value = true
try {
const response = await setSpeakerVolume({
deviceId: speakerDeviceId,
terminalId: TERMINAL_ID,
volume: Math.round(volume)
})
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
speakerVolume.value = volume
confirmedSpeakerVolume = volume
ElMessage.success(`机器人扬声器音量已设置为 ${volume}%`)
} catch (error) {
speakerVolume.value = previousVolume
ElMessage.error(error.message || '设置扬声器音量失败')
} finally {
isSpeakerVolumeLoading.value = false
}
}
async function loadMicrophoneVolume({ silent = false } = {}) {
isMicrophoneVolumeLoading.value = true
try {
const response = await getMicVolume({
deviceId: micDeviceId,
terminalId: TERMINAL_ID
})
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
microphoneVolume.value = volume
confirmedMicrophoneVolume = volume
} catch (error) {
if (!silent) ElMessage.error(error.message || '获取机器人音量失败')
} finally {
isMicrophoneVolumeLoading.value = false
}
}
async function loadSpeakerVolume({ silent = false } = {}) {
isSpeakerVolumeLoading.value = true
try {
const response = await getSpeakerVolume({
deviceId: speakerDeviceId,
terminalId: TERMINAL_ID
})
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
speakerVolume.value = volume
confirmedSpeakerVolume = volume
} catch (error) {
if (!silent) ElMessage.error(error.message || '获取扬声器音量失败')
} finally {
isSpeakerVolumeLoading.value = false
}
}
onMounted(() => {
loadMicrophoneVolume()
loadSpeakerVolume()
})
// AudioWorklet WebSocket // AudioWorklet WebSocket
onBeforeUnmount(cleanupResources) onBeforeUnmount(cleanupResources)
</script> </script>
@ -843,6 +962,30 @@ $panel-border: rgb(112 192 255 / 24%);
:deep(.el-button + .el-button) { margin-left: 0; } :deep(.el-button + .el-button) { margin-left: 0; }
} }
.robot-volume {
display: grid;
grid-template-columns: auto minmax(120px, 210px) 42px;
align-items: center;
gap: 12px;
width: min(100%, 430px);
margin-top: 10px;
color: #badeff;;
font-size: 11px;
&__label { white-space: nowrap; }
&__value { color: #fff; text-align: right; font-variant-numeric: tabular-nums; }
:deep(.el-slider__runway) { background-color: #dff7ff; }
:deep(.el-slider__bar) { background: linear-gradient(90deg, #46c8ff, #936cff); }
:deep(.el-slider__button) {
width: 14px;
height: 14px;
border-color: #46c8ff;
background: #dff7ff;
box-shadow: 0 0 10px rgb(70 200 255 / 65%);
}
}
:deep(.call-button) { :deep(.call-button) {
width: 178px; width: 178px;
height: 34px; height: 34px;

View File

@ -82,8 +82,8 @@
</div> </div>
<div class="speed-container"> <div class="speed-container">
<div class="label">速度</div> <div class="label">速度</div>
<el-slider v-model="jointSpeed" :min="0.1" :step="0.1" :max="9" /> <el-slider v-model="jointSpeed" :min="0.01" :step="0.01" :max="1" />
<div class="proportion">{{( jointSpeed / 9 * 100).toFixed(0) }} %</div> <div class="proportion">{{( jointSpeed / 1 * 100).toFixed(0) }} %</div>
</div> </div>
</div> </div>
</div> </div>
@ -119,7 +119,7 @@ import UrdfViewer from './UrdfView.vue'
import DirectionControl from './DirectionControl.vue' import DirectionControl from './DirectionControl.vue'
import RoboticArm from "./RoboticArm.vue"; import RoboticArm from "./RoboticArm.vue";
import { getRobotList } from '@/api/inspection/robot' import { getRobotList } from '@/api/inspection/robot'
import { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi } from '@/api/inspection/cockpit' import { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, clearFault } from '@/api/inspection/cockpit'
import { Remove, CirclePlus } from '@element-plus/icons-vue' import { Remove, CirclePlus } from '@element-plus/icons-vue'
import VoiceConversation from "./VoiceConversation.vue"; import VoiceConversation from "./VoiceConversation.vue";
import IPlayer from "@/components/IPlayer/index.vue"; import IPlayer from "@/components/IPlayer/index.vue";
@ -178,8 +178,14 @@ const handlerController = (value) => {
if (value === 2) { if (value === 2) {
// 使 // 使
torqueOnApi({ torqueOnApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId terminalId: activeBotData.value.terminalId
}).catch(error => {
console.error('开启机械臂使能失败:', error)
clearFault({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId
})
}) })
jointControls.value = urdfViewerRef.value.jointControls jointControls.value = urdfViewerRef.value.jointControls
if (animationId.value) { if (animationId.value) {
@ -188,6 +194,10 @@ const handlerController = (value) => {
animationId.value = setInterval(() => { animationId.value = setInterval(() => {
getJointState(activeBotData.value) getJointState(activeBotData.value)
}, 100) }, 100)
} else {
if (animationId.value) {
clearInterval(animationId.value)
}
} }
} }
@ -209,7 +219,7 @@ const errCount = ref(0)
const getJointState = async (robot) => { const getJointState = async (robot) => {
try { try {
const res = await getJointStateApi({ const res = await getJointStateApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: robot.terminalId terminalId: robot.terminalId
}) })
if (res.code === 200) { if (res.code === 200) {
@ -246,7 +256,7 @@ const updateJoint = async (jointName, _dir) => {
const index = jointControls.value.findIndex(joint => joint.name === jointName) const index = jointControls.value.findIndex(joint => joint.name === jointName)
velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1) velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1)
const res = await speedJApi({ const res = await speedJApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId, terminalId: activeBotData.value.terminalId,
acceleration: jointSpeed.value + 0.1, acceleration: jointSpeed.value + 0.1,
duration: 60, duration: 60,
@ -259,7 +269,7 @@ const updateJoint = async (jointName, _dir) => {
*/ */
const stop = async () => { const stop = async () => {
await stopMotionApi({ await stopMotionApi({
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICEID, deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId terminalId: activeBotData.value.terminalId
}) })
} }