This commit is contained in:
JayJiaJun 2025-03-18 17:32:30 +08:00
parent 3c80f4a2f7
commit e8da4a76a8
4 changed files with 261 additions and 181 deletions

View File

@ -1,151 +1,111 @@
class GamepadController { class GamepadController {
constructor(options = {}) { constructor(options = {}) {
this.options = { this.options = {
...{ ...{
debug: false, debug: false,
deadZone: 0.1, deadZone: 0.1,
updateInterval: 50, updateInterval: 50
buttonsConfig: [
{ name: "Left2", index: 6 },
{ name: "Back", index: 8 },
{ name: "Right Joystick Press", index: 11 }
]
}, },
...options ...options
}; };
this.gamepadIndex = null; this.gamepadIndex = null;
this.gamepad = null; this.gamepad = null;
this.interval = null; this.interval = null;
this.buttons = []; this.angle = 0; // 这里将存储 0-360 的角度值
this.directionAxis0_1 = ""; this.speed = 0;
this.directionAxis9 = ""; this.directionAxis9 = 0;
this.angle = 0;
this.rightJoystickPressed = false; this.rightJoystickPressed = false;
this.speed = 0; // 添加速度属性 this.isActive = false;
// 初始化按钮状态
this.buttons = this.options.buttonsConfig.map(button => ({
...button,
pressed: false
}));
// 注册事件监听器 this.updateLoop = setInterval(() => this.update(), this.options.updateInterval);
window.addEventListener("gamepadconnected", this.onGamepadConnected.bind(this));
window.addEventListener("gamepaddisconnected", this.onGamepadDisconnected.bind(this));
if (this.options.debug) {
console.log("GamepadController initialized with options:", this.options);
}
} }
onGamepadConnected(e) { update() {
console.log("Gamepad connected:", e.gamepad); const gamepad = navigator.getGamepads()[0];
this.gamepadIndex = e.gamepad.index; if (!gamepad) {
this.gamepad = navigator.getGamepads()[this.gamepadIndex]; this.isActive = false;
this.startGamepad();
}
onGamepadDisconnected() {
clearInterval(this.interval);
this.gamepad = null;
if (this.options.debug) {
console.log("Gamepad disconnected");
}
}
startGamepad() {
this.interval = setInterval(() => {
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.gamepadIndex];
if (gamepad) {
// 注释掉调试打印
// if (this.options.debug) {
// console.log('Axes data:', {
// axis0: gamepad.axes[0],
// axis1: gamepad.axes[1],
// gamepadIndex: this.gamepadIndex
// });
// }
this.updateDirection(gamepad.axes);
this.updateDirectionAxis9(gamepad.axes);
this.pressKey(gamepad.buttons);
}
}, this.options.updateInterval);
}
updateDirection(axes) {
const axis0 = axes[0];
const axis1 = axes[1];
// 检查是否在死区
if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) {
this.directionAxis0_1 = "未定义";
this.angle = 0;
this.speed = 0; // 在死区时速度为0
return; return;
} }
// 计算速度(到原点的距离) this.isActive = true;
// 使用勾股定理计算距离并将结果限制在0-100之间
const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
this.speed = Math.round(Math.min(distance * 100, 100));
// 计算方向角度0-360度 // 获取左摇杆的值
const x = gamepad.axes[0];
const y = gamepad.axes[1];
// 应用死区
const absX = Math.abs(x);
const absY = Math.abs(y);
if (absX < this.options.deadZone && absY < this.options.deadZone) {
this.angle = 0;
this.speed = 0;
} else {
this.updateDirection(gamepad.axes);
}
// 获取右摇杆的值(用于云台控制)
const rightY = gamepad.axes[3];
if (Math.abs(rightY) > this.options.deadZone) {
this.directionAxis9 = rightY > 0 ? 1 : 2;
} else {
this.directionAxis9 = 0;
}
// 更新右摇杆按钮状态
this.rightJoystickPressed = gamepad.buttons[10].pressed;
if (this.options.debug) {
console.log('GamepadController 状态:', {
x: x.toFixed(2),
y: y.toFixed(2),
angle: this.angle,
speed: this.speed.toFixed(2),
directionAxis9: this.directionAxis9,
rightJoystickPressed: this.rightJoystickPressed
});
}
}
updateDirection(axes) {
const axis0 = axes[0]; // X轴
const axis1 = axes[1]; // Y轴
// 检查是否在死区
if (Math.abs(axis0) < this.options.deadZone && Math.abs(axis1) < this.options.deadZone) {
this.angle = 0;
this.speed = 0;
return;
}
// 计算角度0-360度从x轴正半轴开始顺时针旋转
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 = Math.round(angle); if (angle < 0) {
this.angle = angle; angle += 360;
// 更新方向数据
if (Math.abs(axis0) > this.options.deadZone || Math.abs(axis1) > this.options.deadZone) {
this.directionAxis0_1 = `${angle}°`;
// 注释掉调试打印
// if (this.options.debug) {
// console.log(` 摇杆方向: ${angle}°, X轴: ${axis0.toFixed(2)}, Y轴: ${axis1.toFixed(2)}`);
// }
} }
} this.angle = Math.round(angle);
updateDirectionAxis9(axes) { // 计算速度0-100
const axis9 = axes[9]; const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
const roundedAxis9 = Math.round(axis9 * 100) / 100; this.speed = Math.min(Math.round(distance * 100), 100);
if (roundedAxis9 <= -0.9) {
this.directionAxis9 = 1; if (this.options.debug) {
} else if (roundedAxis9 >= 0.0 && roundedAxis9 <= 0.2) { console.log('方向计算:', {
this.directionAxis9 = 2; x: axis0,
// } else if (roundedAxis9 >= 0.6 && roundedAxis9 <= 0.8) { y: axis1,
// this.directionAxis9 = "左"; angle: this.angle,
// } else if (roundedAxis9 >= -0.5 && roundedAxis9 <= -0.4) { speed: this.speed
// this.directionAxis9 = "右"; });
} }
else {
this.directionAxis9 = 0;
}
}
pressKey(buttons) {
this.buttons.forEach(button => {
const buttonData = buttons[button.index];
button.pressed = buttonData ? buttonData.value === 1 : false;
// 特别检查 Right Joystick Press 按钮
if (button.name === "Right Joystick Press") {
this.rightJoystickPressed = button.pressed;
}
});
} }
destroy() { destroy() {
clearInterval(this.interval); if (this.updateLoop) {
window.removeEventListener("gamepadconnected", this.onGamepadConnected); clearInterval(this.updateLoop);
window.removeEventListener("gamepaddisconnected", this.onGamepadDisconnected);
if (this.options.debug) {
console.log("GamepadController destroyed");
} }
} }
} }
// 导出类以便外部使用
export default GamepadController; export default GamepadController;

