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 {
constructor(options = {}) {
this.options = {
this.options = {
...{
debug: false,
deadZone: 0.1,
updateInterval: 50,
buttonsConfig: [
{ name: "Left2", index: 6 },
{ name: "Back", index: 8 },
{ name: "Right Joystick Press", index: 11 }
]
updateInterval: 50
},
...options
};
this.gamepadIndex = null;
this.gamepad = null;
this.interval = null;
this.buttons = [];
this.directionAxis0_1 = "";
this.directionAxis9 = "";
this.angle = 0;
this.gamepadIndex = null;
this.gamepad = null;
this.interval = null;
this.angle = 0; // 这里将存储 0-360 的角度值
this.speed = 0;
this.directionAxis9 = 0;
this.rightJoystickPressed = false;
this.speed = 0; // 添加速度属性
// 初始化按钮状态
this.buttons = this.options.buttonsConfig.map(button => ({
...button,
pressed: false
}));
this.isActive = false;
// 注册事件监听器
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);
}
this.updateLoop = setInterval(() => this.update(), this.options.updateInterval);
}
onGamepadConnected(e) {
console.log("Gamepad connected:", e.gamepad);
this.gamepadIndex = e.gamepad.index;
this.gamepad = navigator.getGamepads()[this.gamepadIndex];
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
update() {
const gamepad = navigator.getGamepads()[0];
if (!gamepad) {
this.isActive = false;
return;
}
// 计算速度(到原点的距离)
// 使用勾股定理计算距离并将结果限制在0-100之间
const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
this.speed = Math.round(Math.min(distance * 100, 100));
this.isActive = true;
// 计算方向角度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);
angle = (angle + 360) % 360;
angle = Math.round(angle);
this.angle = angle;
// 更新方向数据
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)}`);
// }
// 将角度转换到 0-360 范围
if (angle < 0) {
angle += 360;
}
}
this.angle = Math.round(angle);
updateDirectionAxis9(axes) {
const axis9 = axes[9];
const roundedAxis9 = Math.round(axis9 * 100) / 100;
if (roundedAxis9 <= -0.9) {
this.directionAxis9 = 1;
} else if (roundedAxis9 >= 0.0 && roundedAxis9 <= 0.2) {
this.directionAxis9 = 2;
// } else if (roundedAxis9 >= 0.6 && roundedAxis9 <= 0.8) {
// this.directionAxis9 = "左";
// } else if (roundedAxis9 >= -0.5 && roundedAxis9 <= -0.4) {
// this.directionAxis9 = "右";
// 计算速度0-100
const distance = Math.sqrt(axis0 * axis0 + axis1 * axis1);
this.speed = Math.min(Math.round(distance * 100), 100);
if (this.options.debug) {
console.log('方向计算:', {
x: axis0,
y: axis1,
angle: this.angle,
speed: this.speed
});
}
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() {
clearInterval(this.interval);
window.removeEventListener("gamepadconnected", this.onGamepadConnected);
window.removeEventListener("gamepaddisconnected", this.onGamepadDisconnected);
if (this.options.debug) {
console.log("GamepadController destroyed");
if (this.updateLoop) {
clearInterval(this.updateLoop);
}
}
}
// 导出类以便外部使用
export default GamepadController;

View File

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

View File

@ -67,6 +67,7 @@
<span>状态与控制</span>
</div>
<van-cell-group inset>
<!-- 报警状态只读 -->
<van-cell title="报警状态">
<template #value>
@ -105,7 +106,7 @@
</template>
<script>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue';
import { useRouter } from 'vue-router';
// import { showToast } from 'vant';
import axios from 'axios';
@ -426,10 +427,10 @@ export default {
parseInt(networkId.value.slice(2, 4), 16),
parseInt(networkId.value.slice(4, 6), 16)
],
fre_band: parseInt(freqBand.value),
channel: parseInt(channel.value),
SF: parseInt(speedRate.value),
power: parseInt(power.value),
fre_band: freqBandOptions.find(option => option.text === freqBand.value)?.value || 0,
channel: channel.value ? (parseInt(channel.value.match(/\d+/)?.[0] || '1') - 1) : 0,
SF: speedRateOptions.find(option => option.text === speedRate.value)?.value || 0,
power: powerOptions.find(option => option.text === power.value)?.value || 0,
mode: value.selectedOptions[0].value
}
},
@ -454,18 +455,44 @@ export default {
};
const updateStateFromResponse = (data) => {
firmwareVersion.value = data.versions;
macAddress.value = data.mac_addr.join(':');
networkId.value = data.mesh_id.map(num => num.toString(16).padStart(2, '0').toUpperCase()).join('');
freqBand.value = freqBandOptions.find(option => option.value === data.fre_band)?.text || '';
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 || '';
//
if (!data || !data.cones_card) {
console.error('Invalid response data structure');
return;
}
//
const mode = modeOptions.find(option => option.networkId === networkId.value);
if (mode) {
currentMode.value = mode.text;
// LoRa
if (data.cones_card.LoRa_cfg) {
const loraConfig = data.cones_card.LoRa_cfg;
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 => {
console.log('Refresh response:', response.data);
updateStateFromResponse(response.data.cones_card.LoRa_cfg);
//
updateStateFromResponse(response.data);
refreshing.value = false;
})
.catch(error => {
@ -503,7 +531,6 @@ export default {
onMounted(() => {
checkEnvironment();
onRefresh();
});
return {
@ -677,4 +704,33 @@ export default {
.info-row:not(.mode-row) {
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>

View File

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