onegoup

スマホで操作できるプラレールを作成

今回作成するもの

必要なもの


ボディ等を作成するために必要なもの


まずはスマホの画面(UI)を作成

前進・後進・停止及び速度スライダーを設置。Bootstrap を利用しているので、どんなスマホでも横幅はぴったりと合います。
<!doctype html>
<html lang="ja">
    <head>
      <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

      <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

        <title>Go to</title>
        <style>input[type="range"] {-webkit-appearance: slider-vertical;}</style>


    </head>
    <body>
        <div class="container">
            <div class="row" style="height:50px"></div>
            <div class="row">
                <div class="col text-center">
                    <div class="row">
                        <div class="w-100 text-center">
                            <div class="slider-wrapper">
                                <input type="range" value="0" min="0" max="100" step="10" style="height: 500px;" id="input">
                            </div>
                        </div>
                    </div>
                    <div class="row">
                          <div class="col text-center text-info" style="font-size:50px">
                              <output id="output1">0</output>%
                          </div>
                    </div>
                </div>
                <div class="col">
                    <div class="row mt-1" style="height:150px">
                        <button type="button" class="btn btn-success w-100 h-100">前進</button>
                    </div>
                    <div class="row mt-1" style="height:150px">
                        <button type="button" class="btn btn-warning w-100 h-100">停止</button>
                    </div>
                    <div class="row mt-1" style="height:150px">
                        <button type="button" class="btn btn-danger w-100 h-100">後進</button>
                    </div>
                </div>
            </div>
        </div>

        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
        <script>
          $(function() {
              var $input = $('#input');
              var $output1 = $('#output1');
              var value;
              $input.on('input', function(event) {
              value = $input.val();
              $output1.text(value);
              $.get("?value=" + value + '&');
              });

              $("要素").click(function(){
                  if($(this).hasClass("clicked")){ // クリックされた要素がclickedクラスだったら
                    $(this).removeClass("clicked");
                  }else{
                    $(this).addClass("clicked");
                  }
              });

          });
        </script>
    </body>
</html>

前進・後進の切り替えはモータードライバー(TA7291P)を使用

端子の説明と接続先は下表のとおり
端子番号記号端子説明
1GNDグランド
2OUT1モーター端子に接続
3NC
4Vref速度制御端子 → pin 25
5IN1入力端子1 → pin 26 
HIGH or LOW でモーターの回転向きを制御
6IN2入力端子2 → pin 27
HIGH or LOW でモーターの回転向きを制御
7VCCモータードライブの電源 → pin 14
8VSモーターの電源
9NC
10OUT2モーター端子に接続

配線図

配線図イメージ

ESP32のコード

コード概要
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>

const char *ssid = "your ssid";
const char *password = "your password";

// HTTPリクエストを保存しておく変数
String header;

// ピンの状態を保存する変数の宣言
String goState = "off";
String stopState = "on";
String backState = "off";

String speedSlider = "0";

// 使用するピンに名前を付ける
const int speedPin = 25;
const int pin_in1 = 26;
const int pin_in2 = 27;
const int pin_vcc = 14;

/* TCP server at port 80 will respond to HTTP requests */
WiFiServer server(80);

