2.22
This commit is contained in:
parent
e3eea4bd54
commit
e8856f4238
@ -22,6 +22,7 @@ class GamepadController {
|
|||||||
this.directionAxis9 = "";
|
this.directionAxis9 = "";
|
||||||
this.angle = 0;
|
this.angle = 0;
|
||||||
this.rightJoystickPressed = false;
|
this.rightJoystickPressed = false;
|
||||||
|
this.speed = 0; // 添加速度属性
|
||||||
// 初始化按钮状态
|
// 初始化按钮状态
|
||||||
this.buttons = this.options.buttonsConfig.map(button => ({
|
this.buttons = this.options.buttonsConfig.map(button => ({
|
||||||
...button,
|
...button,
|
||||||
@ -81,13 +82,19 @@ class GamepadController {
|
|||||||
// 检查是否在死区
|
// 检查是否在死区
|
||||||
if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) {
|
if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) {
|
||||||
this.directionAxis0_1 = "未定义";
|
this.directionAxis0_1 = "未定义";
|
||||||
this.angle = 0; // 在死区时重置角度为0
|
this.angle = 0;
|
||||||
|
this.speed = 0; // 在死区时速度为0
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算速度(到原点的距离)
|
||||||
|
// 使用勾股定理计算距离,并将结果限制在0-100之间
|
||||||
|
const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
|
||||||
|
this.speed = Math.round(Math.min(distance * 100, 100));
|
||||||
|
|
||||||
// 计算方向角度(0-360度)
|
// 计算方向角度(0-360度)
|
||||||
let angle = Math.atan2(axis1, axis0) * (180 / Math.PI);
|
let angle = Math.atan2(axis1, axis0) * (180 / Math.PI);
|
||||||
angle = (angle + 360) % 360; // 确保角度在 0-360 范围内
|
angle = (angle + 360) % 360;
|
||||||
angle = Math.round(angle);
|
angle = Math.round(angle);
|
||||||
this.angle = angle;
|
this.angle = angle;
|
||||||
|
|
||||||
|
163
src/assets/js/audio-transmitter.js
Normal file
163
src/assets/js/audio-transmitter.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="switch-item">
|
<div class="switch-item">
|
||||||
<span>避障</span>
|
<span>避障</span>
|
||||||
<el-switch v-model="obstacleStatus.top" />
|
<el-switch v-model="obstacleAvoidEnabled" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,6 +80,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import GamepadController from '../assets/js/GamepadController';
|
import GamepadController from '../assets/js/GamepadController';
|
||||||
|
import audioTransmitter from '../assets/js/audio-transmitter';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CarControl',
|
name: 'CarControl',
|
||||||
@ -92,6 +93,7 @@ export default {
|
|||||||
top: false,
|
top: false,
|
||||||
bottom: false
|
bottom: false
|
||||||
},
|
},
|
||||||
|
obstacleAvoidEnabled: false,
|
||||||
gamepadController: null,
|
gamepadController: null,
|
||||||
directionAxis0_1: "",
|
directionAxis0_1: "",
|
||||||
directionAxis9: "",
|
directionAxis9: "",
|
||||||
@ -105,8 +107,11 @@ export default {
|
|||||||
sendInterval: null,
|
sendInterval: null,
|
||||||
lastDirection: 0,
|
lastDirection: 0,
|
||||||
lastSpeed: 0,
|
lastSpeed: 0,
|
||||||
platform_fun:0,
|
platform_fun: 0,
|
||||||
button:"",
|
button: "",
|
||||||
|
audioConfig: audioTransmitter.data(),
|
||||||
|
isRecording: false,
|
||||||
|
audioHandler: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -136,6 +141,12 @@ export default {
|
|||||||
this.sendInterval = setInterval(() => {
|
this.sendInterval = setInterval(() => {
|
||||||
this.sendControlData();
|
this.sendControlData();
|
||||||
}, 1000); // 每100ms发送一次
|
}, 1000); // 每100ms发送一次
|
||||||
|
|
||||||
|
// 初始化音频处理器
|
||||||
|
this.audioHandler = {
|
||||||
|
...audioTransmitter.data(),
|
||||||
|
...audioTransmitter.methods
|
||||||
|
};
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
// 组件销毁前清理资源
|
// 组件销毁前清理资源
|
||||||
@ -157,6 +168,11 @@ export default {
|
|||||||
if (this.sendInterval) {
|
if (this.sendInterval) {
|
||||||
clearInterval(this.sendInterval);
|
clearInterval(this.sendInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保停止音频传输
|
||||||
|
if (this.audioHandler && this.audioHandler.isRecording) {
|
||||||
|
this.audioHandler.stopRecording();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goto(path) {
|
goto(path) {
|
||||||
@ -181,41 +197,74 @@ export default {
|
|||||||
},
|
},
|
||||||
handleControlPress() {
|
handleControlPress() {
|
||||||
this.isControlPressed = true;
|
this.isControlPressed = true;
|
||||||
|
// 开始音频传输
|
||||||
|
console.log('开始音频传输...');
|
||||||
|
if (this.audioHandler) {
|
||||||
|
this.audioHandler.startRecording();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleControlRelease() {
|
handleControlRelease() {
|
||||||
this.isControlPressed = false;
|
this.isControlPressed = false;
|
||||||
|
// 停止音频传输
|
||||||
|
console.log('停止音频传输...');
|
||||||
|
if (this.audioHandler) {
|
||||||
|
this.audioHandler.stopRecording();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
initWebSocket() {
|
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.ws.onopen = () => {
|
||||||
this.lastResponseTime = Date.now(); // 更新最后响应时间
|
console.log('WebSocket 已连接');
|
||||||
try {
|
this.wsConnected = true;
|
||||||
const response = JSON.parse(event.data);
|
this.lastResponseTime = Date.now();
|
||||||
if (response.JSON_id === 1 && response.rc_car_card) {
|
this.sendCarInfo();
|
||||||
this.updateCarStatus(response.rc_car_card);
|
};
|
||||||
|
|
||||||
|
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 = () => {
|
this.ws.onclose = (event) => {
|
||||||
console.log('WebSocket 已断开');
|
console.log('WebSocket 已断开, code:', event.code, 'reason:', event.reason);
|
||||||
this.wsConnected = false;
|
this.wsConnected = false;
|
||||||
};
|
// 尝试重新连接
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('尝试重新连接...');
|
||||||
|
this.initWebSocket();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
this.ws.onerror = (error) => {
|
||||||
console.error('WebSocket 错误:', error);
|
console.error('WebSocket 错误:', error);
|
||||||
this.wsConnected = false;
|
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 angle = this.gamepadController.angle;
|
||||||
const directionAxis9 = this.gamepadController.directionAxis9;
|
const directionAxis9 = this.gamepadController.directionAxis9;
|
||||||
|
const speed = this.gamepadController.speed; // 获取速度值
|
||||||
|
|
||||||
// 更新控制按钮状态
|
// 更新控制按钮状态
|
||||||
|
const wasPressed = this.isControlPressed;
|
||||||
this.isControlPressed = this.gamepadController.rightJoystickPressed;
|
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.lastDirection = angle;
|
||||||
this.platform_fun = directionAxis9;
|
this.platform_fun = directionAxis9;
|
||||||
this.lastSpeed = 0;
|
this.lastSpeed = speed; // 更新速度值
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendCarInfo() {
|
sendCarInfo() {
|
||||||
@ -240,8 +306,6 @@ export default {
|
|||||||
"rc_car_card": {
|
"rc_car_card": {
|
||||||
"get_info": 1,
|
"get_info": 1,
|
||||||
"get_attribute": 1,
|
"get_attribute": 1,
|
||||||
"get_function": 1,
|
|
||||||
"get_driver": 1,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -263,19 +327,23 @@ export default {
|
|||||||
if (data.attribute) {
|
if (data.attribute) {
|
||||||
// 更新电池状态
|
// 更新电池状态
|
||||||
this.batteryLevel = data.attribute.bat_quantity;
|
this.batteryLevel = data.attribute.bat_quantity;
|
||||||
// 更新障碍物状态
|
|
||||||
|
// 更新障碍物显示状态
|
||||||
this.obstacleStatus = {
|
this.obstacleStatus = {
|
||||||
top: data.attribute.obstacle_sta === 0,
|
top: data.attribute.obstacle_sta === 0,
|
||||||
bottom: data.attribute.obstacle_sta === 1
|
bottom: data.attribute.obstacle_sta === 1
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (data.function) {
|
|
||||||
// 更新开关状态
|
// 更新开关状态
|
||||||
this.screenStatus = data.function.screen_en === 1;
|
this.platform_fun = data.attribute.platform;
|
||||||
this.warningLightStatus = data.function.warn_light_en === 1;
|
this.screenStatus = data.attribute.screen_en === 1;
|
||||||
this.followStatus = data.function.follow_en === 1;
|
this.warningLightStatus = data.attribute.warn_light_en === 1;
|
||||||
this.obstacleStatus = data.function.obstacle_avoid_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 = {
|
const controlData = {
|
||||||
"JSON_id": 1,
|
"JSON_id": 1,
|
||||||
"rc_car_card": {
|
"rc_car_card": {
|
||||||
"get_info": 1,
|
"attribute": {
|
||||||
"get_attribute": 1,
|
"platform": this.platform_fun,
|
||||||
"function": {
|
|
||||||
"platform_fun": this.platform_fun,
|
|
||||||
"screen_en": this.screenStatus ? 1 : 0,
|
"screen_en": this.screenStatus ? 1 : 0,
|
||||||
"warn_light_en": this.warningLightStatus ? 1 : 0,
|
"warn_light_en": this.warningLightStatus ? 1 : 0,
|
||||||
"follow_en": this.followStatus ? 1 : 0,
|
"follow_en": this.followStatus ? 1 : 0,
|
||||||
"obstacle_avoid_en": this.obstacleStatus.top ? 1 : 0
|
"obstacle_avoid_en": this.obstacleAvoidEnabled ? 1 : 0,
|
||||||
},
|
"car_speed": this.lastSpeed,
|
||||||
"driver": {
|
"car_direction": this.lastDirection
|
||||||
"director": this.lastDirection, // 从手柄获取的角度
|
|
||||||
"speed": this.lastSpeed // 从手柄获取的速度
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -546,4 +610,4 @@ export default {
|
|||||||
.voice-icon-img.show-voice {
|
.voice-icon-img.show-voice {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user