スマホゲームとかで画面上に指を置くとアナログのジョイスティックみたいに使えるバーチャルスティックをESP32で使ってみた。ESP32でWebサーバーを立てて、ブラウザ上のバーチャルジョイスティックからラジコンを操作してみた。
ブラウザ上でJavascriptによってバーチャルスティックを実現できるライブラリは古くから存在しているようで、今回はvirtualjoystick.jsを使わせてもらった。
このライブラリでは画面をタッチするとバーチャルスティックが出て座標が取得できる。このライブラリからESP32本体にデータを送るためにはWebSocketを使ってみた。
virtualjoystick.jsから出てきた座標データをESP32のWebSocketサーバーにJavascriptで投げる感じ。
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
overflow : hidden;
padding : 0;
margin : 0;
background-color: #BBB;
overscroll-behavior-y: none;
}
#info {
position : absolute;
top : 0px;
width : 100%;
padding : 5px;
text-align : center;
}
#info a {
color : #66F;
text-decoration : none;
}
#info a:hover {
text-decoration : underline;
}
#container {
width : 100%;
height : 100%;
overflow : hidden;
padding : 0;
margin : 0;
-webkit-user-select : none;
-moz-user-select : none;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info">
画面をタッチしてスライド
<br/>
<span id="result"></span>
<br/>
<span id="consoleArea"></span>
</div>
<div id="consoleArea">sa</div>
<script src="./virtualjoystick.js"></script>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var consoleArea = document.getElementById("consoleArea");
consoleArea.innerHTML = event.data;
}
function onLoad(event) {
initWebSocket();
}
function toggle(){
websocket.send('toggle');
}
console.log("touchscreen is", VirtualJoystick.touchScreenAvailable() ? "available" : "not available");
var joystick = new VirtualJoystick({
container : document.getElementById('container'),
mouseSupport : true,
limitStickTravel : true,
});
joystick.addEventListener('touchStart', function(){
console.log('down')
})
joystick.addEventListener('touchEnd', function(){
console.log('up')
})
var prevX = 0;
var prevY = 0;
var newX = 0;
var newY = 0;
setInterval(function(){
var outputEl = document.getElementById('result');
newX = Math.round(joystick.deltaX());
newY = Math.round(joystick.deltaY()) * -1;
outputEl.innerHTML = '<b>Position:</b> '
+ ' X:'+newX
+ ' Y:'+newY;
if ( newX != prevX || newY != prevY ){
websocket.send("x:"+newX+",y:"+newY);
}
prevX = newX;
prevY = newY;
}, 1/30 * 1000);
</script>
</body>
</html>
ESP32のWebサーバーで表示させるHTMLファイルはこんな感じ。エラー処理適当だけどとりあえずESP32のWebSocketサーバーにバーチャルスティックの座標データを送りつける感じ。
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
/*if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
*/
//Serial.println((char*)data);
uint8_t ypos = 0;
char strx[4];
char stry[4];
if(data[0] == 'x'){
for (int i=2; i <= len; i++){
if(data[i] == 'y'){
ypos = i + 2;
break;
}else{
strx[i-2] = data[i];
}
}
for (int i=ypos; i <= len; i++){
stry[i-ypos] = data[i];
}
//Serial.println(ypos);
x = atoi(strx);
y = atoi(stry);
#ifdef debug
Serial.print("x:");
Serial.print(x);
Serial.print(",y:");
Serial.println(y);
#endif
}
}
}
そんでもってESP32側のWebSocketサーバーでバーチャルジョイスティックのデータを受けたらグローバル変数に突っ込んでloopの中でモーターにデータを送る形にしてみた。
HTMLファイルとvirtualjoystick.jsは結構大きくなってしまったのでOnline converter: File to (cpp) gzip byte arrayを使用してgz圧縮してスケッチ内に組み込んでしまった。
とりあえず前に作った車体に乗っけてみて実験してみた。(写真撮りながら難しすぎ
対向二輪タイプの車体なのでバーチャルジョイスティックの値をそれぞれの車輪にどう振り分けるかの計算をもう少し調整しないといけないかも。レスポンス的にも十分問題なさそうなのでもう少し改良を進めようかな。
blynkとかでも同じようなことができるけどESP32だけでスマホ側にアプリのインストール要らないのは便利かもしれない。