///////////////////////////
void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.print("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  // 出力ピンに割り当て
  pinMode(pin_in1, OUTPUT);
  pinMode(pin_in2, OUTPUT);
  pinMode(pin_vcc, OUTPUT);

  // ピンを初期化
  digitalWrite(pin_in1, LOW);
  digitalWrite(pin_in2, LOW);
  digitalWrite(pin_vcc, HIGH);
  
  ledcSetup(0, 12800, 8);
  ledcAttachPin(speedPin, 0);

  
   // ssidとpasswordを用いてWi-Fiに接続
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // IPアドレスを出力し、webserverをスタート
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop() {
  /* Check if a client has connected */
    WiFiClient client = server.available();
    if (!client) {
        return;
    }
    if (client) {                             // クライアントが来たとき
    Serial.println("New Client.");          
    String currentLine = "";                // クライアントからくるデータを格納する変数
    while (client.connected()) {            // クライアントがつながっている間、以下をループ
      if (client.available()) {             // クライアントからデータが来ているとき
        char c = client.read();             // データを読み込み
        Serial.write(c);                    // 届いたデータをシリアルモニタに出力
        header += c;
        if (c == '\n') {                    // 届いたデータが改行コードだった時
          // もし現在の行が空白ならば、この改行コードのみ受け取る
          // つまりHTTPリクエストの終わりなので、レスポンスを返す
          if (currentLine.length() == 0) {
            // HTTPヘッダは(HTTP/1.1 200 OK)のようなステータスコードから始まる
            // 次にコンテントタイプを送信。今回はhtml形式なので以下のようにする
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close"); 
            client.println();
            
            // リクエストに従ってGPIOをスイッチする
            if (header.indexOf("GET /GO/on") >= 0) {
              Serial.println("GO on");
              goState = "on";
              stopState ="off";
              backState ="off";
              digitalWrite(pin_in1, HIGH);
              digitalWrite(pin_in2, LOW);            
            } else if(header.indexOf("GET /GO/off") >= 0){
              Serial.println("GO off");
              goState = "off";
              digitalWrite(pin_in1, LOW);
              digitalWrite(pin_in2, LOW);
              speedSlider = "0";
              ledcWrite(0, speedSlider.toInt() * 2.5);
            }

            if (header.indexOf("GET /STOP/on") >= 0) {
              Serial.println("STOP on");
              goState = "off";
              stopState ="on";
              backState ="off";
              digitalWrite(pin_in1, LOW);
              digitalWrite(pin_in2, LOW);
              speedSlider = "0";
              ledcWrite(0, speedSlider.toInt() * 2.5);
            }

            if (header.indexOf("GET /BACK/on") >= 0) {
              Serial.println("BACK on");
              goState = "off";
              stopState ="off";
              backState ="on";
              digitalWrite(pin_in1, LOW);
              digitalWrite(pin_in2, HIGH);
            } else if(header.indexOf("GET /BACK/off") >= 0){
              Serial.println("BACK off");
              goState = "off";
              digitalWrite(pin_in1, LOW);
              digitalWrite(pin_in2, LOW);
              speedSlider = "0";
              ledcWrite(0, speedSlider.toInt() * 2.5);
            } 

            if(header.indexOf("GET /GO/on?value=") >= 0){
               int pos1 = header.indexOf("=");
               int pos2 = header.indexOf("&");
               speedSlider = header.substring(pos1+1, pos2);
               Serial.print("Value="); Serial.println(speedSlider);
               ledcWrite(0, speedSlider.toInt() * 2.5);
            }

            if(header.indexOf("GET /BACK/on?value=") >= 0){
               int pos1 = header.indexOf("=");
               int pos2 = header.indexOf("&");
               speedSlider = header.substring(pos1+1, pos2);
               Serial.print("Value="); Serial.println(speedSlider);
               ledcWrite(0, speedSlider.toInt() * 2.5);
            }
            
            // htmlを表示
            client.println("<!doctype html>");
            client.println("<html lang=\"ja\">");
            client.println("<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">");
            client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css\" integrity=\"sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z\" crossorigin=\"anonymous\">");
            client.println("<title>Go to</title>");
            client.println("<style>input[type=\"range\"] {-webkit-appearance: slider-vertical;}</style>");
            client.println("</head>");
            client.println("<body><div class=\"container\">");
            client.println("<div class=\"row\" style=\"height:50px\"></div>");
            client.println("<div class=\"row\">");
            client.println("<div class=\"col text-center\">");
            client.println("<div class=\"row\">");
            client.println("<div class=\"w-100 text-center\">");
            client.println("<div class=\"slider-wrapper\">");
            if(stopState=="off"){
              client.println("<input type=\"range\" class=\"slider\" value=\"0\" min=\"0\" max=\"100\" step=\"10\" style=\"height: 500px;\" id=\"input\">");     
            }else{  
            }
            client.println("</div></div></div>");
            client.println("<div class=\"row\">");
            client.println("<div class=\"col text-center text-info\" style=\"font-size:50px\">");
            if(stopState=="off"){
              client.println("<output id=\"output1\">0</output>%");  
            }else{
              client.println("<output>停車中</output>");  
            }
            client.println("</div></div></div>");
            client.println("<div class=\"col\">");
            client.println("<div class=\"row mt-1\" style=\"height:150px\">");
            if(goState=="on") {
              client.println("<a href=\"/GO/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">前進</button></a></div>");              
            }else{
              client.println("<a href=\"/GO/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-success w-100 h-100\" style=\"font-size:30px\">前進</button></a></div>");  
            }
            client.println("<div class=\"row mt-1\" style=\"height:150px\">");
            if(stopState=="on"){
              client.println("<a href=\"/STOP/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">停止</button></a></div>");  
            }else{
              client.println("<a href=\"/STOP/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-warning w-100 h-100\" style=\"font-size:30px\">停止</button></a></div>");  
            }
            client.println("<div class=\"row mt-1\" style=\"height:150px\">");
            if(backState=="on"){
              client.println("<a href=\"/BACK/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">後進</button></a></div>");
            }else{
              client.println("<a href=\"/BACK/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-danger w-100 h-100\" style=\"font-size:30px\">後進</button></a></div>");
            }
            client.println("</div></div></div>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>");
            client.println("<script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\" integrity=\"sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN\" crossorigin=\"anonymous\"></script>");
            client.println("<script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js\" integrity=\"sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV\" crossorigin=\"anonymous\"></script>");
            client.println("<script>$(function(){var $input = $('#input');var $output1 = $('#output1');var value;$input.on('input', function(event) {value = $input.val();$output1.text(value);$.get(\"\?value=\" + value + '&');});});</script>");
            client.println("</body></html>");
            
            // HTTPレスポンスの最後は改行で終了
            client.println();
            // whileループの終了
            break;
          } else { // 改行コードを取得したら、currentLineをリセット
            currentLine = "";
          }
        } else if (c != '\r') {  // 改行以外の何かしらのコードが来ているとき
          currentLine += c;      // currentLineに追加
        }
      }
    }
    // ヘッダーをリセット
    header = "";
    // 接続をリセット
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}