View File

@ -5,7 +5,8 @@
<div class="placeholder-div"></div> <div class="placeholder-div"></div>
<div class="title-container"> <div class="title-container">
<div class="title-box"> <div class="title-box">
遥控车当前状态 <!-- 根据方向状态显示不同文本 -->
{{ getDirectionText }}
<img src="../assets/img/shout.png" alt="voice" class="voice-icon-img" <img src="../assets/img/shout.png" alt="voice" class="voice-icon-img"
:class="{ 'show-voice': isControlPressed }" style="width: 20px; height: 20px; margin-left: 20px;"> :class="{ 'show-voice': isControlPressed }" style="width: 20px; height: 20px; margin-left: 20px;">
</div> </div>
@ -23,19 +24,19 @@
<div class="left-controls"> <div class="left-controls">
<div class="switch-item"> <div class="switch-item">
<span>屏幕</span> <span>屏幕</span>
<el-switch v-model="screenStatus" /> <el-switch v-model="screenStatus" @change="handleScreenChange" />
</div> </div>
<div class="switch-item"> <div class="switch-item">
<span>警灯</span> <span>警灯</span>
<el-switch v-model="warningLightStatus" /> <el-switch v-model="warningLightStatus" @change="handleWarningLightChange" />
</div> </div>
<div class="switch-item"> <div class="switch-item">
<span>跟随</span> <span>跟随</span>
<el-switch v-model="followStatus" /> <el-switch v-model="followStatus" @change="handleFollowChange" />
</div> </div>
<div class="switch-item"> <div class="switch-item">
<span>避障</span> <span>避障</span>
<el-switch v-model="obstacleAvoidEnabled" /> <el-switch v-model="obstacleAvoidEnabled" @change="handleObstacleAvoidChange" />
</div> </div>
</div> </div>
@ -114,13 +115,14 @@ export default {
isRecording: false, isRecording: false,
audioHandler: null, audioHandler: null,
reconnectTimer: null, // reconnectTimer: null, //
isComponentUnmounted: false // isComponentUnmounted: false, //
isInitialized: false, //
} }
}, },
created() { created() {
// GamepadController // GamepadController
this.gamepadController = new GamepadController({ this.gamepadController = new GamepadController({
debug: true, debug: false, // debug false
deadZone: 0.1, deadZone: 0.1,
updateInterval: 50 updateInterval: 50
}); });
@ -223,8 +225,8 @@ export default {
initWebSocket() { initWebSocket() {
try { try {
console.log('正在连接 WebSocket...'); console.log('正在连接 WebSocket...');
// this.ws = new WebSocket('ws://192.168.4.120/ws'); this.ws = new WebSocket('ws://192.168.4.120/ws');
this.ws = new WebSocket('ws://192.168.1.120/ws'); // this.ws = new WebSocket('ws://192.168.1.120/ws');
// this.ws = new WebSocket('ws://192.168.1.60:81'); // this.ws = new WebSocket('ws://192.168.1.60:81');
@ -277,13 +279,27 @@ export default {
} }
}, },
// // updateGamepadData
updateGamepadData() { updateGamepadData() {
if (this.gamepadController) { if (this.gamepadController) {
// //
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 speed = this.gamepadController.speed;
// 0-4
let direction = 0; //
if (speed > 10) { //
if (angle >= 315 || angle < 45) {
direction = 4; //
} else if (angle >= 45 && angle < 135) {
direction = 2; // 退
} else if (angle >= 135 && angle < 225) {
direction = 3; //
} else if (angle >= 225 && angle < 315) {
direction = 1; //
}
}
// //
const wasPressed = this.isControlPressed; const wasPressed = this.isControlPressed;
@ -304,10 +320,10 @@ export default {
} }
} }
// //
this.lastDirection = angle; this.lastDirection = direction;
this.platform_fun = directionAxis9; if (directionAxis9 !== undefined) this.platform_fun = directionAxis9;
this.lastSpeed = speed; // if (speed !== undefined) this.lastSpeed = speed;
} }
}, },
sendCarInfo() { sendCarInfo() {
@ -333,26 +349,28 @@ export default {
this.sendCarInfo(); this.sendCarInfo();
}, },
//
updateCarStatus(data) { updateCarStatus(data) {
if (data.attribute) { if (data.attribute) {
//
// this.batteryLevel = data.attribute.bat_quantity;
// //
this.obstacleStatus = { this.obstacleStatus = {
top: data.attribute.obstacle_sta === 0, top: data.attribute.obstacle_sta === 1,
bottom: data.attribute.obstacle_sta === 1 bottom: data.attribute.obstacle_sta === 2
}; };
// //
this.platform_fun = data.attribute.platform; if (!this.isInitialized) {
this.screenStatus = data.attribute.screen_en === 1; this.screenStatus = data.attribute.screen_en === 1;
this.warningLightStatus = data.attribute.warn_light_en === 1; this.warningLightStatus = data.attribute.warn_light_en === 1;
this.followStatus = data.attribute.follow_en === 1; this.followStatus = data.attribute.follow_en === 1;
this.obstacleAvoidEnabled = data.attribute.obstacle_avoid_en === 1; this.obstacleAvoidEnabled = data.attribute.obstacle_avoid_en === 1;
this.isInitialized = true;
}
}
// //
this.lastSpeed = data.attribute.car_speed; if (data.driver && typeof data.driver.direction !== 'undefined') {
this.lastDirection = data.attribute.car_direction; this.lastDirection = data.driver.direction; // 使
} }
}, },
@ -364,9 +382,33 @@ export default {
} }
}, },
//
handleScreenChange(value) {
this.screenStatus = value;
this.sendControlData();
},
handleWarningLightChange(value) {
this.warningLightStatus = value;
this.sendControlData();
},
handleFollowChange(value) {
this.followStatus = value;
this.sendControlData();
},
handleObstacleAvoidChange(value) {
this.obstacleAvoidEnabled = value;
this.sendControlData();
},
// sendControlData
sendControlData() { sendControlData() {
// //
this.updateGamepadData(); if (this.gamepadController && this.gamepadController.isActive) {
this.updateGamepadData();
}
const controlData = { const controlData = {
"JSON_id": 1, "JSON_id": 1,
@ -405,6 +447,23 @@ export default {
return this.wsConnected ? return this.wsConnected ?
require('../assets/img/connect.png') : require('../assets/img/connect.png') :
require('../assets/img/no_connect.png'); require('../assets/img/no_connect.png');
},
getDirectionText() {
// direction
switch (this.lastDirection) {
case 0:
return '遥控车已停止';
case 1:
return '遥控车正在前进';
case 2:
return '遥控车正在后退';
case 3:
return '遥控车正在左转';
case 4:
return '遥控车正在右转';
default:
return '遥控车当前状态';
}
} }
}, },
setup() { setup() {
@ -464,6 +523,10 @@ export default {
border: 2px solid #00ffff; border: 2px solid #00ffff;
padding: 2px; padding: 2px;
border-radius: 8px; border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
min-width: 180px; /* 确保文本变化时盒子大小稳定 */
} }
.main-content { .main-content {

View File

@ -67,6 +67,7 @@
<span>状态与控制</span> <span>状态与控制</span>
</div> </div>
<van-cell-group inset> <van-cell-group inset>
<!-- 报警状态只读 --> <!-- 报警状态只读 -->
<van-cell title="报警状态"> <van-cell title="报警状态">
<template #value> <template #value>
@ -105,7 +106,7 @@
</template> </template>
<script> <script>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
// import { showToast } from 'vant'; // import { showToast } from 'vant';
import axios from 'axios'; import axios from 'axios';
@ -426,10 +427,10 @@ export default {
parseInt(networkId.value.slice(2, 4), 16), parseInt(networkId.value.slice(2, 4), 16),
parseInt(networkId.value.slice(4, 6), 16) parseInt(networkId.value.slice(4, 6), 16)
], ],
fre_band: parseInt(freqBand.value), fre_band: freqBandOptions.find(option => option.text === freqBand.value)?.value || 0,
channel: parseInt(channel.value), channel: channel.value ? (parseInt(channel.value.match(/\d+/)?.[0] || '1') - 1) : 0,
SF: parseInt(speedRate.value), SF: speedRateOptions.find(option => option.text === speedRate.value)?.value || 0,
power: parseInt(power.value), power: powerOptions.find(option => option.text === power.value)?.value || 0,
mode: value.selectedOptions[0].value mode: value.selectedOptions[0].value
} }
}, },
@ -454,18 +455,44 @@ export default {
}; };
const updateStateFromResponse = (data) => { const updateStateFromResponse = (data) => {
firmwareVersion.value = data.versions; //
macAddress.value = data.mac_addr.join(':'); if (!data || !data.cones_card) {
networkId.value = data.mesh_id.map(num => num.toString(16).padStart(2, '0').toUpperCase()).join(''); console.error('Invalid response data structure');
freqBand.value = freqBandOptions.find(option => option.value === data.fre_band)?.text || ''; return;
channel.value = `信道${data.channel + 1}`; }
speedRate.value = speedRateOptions.find(option => option.value === data.SF)?.text || '';
power.value = powerOptions.find(option => option.value === data.power)?.text || '';
// // LoRa
const mode = modeOptions.find(option => option.networkId === networkId.value); if (data.cones_card.LoRa_cfg) {
if (mode) { const loraConfig = data.cones_card.LoRa_cfg;
currentMode.value = mode.text; firmwareVersion.value = loraConfig.versions;
macAddress.value = loraConfig.mac_addr.join(':');
networkId.value = loraConfig.mesh_id.map(num => num.toString(16).padStart(2, '0').toUpperCase()).join('');
freqBand.value = freqBandOptions.find(option => option.value === loraConfig.fre_band)?.text || '';
channel.value = `信道${loraConfig.channel + 1}`;
speedRate.value = speedRateOptions.find(option => option.value === loraConfig.SF)?.text || '';
power.value = powerOptions.find(option => option.value === loraConfig.power)?.text || '';
//
const mode = modeOptions.find(option => option.networkId === networkId.value);
if (mode) {
currentMode.value = mode.text;
}
}
//
if (data.cones_card.traffic_cone) {
const trafficCone = data.cones_card.traffic_cone;
//
alarmStatus.value = trafficCone.alarm === 1 ? '报警' : '正常';
//
const lightOption = lightStatusOptions.find(option => option.value === trafficCone.lamplight);
if (lightOption) {
lightStatus.value = lightOption.text;
}
//
defenseEnabled.value = trafficCone.mode === 1;
} }
}; };
@ -487,7 +514,8 @@ export default {
}) })
.then(response => { .then(response => {
console.log('Refresh response:', response.data); console.log('Refresh response:', response.data);
updateStateFromResponse(response.data.cones_card.LoRa_cfg); //
updateStateFromResponse(response.data);
refreshing.value = false; refreshing.value = false;
}) })
.catch(error => { .catch(error => {
@ -503,7 +531,6 @@ export default {
onMounted(() => { onMounted(() => {
checkEnvironment(); checkEnvironment();
onRefresh(); onRefresh();
}); });
return { return {
@ -677,4 +704,33 @@ export default {
.info-row:not(.mode-row) { .info-row:not(.mode-row) {
opacity: 0.85; opacity: 0.85;
} }
.car-status {
font-weight: bold;
}
.status-0 {
color: #909399;
/* 灰色 - 停止 */
}
.status-1 {
color: #67C23A;
/* 绿色 - 前进 */
}
.status-2 {
color: #E6A23C;
/* 橙色 - 后退 */
}
.status-3 {
color: #409EFF;
/* 蓝色 - 左转 */
}
.status-4 {
color: #409EFF;
/* 蓝色 - 右转 */
}
</style> </style>

View File

@ -26,6 +26,7 @@ module.exports = defineConfig({
devServer: { devServer: {
open: true, open: true,
hot: true,//自动保存 hot: true,//自动保存
port:80
}, },
chainWebpack: (config) => { chainWebpack: (config) => {
config.plugin('code-inspector-plugin').use( config.plugin('code-inspector-plugin').use(