GateWay/src/views/CarControl.vue

642 lines
16 KiB
Vue
Raw Normal View History

2025-02-17 18:44:53 +08:00
<template>
<div class="control-panel">
<!-- 顶部状态栏 -->
<div class="status-bar">
<div class="placeholder-div"></div>
<div class="title-container">
<div class="title-box">
遥控车当前状态
2025-02-19 07:46:51 +08:00
<img src="../assets/img/shout.png" alt="voice" class="voice-icon-img"
:class="{ 'show-voice': isControlPressed }" style="width: 20px; height: 20px; margin-left: 20px;">
2025-02-17 18:44:53 +08:00
</div>
</div>
<div class="top-right-icons">
2025-02-19 07:46:51 +08:00
<img :src="connectionImage" alt="link" class="top-icon">
2025-03-05 11:40:40 +08:00
<!-- 注释掉电池图标 -->
<!-- <img :src="batteryImage" alt="battery" class="top-icon"> -->
2025-02-17 18:44:53 +08:00
</div>
</div>
<!-- 主要内容区域 -->
<div class="main-content">
<!-- 左侧控制开关 -->
<div class="left-controls">
<div class="switch-item">
<span>屏幕</span>
<el-switch v-model="screenStatus" />
</div>
<div class="switch-item">
<span>警灯</span>
<el-switch v-model="warningLightStatus" />
</div>
<div class="switch-item">
<span>跟随</span>
<el-switch v-model="followStatus" />
</div>
<div class="switch-item">
<span>避障</span>
2025-02-22 21:04:37 +08:00
<el-switch v-model="obstacleAvoidEnabled" />
2025-02-17 18:44:53 +08:00
</div>
</div>
<!-- 中间车辆状态显示区域 -->
<div class="center-display">
<div class="status-text">云台状态</div>
<div class="car-display">
2025-02-19 07:46:51 +08:00
<div class="boom-container">
<img src="../assets/img/boom.png" alt="boom" class="boom-image top-boom" v-show="obstacleStatus.top">
<img src="../assets/img/car.png" alt="car" class="car-image" />
<img src="../assets/img/boom.png" alt="boom" class="boom-image bottom-boom" v-show="obstacleStatus.bottom">
</div>
2025-02-17 18:44:53 +08:00
</div>
</div>
<!-- 右侧状态图标 -->
<div class="right-status">
<div class="status-icons">
<div class="icons-row">
<div class="icon-item">
2025-02-19 07:46:51 +08:00
<img src="../assets/img/refresh.png" alt="refresh" @mousedown="handleRefresh"
style="width: 40px; height: 40px;margin-right: 20px;">
2025-02-17 18:44:53 +08:00
</div>
<div class="icon-item">
2025-02-19 07:46:51 +08:00
<img src="../assets/img/mp3.png" alt="play" @click="goto('voiceset')"
style="width: 40px; height: 40px;margin-left: 20px;">
2025-02-17 18:44:53 +08:00
</div>
</div>
</div>
<div class="control-button">
2025-02-19 07:46:51 +08:00
<div class="circle-border" :class="{ 'pressed': isControlPressed }" @mousedown="handleControlPress"
@mouseup="handleControlRelease" @mouseleave="handleControlRelease" @touchstart="handleControlPress"
@touchend="handleControlRelease" @touchcancel="handleControlRelease">
<img src="../assets/img/stop.png" alt="control" class="control-button-img"
style="width: 80px; height: 80px;">
</div>
2025-02-17 18:44:53 +08:00
</div>
</div>
</div>
</div>
</template>
<script>
2025-02-19 07:46:51 +08:00
import { useRouter } from 'vue-router';
import GamepadController from '../assets/js/GamepadController';
2025-02-22 21:04:37 +08:00
import audioTransmitter from '../assets/js/audio-transmitter';
2025-02-19 07:46:51 +08:00
2025-02-17 18:44:53 +08:00
export default {
name: 'CarControl',
data() {
return {
screenStatus: false,
warningLightStatus: false,
followStatus: false,
2025-02-19 07:46:51 +08:00
obstacleStatus: {
top: false,
bottom: false
},
2025-02-22 21:04:37 +08:00
obstacleAvoidEnabled: false,
2025-02-19 07:46:51 +08:00
gamepadController: null,
directionAxis0_1: "",
directionAxis9: "",
isGamepadConnected: false,
isControlPressed: false,
ws: null,
wsConnected: false,
2025-03-05 11:40:40 +08:00
// batteryLevel: 0, // 注释掉电池电量
2025-02-19 07:46:51 +08:00
lastResponseTime: 0,
connectionCheckInterval: null,
sendInterval: null,
lastDirection: 0,
2025-02-19 21:33:57 +08:00
lastSpeed: 0,
2025-02-22 21:04:37 +08:00
platform_fun: 0,
button: "",
audioConfig: audioTransmitter.data(),
isRecording: false,
2025-03-05 11:40:40 +08:00
audioHandler: null,
reconnectTimer: null, // 添加重连定时器引用
isComponentUnmounted: false // 添加组件卸载标志
2025-02-19 07:46:51 +08:00
}
},
created() {
// 初始化 GamepadController启用调试模式
this.gamepadController = new GamepadController({
debug: true,
deadZone: 0.1,
updateInterval: 50
});
// 添加全局事件监听
window.addEventListener("gamepadconnected", this.handleGamepadConnected);
window.addEventListener("gamepaddisconnected", this.handleGamepadDisconnected);
2025-03-05 11:40:40 +08:00
this.isComponentUnmounted = false; // 添加组件卸载标志
2025-02-19 07:46:51 +08:00
this.initWebSocket();
// 启动连接状态检查
this.connectionCheckInterval = setInterval(() => {
this.checkConnectionStatus();
}, 3000); // 每3秒检查一次
// 启动定时发送数据
this.sendInterval = setInterval(() => {
this.sendControlData();
}, 1000); // 每100ms发送一次
2025-02-22 21:04:37 +08:00
// 初始化音频处理器
this.audioHandler = {
...audioTransmitter.data(),
...audioTransmitter.methods
};
2025-02-19 07:46:51 +08:00
},
beforeDestroy() {
2025-03-05 11:40:40 +08:00
this.isComponentUnmounted = true; // 标记组件已卸载
// 清除重连定时器
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
2025-02-19 07:46:51 +08:00
// 组件销毁前清理资源
if (this.gamepadController) {
this.gamepadController.destroy();
}
// 移除全局事件监听
window.removeEventListener("gamepadconnected", this.handleGamepadConnected);
window.removeEventListener("gamepaddisconnected", this.handleGamepadDisconnected);
if (this.ws) {
this.ws.close();
2025-03-05 11:40:40 +08:00
this.ws = null;
2025-02-19 07:46:51 +08:00
}
if (this.connectionCheckInterval) {
clearInterval(this.connectionCheckInterval);
}
if (this.sendInterval) {
clearInterval(this.sendInterval);
}
2025-02-22 21:04:37 +08:00
// 确保停止音频传输
if (this.audioHandler && this.audioHandler.isRecording) {
this.audioHandler.stopRecording();
}
2025-02-19 07:46:51 +08:00
},
methods: {
goto(path) {
this.$router.push({ name: path });
},
handleGamepadConnected(e) {
console.log('Gamepad connected:', e.gamepad);
this.isGamepadConnected = true;
},
handleGamepadDisconnected() {
console.log('Gamepad disconnected');
this.isGamepadConnected = false;
// 可以在这里添加断开连接的处理逻辑
},
// 获取手柄数据的方法
getGamepadData() {
if (this.gamepadController) {
this.directionAxis0_1 = this.gamepadController.directionAxis0_1;
this.directionAxis9 = this.gamepadController.directionAxis9;
// 可以在这里添加其他数据的获取和处理
}
},
handleControlPress() {
this.isControlPressed = true;
2025-02-22 21:04:37 +08:00
// 开始音频传输
console.log('开始音频传输...');
if (this.audioHandler) {
this.audioHandler.startRecording();
}
2025-02-19 07:46:51 +08:00
},
handleControlRelease() {
this.isControlPressed = false;
2025-02-22 21:04:37 +08:00
// 停止音频传输
console.log('停止音频传输...');
if (this.audioHandler) {
this.audioHandler.stopRecording();
}
2025-02-19 07:46:51 +08:00
},
initWebSocket() {
2025-02-22 21:04:37 +08:00
try {
console.log('正在连接 WebSocket...');
2025-03-13 09:58:38 +08:00
// this.ws = new WebSocket('ws://192.168.4.120/ws');
this.ws = new WebSocket('ws://192.168.1.120/ws');
2025-02-22 21:04:37 +08:00
// this.ws = new WebSocket('ws://192.168.1.60:81');
2025-02-19 07:46:51 +08:00
2025-02-22 21:04:37 +08:00
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);
2025-02-19 07:46:51 +08:00
}
2025-02-22 21:04:37 +08:00
};
2025-02-19 07:46:51 +08:00
2025-03-05 11:40:40 +08:00
this.ws.onclose = () => {
console.log('WebSocket连接关闭');
// 只有在组件没有被卸载的情况下才重连
if (!this.isComponentUnmounted) {
this.reconnectTimer = setTimeout(() => {
this.initWebSocket();
}, 3000);
}
2025-02-22 21:04:37 +08:00
};
2025-02-19 07:46:51 +08:00
2025-02-22 21:04:37 +08:00
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);
}
2025-02-19 07:46:51 +08:00
},
// 更新手柄数据
updateGamepadData() {
if (this.gamepadController) {
2025-02-19 21:33:57 +08:00
// 获取手柄数据
2025-02-19 07:46:51 +08:00
const angle = this.gamepadController.angle;
2025-02-19 21:33:57 +08:00
const directionAxis9 = this.gamepadController.directionAxis9;
2025-02-22 21:04:37 +08:00
const speed = this.gamepadController.speed; // 获取速度值
2025-02-19 21:33:57 +08:00
// 更新控制按钮状态
2025-02-22 21:04:37 +08:00
const wasPressed = this.isControlPressed;
2025-02-19 21:33:57 +08:00
this.isControlPressed = this.gamepadController.rightJoystickPressed;
2025-02-22 21:04:37 +08:00
// 检测按钮状态变化并处理音频传输
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();
}
}
2025-02-19 21:33:57 +08:00
// 更新其他数据
2025-02-19 07:46:51 +08:00
this.lastDirection = angle;
2025-02-19 21:33:57 +08:00
this.platform_fun = directionAxis9;
2025-02-22 21:04:37 +08:00
this.lastSpeed = speed; // 更新速度值
2025-02-19 07:46:51 +08:00
}
},
sendCarInfo() {
const carData = {
"JSON_id": 1,
"rc_car_card": {
"get_info": 1,
"get_attribute": 1,
}
};
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(carData));
// 在控制台打印发送的数据
console.log('发送的数据:', JSON.stringify(carData));
} else {
console.error('WebSocket 未连接');
}
},
// 修改 refresh 图标的点击事件
handleRefresh() {
this.sendCarInfo();
},
updateCarStatus(data) {
if (data.attribute) {
2025-03-05 11:40:40 +08:00
// 注释掉电池状态更新
// this.batteryLevel = data.attribute.bat_quantity;
2025-02-22 21:04:37 +08:00
// 更新障碍物显示状态
2025-02-19 07:46:51 +08:00
this.obstacleStatus = {
top: data.attribute.obstacle_sta === 0,
bottom: data.attribute.obstacle_sta === 1
};
// 更新开关状态
2025-02-22 21:04:37 +08:00
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;
2025-02-19 07:46:51 +08:00
}
},
checkConnectionStatus() {
const now = Date.now();
// 如果超过5秒没有收到响应认为连接已断开
if (now - this.lastResponseTime > 5000) {
this.wsConnected = false;
}
},
sendControlData() {
// 先更新手柄数据
this.updateGamepadData();
const controlData = {
"JSON_id": 1,
"rc_car_card": {
2025-02-22 21:04:37 +08:00
"attribute": {
"platform": this.platform_fun,
2025-02-19 07:46:51 +08:00
"screen_en": this.screenStatus ? 1 : 0,
"warn_light_en": this.warningLightStatus ? 1 : 0,
"follow_en": this.followStatus ? 1 : 0,
2025-02-22 21:04:37 +08:00
"obstacle_avoid_en": this.obstacleAvoidEnabled ? 1 : 0,
"car_speed": this.lastSpeed,
"car_direction": this.lastDirection
2025-02-19 07:46:51 +08:00
}
}
};
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(controlData));
console.log('发送控制数据:', JSON.stringify(controlData));
}
},
},
computed: {
2025-03-05 11:40:40 +08:00
// 注释掉电池图片计算属性
/*
2025-02-19 07:46:51 +08:00
batteryImage() {
if (this.batteryLevel >= 80) return require('../assets/img/bat_full.png');
if (this.batteryLevel >= 60) return require('../assets/img/bat_high.png');
if (this.batteryLevel >= 40) return require('../assets/img/bat_medium.png');
if (this.batteryLevel >= 20) return require('../assets/img/bat_low.png');
return require('../assets/img/bat_empty.png');
},
2025-03-05 11:40:40 +08:00
*/
2025-02-19 07:46:51 +08:00
connectionImage() {
return this.wsConnected ?
require('../assets/img/connect.png') :
require('../assets/img/no_connect.png');
2025-02-17 18:44:53 +08:00
}
2025-03-05 11:40:40 +08:00
},
setup() {
const router = useRouter();
// 添加返回按钮处理函数
const onClickLeft = () => {
router.back();
};
return {
onClickLeft,
};
2025-02-17 18:44:53 +08:00
}
}
</script>
<style scoped>
.control-panel {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #000033;
color: #fff;
overflow: hidden;
display: flex;
flex-direction: column;
}
.status-bar {
height: 60px;
background-color: #000033;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
position: relative;
}
.placeholder-div {
width: 63px;
}
.title-container {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.title-box {
border: 2px solid #00ffff;
2025-02-19 07:46:51 +08:00
padding: 2px;
2025-02-17 18:44:53 +08:00
border-radius: 8px;
}
.main-content {
flex: 1;
display: flex;
2025-02-19 07:46:51 +08:00
padding: 10px;
2025-02-17 18:44:53 +08:00
gap: 20px;
}
.left-controls {
width: 120px;
display: flex;
flex-direction: column;
gap: 15px;
}
.switch-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid #00ffff;
border-radius: 8px;
font-size: 20px;
}
.center-display {
flex: 1;
border: 1px solid #00ffff;
border-radius: 8px;
2025-02-19 07:46:51 +08:00
padding: 5px;
2025-02-17 18:44:53 +08:00
display: flex;
flex-direction: column;
align-items: flex-start;
}
.status-text {
font-size: 20px;
color: #00ffff;
align-self: flex-start;
margin-bottom: 10px;
}
.car-display {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
2025-02-19 07:46:51 +08:00
.boom-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
/* gap: 10px; */
}
.boom-image {
width: 60px;
height: 60px;
}
.top-boom {
transform: rotate(0);
}
.bottom-boom {
transform: rotate(180deg);
}
2025-02-17 18:44:53 +08:00
.car-image {
width: 160px;
height: auto;
}
.right-status {
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.status-icons {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-top: 50px;
}
.icons-row {
display: flex;
flex-direction: row;
gap: 15px;
justify-content: center;
}
.icon-item {
width: 40px;
height: 40px;
/* border: 1px solid #00ffff; */
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: #00ffff;
}
.battery {
background-color: #00ffff;
color: #000033;
}
:deep(.el-switch__core) {
border-color: #00ffff;
background-color: transparent;
}
:deep(.el-switch.is-checked .el-switch__core) {
background-color: #00ffff;
}
.top-right-icons {
display: flex;
gap: 15px;
align-items: center;
width: 63px;
}
.top-icon {
width: 24px;
height: 24px;
cursor: pointer;
}
2025-02-19 07:46:51 +08:00
.circle-border {
width: 100px;
height: 100px;
border: 2px solid white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
transition: border-color 0.3s ease;
cursor: pointer;
}
.circle-border.pressed {
border-color: red;
}
.control-button-img {
width: 80px;
height: 80px;
user-select: none;
/* 防止拖动图片 */
}
.voice-icon-img {
opacity: 0;
transition: opacity 0.3s ease;
}
.voice-icon-img.show-voice {
opacity: 1;
}
2025-03-05 11:40:40 +08:00
/* 确保标题栏在最上层 */
:deep(.van-nav-bar) {
z-index: 999;
}
2025-02-22 21:04:37 +08:00
</style>