概要
ESP32にweb serverを立て、同一ネットワーク内の機器から操作できるラジコンを作りました。
具体的な構成は以下の通りです。
動画
コード
操作する側が目にするhtmlとESP32上で動くプログラムで構成されます。
ホスト側
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RC Controller</title>
<style>
.button-container {
text-align: center;
}
.button {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
text-align: center;
text-decoration: none;
outline: none;
border: none;
border-radius: 5px;
background-color: #3498db;
color: #ffffff;
margin: 10px;
}
.button:hover {
background-color: #2980b9;
}
</style>
</head>
<body>
<h1>RC Controller</h1>
<div class="button-container">
<button class="button" id="forward" onmousedown="changeState('forward')" onmouseup="changeState('forward')">Forward</button><br>
<button class="button" id="left" onmousedown="changeState('left')" onmouseup="changeState('left')">Left</button>
<button class="button" id="stop" onmousedown="changeState('stop')" onmouseup="changeState('stop')">Stop</button>
<button class="button" id="right" onmousedown="changeState('right')" onmouseup="changeState('right')">Right</button><br>
<button class="button" id="backward" onmousedown="changeState('backward')" onmouseup="changeState('backward')">Backward</button>
</div>
<script>
const states = {
'forward': false,
'backward': false,
'left': false,
'right': false,
'stop': false
};
function changeState(target) {
if (states.hasOwnProperty(target)) {
states[target] = !states[target];
}
fetch('/control', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({command: target, state: states[target]})
}).then(data => {
console.log('Success:', data);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
</html>
ESP32側
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
const int IN1 = 15, IN2 = 16, IN3 = 17, IN4 = 18;
const char *ssid = "ssid";
const char *pass = "pass";
WebServer Server(80);
void send_message() {
Serial.println("send_message");
String html = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>RC Controller</title><style>.button-container {text-align: center;}.button {display: inline-block;padding: 10px 20px;font-size: 16px;cursor: pointer;text-align: center;text-decoration: none;outline: none;border: none;border-radius: 5px;background-color: #3498db;color: #ffffff;margin: 10px;}.button:hover {background-color: #2980b9;}</style></head><body><h1>RC Controller</h1><div class=\"button-container\"><button class=\"button\" id=\"forward\" onmousedown=\"changeState('forward')\" onmouseup=\"changeState('forward')\">Forward</button><br><button class=\"button\" id=\"left\" onmousedown=\"changeState('left')\" onmouseup=\"changeState('left')\">Left</button><button class=\"button\" id=\"stop\" onmousedown=\"changeState('stop')\" onmouseup=\"changeState('stop')\">Stop</button><button class=\"button\" id=\"right\" onmousedown=\"changeState('right')\" onmouseup=\"changeState('right')\">Right</button><br><button class=\"button\" id=\"backward\" onmousedown=\"changeState('backward')\" onmouseup=\"changeState('backward')\">Backward</button></div><script>const states = {'forward': false,'backward': false,'left': false,'right': false,'stop': false};function changeState(target) {if (states.hasOwnProperty(target)) {states[target] = !states[target];}fetch('/control', {method: 'PUT',headers: {'Content-Type': 'application/json'},body: JSON.stringify({command: target, state: states[target]})}).then(data => {console.log('Success:', data);}).catch(error => {console.error('Error:', error);});}</script></body></html>";
Server.send(200, "text/html", html);
}
void send_not_found() {
Serial.println("send_not_found");
Server.send(404, "text/plain", "404 not found...");
}
void change_state() {
String body = Server.arg("plain"); // ボディの取得
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, body);
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
const char* command = doc["command"];
bool state = doc["state"];
Serial.print("Command: ");
Serial.println(command);
Serial.print("State: ");
Serial.println(state ? "true" : "false");
if(!state){
stop();
}else{
if (strcmp(command, "stop") == 0) {
stop();
} else if (strcmp(command, "forward") == 0) {
forward();
} else if (strcmp(command, "backward") == 0) {
backward();
} else if (strcmp(command, "left") == 0) {
turn_left();
} else if (strcmp(command, "right") == 0) {
turn_right();
} else {
// デフォルトの処理
}
}
Server.send(200, "text/plain", "PUT request received");
}
void setup() {
// put your setup code here, to run once:
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
Serial.begin(115200);
delay(100);
Serial.println("\n*** Starting ***");
// 無線 LAN に接続
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
Serial.println("Connecting...");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
if (WiFi.status() == WL_CONNECT_FAILED) {
Serial.println("Can't connect");
}
}
Serial.println("Connected");
Serial.println(WiFi.localIP());
Server.on("/", HTTP_GET, send_message);
Server.on("/control", HTTP_PUT, change_state);
Server.onNotFound(send_not_found);
Server.begin();
}
void forward() {
Serial.println("forward");
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void backward() {
Serial.println("backward");
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void stop() {
Serial.println("stop");
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
void brake() {
Serial.println("brake");
digitalWrite(IN1, HIGH);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, HIGH);
}
void turn_left() {
Serial.println("turn_left");
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void turn_right() {
Serial.println("turn_right");
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
void loop() {
// put your main code here, to run repeatedly:
Server.handleClient();
delay(1);
}
編集から操作までの流れ
- arduino ideで編集
- htmlを作成し改行を無くしダブルクォーテーションをエスケープする
- 1をsend_message()内のhtml変数に代入しなおす
- 書き込み (これ以降ホストとESP32を切り離してもいい)
- シリアルモニタに表示されるIPにアクセス
- 操作
最後に
ChatGPTの力で1日かからずプログラムが完成しました。Bluetoothで無線操作できるように改良していきます。ありがとうございました。
追記
bluetoothラジコンもライントレーサ (超低性能)も作れました。工作は楽しい!