ESP32でラジコンを作ってみた

# 概要
ESP32にweb serverを立て、同一ネットワーク内の機器から操作できるラジコンを作りました。
具体的な構成は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2531876/3ad4b376-681c-cb93-31f4-318c66cef6d0.png)

# 動画
https://www.youtube.com/watch?v=6Gw8zOOseHY

# コード
操作する側が目にするhtmlとESP32上で動くプログラムで構成されます。
## ホスト側
```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>

```
## ESP32側
```c
#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);
}

```

# 編集から操作までの流れ
1. arduino ideで編集
1. htmlを作成し改行を無くしダブルクォーテーションをエスケープする
2. 1をsend_message()内のhtml変数に代入しなおす
2. 書き込み (これ以降ホストとESP32を切り離してもいい)
2. シリアルモニタに表示されるIPにアクセス
2. 操作


# 最後に
ChatGPTの力で1日かからずプログラムが完成しました。Bluetoothで無線操作できるように改良していきます。ありがとうございました。

追記
bluetoothラジコンもライントレーサ (超低性能)も作れました。工作は楽しい！

https://www.youtube.com/shorts/q0-KzzSweuo

https://www.youtube.com/watch?v=DPGRea99hHc