This commit is contained in:
JayJiaJun 2025-02-22 21:04:37 +08:00
parent e3eea4bd54
commit e8856f4238
3 changed files with 286 additions and 52 deletions

View File

@ -22,6 +22,7 @@ class GamepadController {
this.directionAxis9 = "";
this.angle = 0;
this.rightJoystickPressed = false;
this.speed = 0; // 添加速度属性
// 初始化按钮状态
this.buttons = this.options.buttonsConfig.map(button => ({
...button,
@ -81,13 +82,19 @@ class GamepadController {
// 检查是否在死区
if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) {
this.directionAxis0_1 = "未定义";
this.angle = 0; // 在死区时重置角度为0
this.angle = 0;
this.speed = 0; // 在死区时速度为0
return;
}
// 计算速度(到原点的距离)
// 使用勾股定理计算距离并将结果限制在0-100之间
const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
this.speed = Math.round(Math.min(distance * 100, 100));
// 计算方向角度0-360度
let angle = Math.atan2(axis1, axis0) * (180 / Math.PI);
angle = (angle + 360) % 360; // 确保角度在 0-360 范围内
angle = (angle + 360) % 360;
angle = Math.round(angle);
this.angle = angle;

View File

@ -0,0 +1,163 @@
// audio-transmitter.js
export default {
data() {
return {
ws: null, // WebSocket 连接
mediaRecorder: null, // MediaRecorder 实例
audioChunks: [], // 音频数据
status: '点击按钮开始传输音频',
isRecording: false, // 用于判断是否在录音
audioConfig: {
sampleRate: 44100, // 固定为 44100Hz
bitsPerSample: 16, // 固定为 16 位深度
channels: 2 // 固定为双声道
},
stream: null,
audioContext: null,
worklet: null,
lastSendTime: 0,
sendInterval: 200, // 数据传输间隔(毫秒)
audioWs: null, // 音频 WebSocket 连接
};
},
mounted() {
// 初始化 WebSocket 连接
this.ws = new WebSocket('ws://192.168.4.103/ws'); // 替换为 ESP32 IP 地址
this.ws.onopen = () => {
console.log('WebSocket 连接已打开');
};
this.ws.onmessage = (event) => {
console.log(' 接收到来自 ESP32 的消息:', event.data);
};
this.ws.onclose = () => {
console.log('WebSocket 连接已关闭');
};
},
methods: {
toggleRecording() {
if (this.isRecording) {
this.stopRecording();
} else {
this.startRecording();
}
},
async startRecording() {
console.log('音频传输器: 开始录音');
this.status = '开始录音并传输音频...';
this.isRecording = true;
const constraints = {
audio: {
sampleRate: this.audioConfig.sampleRate,
channelCount: this.audioConfig.channels,
sampleSize: this.audioConfig.bitsPerSample
}
};
console.log('使用的音频配置:', constraints);
try {
// 初始化音频 WebSocket 连接
this.audioWs = new WebSocket('ws://192.168.1.60:81');
// 等待 WebSocket 连接成功
await new Promise((resolve, reject) => {
this.audioWs.onopen = () => {
console.log('音频 WebSocket 已连接');
resolve();
};
this.audioWs.onerror = (error) => {
console.error('音频 WebSocket 错误:', error);
reject(error);
};
});
// WebSocket 连接成功后,开始获取麦克风权限
const stream = await navigator.mediaDevices.getUserMedia(constraints);
console.log('成功获取麦克风权限');
// 创建 AudioContext
const audioContext = new AudioContext({
sampleRate: this.audioConfig.sampleRate
});
// 加载 AudioWorklet
await audioContext.audioWorklet.addModule('audio-processor.js');
const source = audioContext.createMediaStreamSource(stream);
const worklet = new AudioWorkletNode(audioContext, 'audio-processor');
// 监听来自 worklet 的消息
worklet.port.onmessage = (event) => {
if (this.audioWs && this.audioWs.readyState === WebSocket.OPEN) {
const currentTime = Date.now();
if (!this.lastSendTime || currentTime - this.lastSendTime > this.sendInterval) {
const pcmData = new Int16Array(event.data);
console.log('发送的音频数据:', Array.from(pcmData.slice(0, 20)));
this.audioWs.send(event.data);
this.lastSendTime = currentTime;
}
}
};
source.connect(worklet);
worklet.connect(audioContext.destination);
// 保存引用以便后续停止
this.stream = stream;
this.audioContext = audioContext;
this.worklet = worklet;
} catch (error) {
console.error('音频初始化失败:', error);
// 清理音频相关资源
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
if (this.worklet) {
this.worklet.disconnect();
}
if (this.audioContext) {
this.audioContext.close();
}
if (this.audioWs) {
this.audioWs.close();
this.audioWs = null;
}
this.isRecording = false;
// 不抛出错误,让程序继续执行
return;
}
},
stopRecording() {
console.log('音频传输器: 停止录音');
this.status = '停止录音';
this.isRecording = false;
// 关闭音频资源
if (this.stream) {
console.log('关闭音频流');
this.stream.getTracks().forEach(track => track.stop());
}
if (this.worklet) {
console.log('断开 AudioWorklet');
this.worklet.disconnect();
}
if (this.audioContext) {
console.log('关闭音频上下文');
this.audioContext.close();
}
// 关闭音频 WebSocket 连接
if (this.audioWs) {
console.log('关闭音频 WebSocket 连接');
this.audioWs.close();
this.audioWs = null;
}
}
}
};

View File

@ -34,7 +34,7 @@
</div>
<div class="switch-item">
<span>避障</span>
<el-switch v-model="obstacleStatus.top" />
<el-switch v-model="obstacleAvoidEnabled" />
</div>
</div>
@ -80,6 +80,7 @@
<script>
import { useRouter } from 'vue-router';
import GamepadController from '../assets/js/GamepadController';
import audioTransmitter from '../assets/js/audio-transmitter';
export default {
name: 'CarControl',
@ -92,6 +93,7 @@ export default {
top: false,
bottom: false
},
obstacleAvoidEnabled: false,
gamepadController: null,
directionAxis0_1: "",
directionAxis9: "",
@ -105,8 +107,11 @@ export default {
sendInterval: null,
lastDirection: 0,
lastSpeed: 0,
platform_fun:0,
button:"",
platform_fun: 0,
button: "",
audioConfig: audioTransmitter.data(),
isRecording: false,
audioHandler: null
}
},
created() {
@ -136,6 +141,12 @@ export default {
this.sendInterval = setInterval(() => {
this.sendControlData();
}, 1000); // 100ms
//
this.audioHandler = {
...audioTransmitter.data(),
...audioTransmitter.methods
};
},
beforeDestroy() {
//
@ -157,6 +168,11 @@ export default {
if (this.sendInterval) {
clearInterval(this.sendInterval);
}
//
if (this.audioHandler && this.audioHandler.isRecording) {
this.audioHandler.stopRecording();
}
},
methods: {
goto(path) {
@ -181,41 +197,74 @@ export default {
},
handleControlPress() {
this.isControlPressed = true;
//
console.log('开始音频传输...');
if (this.audioHandler) {
this.audioHandler.startRecording();
}
},
handleControlRelease() {
this.isControlPressed = false;
//
console.log('停止音频传输...');
if (this.audioHandler) {
this.audioHandler.stopRecording();
}
},
initWebSocket() {
this.ws = new WebSocket('ws://192.168.1.60:81');
try {
console.log('正在连接 WebSocket...');
this.ws = new WebSocket('ws://192.168.4.120/ws');
// this.ws = new WebSocket('ws://192.168.1.60:81');
this.ws.onopen = () => {
console.log('WebSocket 已连接');
this.wsConnected = true;
this.lastResponseTime = Date.now();
this.sendCarInfo();
};
this.ws.onmessage = (event) => {
this.lastResponseTime = Date.now(); //
try {
const response = JSON.parse(event.data);
if (response.JSON_id === 1 && response.rc_car_card) {
this.updateCarStatus(response.rc_car_card);
this.ws.onopen = () => {
console.log('WebSocket 已连接');
this.wsConnected = true;
this.lastResponseTime = Date.now();
this.sendCarInfo();
};
this.ws.onmessage = (event) => {
this.lastResponseTime = Date.now();
try {
const response = JSON.parse(event.data);
console.log('收到数据:', JSON.stringify(response));
if (response.JSON_id === 1 && response.rc_car_card) {
this.updateCarStatus(response.rc_car_card);
}
} catch (error) {
console.error('解析响应数据失败:', error);
}
} catch (error) {
console.error('解析响应数据失败:', error);
}
};
};
this.ws.onclose = () => {
console.log('WebSocket 已断开');
this.wsConnected = false;
};
this.ws.onclose = (event) => {
console.log('WebSocket 已断开, code:', event.code, 'reason:', event.reason);
this.wsConnected = false;
//
setTimeout(() => {
console.log('尝试重新连接...');
this.initWebSocket();
}, 3000);
};
this.ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
this.wsConnected = false;
};
this.ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
this.wsConnected = false;
//
if (error.message) {
console.error('错误信息:', error.message);
}
};
} catch (error) {
console.error('WebSocket 初始化失败:', error);
//
setTimeout(() => {
console.log('尝试重新连接...');
this.initWebSocket();
}, 3000);
}
},
//
@ -224,14 +273,31 @@ export default {
//
const angle = this.gamepadController.angle;
const directionAxis9 = this.gamepadController.directionAxis9;
const speed = this.gamepadController.speed; //
//
const wasPressed = this.isControlPressed;
this.isControlPressed = this.gamepadController.rightJoystickPressed;
//
if (!wasPressed && this.isControlPressed) {
//
console.log('手柄按钮按下,开始音频传输...');
if (this.audioHandler) {
this.audioHandler.startRecording();
}
} else if (wasPressed && !this.isControlPressed) {
//
console.log('手柄按钮释放,停止音频传输...');
if (this.audioHandler) {
this.audioHandler.stopRecording();
}
}
//
this.lastDirection = angle;
this.platform_fun = directionAxis9;
this.lastSpeed = 0;
this.lastSpeed = speed; //
}
},
sendCarInfo() {
@ -240,8 +306,6 @@ export default {
"rc_car_card": {
"get_info": 1,
"get_attribute": 1,
"get_function": 1,
"get_driver": 1,
}
};
@ -263,19 +327,23 @@ export default {
if (data.attribute) {
//
this.batteryLevel = data.attribute.bat_quantity;
//
//
this.obstacleStatus = {
top: data.attribute.obstacle_sta === 0,
bottom: data.attribute.obstacle_sta === 1
};
}
if (data.function) {
//
this.screenStatus = data.function.screen_en === 1;
this.warningLightStatus = data.function.warn_light_en === 1;
this.followStatus = data.function.follow_en === 1;
this.obstacleStatus = data.function.obstacle_avoid_en === 1;
this.platform_fun = data.attribute.platform;
this.screenStatus = data.attribute.screen_en === 1;
this.warningLightStatus = data.attribute.warn_light_en === 1;
this.followStatus = data.attribute.follow_en === 1;
this.obstacleAvoidEnabled = data.attribute.obstacle_avoid_en === 1;
//
this.lastSpeed = data.attribute.car_speed;
this.lastDirection = data.attribute.car_direction;
}
},
@ -294,18 +362,14 @@ export default {
const controlData = {
"JSON_id": 1,
"rc_car_card": {
"get_info": 1,
"get_attribute": 1,
"function": {
"platform_fun": this.platform_fun,
"attribute": {
"platform": this.platform_fun,
"screen_en": this.screenStatus ? 1 : 0,
"warn_light_en": this.warningLightStatus ? 1 : 0,
"follow_en": this.followStatus ? 1 : 0,
"obstacle_avoid_en": this.obstacleStatus.top ? 1 : 0
},
"driver": {
"director": this.lastDirection, //
"speed": this.lastSpeed //
"obstacle_avoid_en": this.obstacleAvoidEnabled ? 1 : 0,
"car_speed": this.lastSpeed,
"car_direction": this.lastDirection
}
}
};