commit all stuff
parent
d0b99de71c
commit
8f60511606
|
@ -9,6 +9,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.3",
|
||||
"lodash": "^4.17.21",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
153
src/App.vue
153
src/App.vue
|
@ -1,26 +1,153 @@
|
|||
<template>
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
<div id="main">
|
||||
<form id="connection" @submit.prevent="connectionFormSubmit">
|
||||
<input type="text" v-model="host">
|
||||
<input type="submit" :value="isConnected ? 'Disconnect' : 'Connect'">
|
||||
</form>
|
||||
<div id="camera-view" v-if="isConnected" style="float:left;">
|
||||
<img v-if="camera_image_src !== null" :src="camera_image_src" alt="Camera View">
|
||||
</div>
|
||||
<div id="controls" v-if="isConnected" style="float: right">
|
||||
<input type="range" v-model="driveThrottle">
|
||||
<JoyStick @change="joystickChanged" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import JoyStick from "@/components/JoyStick";
|
||||
const _ = require('lodash');
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld
|
||||
components: {JoyStick},
|
||||
data () {
|
||||
return {
|
||||
connection: null,
|
||||
host: 'rc-car-esp32.local',
|
||||
camera_image_src: null,
|
||||
stickData: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
speed: 0,
|
||||
angle: 0
|
||||
},
|
||||
steerValue: 'straight',
|
||||
driveSide: 'stop',
|
||||
driveThrottle: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isConnected() {
|
||||
return this.connection instanceof WebSocket;
|
||||
},
|
||||
command_str() {
|
||||
return `${this.driveSide}|${this.driveThrottle}|${this.steerValue}`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
connectionFormSubmit() {
|
||||
if (!this.isConnected) {
|
||||
this.connection = new WebSocket(`ws://${this.host}`)
|
||||
|
||||
this.connection.onmessage = _.throttle((event) => {
|
||||
if (event.data instanceof Blob) {
|
||||
this.camera_image_src = URL.createObjectURL(event.data);
|
||||
}
|
||||
}, 10);
|
||||
|
||||
this.connection.onopen = () => {
|
||||
console.log("Successfully connected to the websocket server.");
|
||||
};
|
||||
this.connection.onclose = () => {
|
||||
console.log("Connection to the websocket server closed.");
|
||||
};
|
||||
}
|
||||
else {
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
}
|
||||
},
|
||||
joystickChanged: _.throttle(function (data) {
|
||||
this.stickData = data;
|
||||
}, 100),
|
||||
scale(number, fromLeft, fromRight, toLeft, toRight) {
|
||||
return toLeft + (number - fromLeft) / (fromRight - fromLeft) * (toRight - toLeft);
|
||||
},
|
||||
handleKeyDown: function (e) {
|
||||
const keyCode = String(e.keyCode || e.code || e.keyIdentifier);
|
||||
|
||||
if (keyCode === "37") {
|
||||
this.steerValue = 'left';
|
||||
}
|
||||
if (keyCode === "39") {
|
||||
this.steerValue = 'right';
|
||||
}
|
||||
if (keyCode === "38") {
|
||||
this.driveSide = 'forward';
|
||||
}
|
||||
if (keyCode === "40") {
|
||||
this.driveSide = 'backward';
|
||||
}
|
||||
|
||||
},
|
||||
handleKeyUp: function (e) {
|
||||
const keyCode = String(e.keyCode || e.code || e.keyIdentifier);
|
||||
|
||||
if (keyCode === "37" || keyCode === "39") {
|
||||
this.steerValue = 'straight';
|
||||
}
|
||||
if (keyCode === "38" || keyCode === "40") {
|
||||
this.driveSide = 'stop';
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
stickData(newValues) {
|
||||
const xDeadzone = 5;
|
||||
const yDeadzone = 5;
|
||||
|
||||
const true_values = {
|
||||
x: this.scale(newValues.x, -96, 96, 0, 100),
|
||||
y: this.scale(newValues.y, 96, -96, 0, 100)
|
||||
};
|
||||
|
||||
if (_.inRange(true_values.x, 50 - xDeadzone, 50 + xDeadzone)) {
|
||||
this.steerValue = 'straight';
|
||||
}
|
||||
else if (true_values.x > 50) {
|
||||
this.steerValue = 'right';
|
||||
}
|
||||
else if (true_values.x < 50) {
|
||||
this.steerValue = 'left';
|
||||
}
|
||||
|
||||
if (_.inRange(true_values.y, 50 - yDeadzone, 50 + yDeadzone)) {
|
||||
this.driveSide = 'stop';
|
||||
this.driveThrottle = 0;
|
||||
}
|
||||
else if (_.inRange(true_values.y, 50 - yDeadzone, 0)) {
|
||||
this.driveThrottle = this.scale(true_values.y, 50, 0, 0, 100);
|
||||
this.driveSide = 'backward';
|
||||
}
|
||||
if (_.inRange(true_values.y, 50 + yDeadzone, 100)) {
|
||||
this.driveThrottle = this.scale(true_values.y, 50, 100, 0, 100);
|
||||
this.driveSide = 'forward';
|
||||
}
|
||||
},
|
||||
command_str(newCmd) {
|
||||
if (this.isConnected && this.connection.readyState === WebSocket.OPEN) {
|
||||
this.connection.send(newCmd);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.handleKeyDown);
|
||||
window.addEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,127 @@
|
|||
<style>
|
||||
.vue-joystick {
|
||||
display: inline-block;
|
||||
background: white;
|
||||
height: 256px;
|
||||
width: 256px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
border: solid 4px var(--color);
|
||||
}
|
||||
.vue-joystick::before,
|
||||
.vue-joystick::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
.vue-joystick::before {
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: -32px;
|
||||
background: var(--color);
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
border-radius: 50%;
|
||||
transform: translateX(var(--x)) translateY(var(--y));
|
||||
}
|
||||
.vue-joystick::after {
|
||||
left: 126px;
|
||||
bottom: 128px;
|
||||
border-radius: 10px;
|
||||
width: 4px;
|
||||
background: var(--color);
|
||||
transform: rotate(var(--angle));
|
||||
transform-origin: bottom center;
|
||||
height: var(--speed);
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div
|
||||
class="vue-joystick"
|
||||
:style="style"
|
||||
@touchmove="handleTouch"
|
||||
@mousemove="handleMove"
|
||||
@mousedown="handleStart"
|
||||
@mouseup="handleRelease"
|
||||
@touchend="handleRelease"
|
||||
></div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: "#25B"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
angle: 0,
|
||||
speed: 0,
|
||||
isMouseDown: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
"--x": `${this.x + 128}px`,
|
||||
"--y": `${this.y + 128}px`,
|
||||
"--speed": `${this.speed}px`,
|
||||
"--angle": `${this.angle}deg`,
|
||||
"--color": `${this.color}`
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleStart() {
|
||||
this.isMouseDown = true;
|
||||
},
|
||||
handleTouch({ touches: [touch] }) {
|
||||
const { clientX, clientY } = touch;
|
||||
const { offsetLeft, offsetTop } = this.$el;
|
||||
const x = Math.round(clientX - offsetLeft - 128);
|
||||
const y = Math.round(clientY - offsetTop - 128);
|
||||
this.updatePosition(x, y);
|
||||
},
|
||||
handleMove({ clientX, clientY }) {
|
||||
if (!this.isMouseDown) {
|
||||
return;
|
||||
}
|
||||
const { offsetLeft, offsetTop } = this.$el;
|
||||
const x = Math.round(clientX - offsetLeft - 128);
|
||||
const y = Math.round(clientY - offsetTop - 128);
|
||||
this.updatePosition(x, y);
|
||||
},
|
||||
handleRelease() {
|
||||
this.emitAll("release");
|
||||
this.isMouseDown = false;
|
||||
this.updatePosition(0, 0);
|
||||
},
|
||||
updatePosition(x, y) {
|
||||
const offset = 128 - 32;
|
||||
const radians = Math.atan2(y, x);
|
||||
const angle = Math.round((radians * 180) / Math.PI, 4);
|
||||
this.angle = angle + (angle > 90 ? -270 : 90);
|
||||
this.speed = Math.min(
|
||||
Math.round(Math.sqrt(Math.pow(y, 2) + Math.pow(x, 2))),
|
||||
128
|
||||
);
|
||||
this.x = this.speed > offset ? Math.cos(radians) * offset : x;
|
||||
this.y = this.speed >= offset ? Math.sin(radians) * offset : y;
|
||||
this.emitAll();
|
||||
},
|
||||
emitAll(name = "change") {
|
||||
this.$emit(name, {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
speed: this.speed,
|
||||
angle: this.angle
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.emitAll();
|
||||
}
|
||||
};
|
||||
</script>
|
Loading…
Reference in New Issue