6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ESP-WROOM-02で鉄道模型を遠隔制御(第4回)

Posted at

#概要
 鉄道模型をマイコン制御してやろうと思われている皆さん、こんにちは。綾瀬です。ESP-WROOM-02を使って鉄道模型を制御してきたことをまとめていくシリーズの第4回の今回は、無線LANで接続したデバイス(タブレット端末やスマートフォンなど)から鉄道模型車両を遠隔制御するために、前回ESP-WROOM-02に構築したWebサーバにWebAPIを実装してみたいと思います。
 今回実装するWebAPIは、無線LAN内蔵SDカードである東芝FlashAirのWebAPIと互換性を持たせます。これは、私がこれまでFlashAirを使って製作した鉄道模型制御装置と、クライアントアプリを共用できるようにするためです。

 本シリーズの過去記事は以下を参照してください。
 第1回
 第2回
 第3回

#構成

###ハードウェア構成
 今回は第2回で製作した構成をそのまま使います。

###ソフトウェア構成
 今回構築するソフトウェアの構成を簡単なイラストで示します。
image1.png

 第3回の記事で作成したのは、このイラストのWebAPIを作成するための土台であるWebサーバを構築しました。今回は、ここにWebAPIを実装します。
 WebAPIでは制御コマンドを受け取って、そのままPWM出力しても良いのですが、FlashAirのWebAPI互換とするため、今回は共有メモリ領域を確保し、ここにステータス情報を定義します。ステータス情報には、速度値と進行方向を設けます。

image2.png

 このステータス情報を、WebAPIから変更できるようにします。PWM出力処理は、周期的にステータス情報を参照して、変化に応じてPWM出力処理を行うようにします。
 こうすることで、WebAPIで制御コマンドを受け取る処理とPWM出力処理を非同期に処理することができます。

###WebAPIの仕様
 今回作成するWebAPIは、共有メモリ領域のステータス情報を変更するものとします。ついでにステータス情報を参照できるようにもします。WebAPIの仕様は、FlashAirのWebAPI互換とします。
 FlashAirのAPIリファレンスは、FlashAir Developersで公開されています。今回、互換性を持たせるAPIは、command.cgi内の以下のAPIです。

#準備
 前回同様、ESP-WROOM-02をArduino化して使用します。そのため、Arduino core for ESP8266をあらかじめArduino IDEに組み込んでおきます。

#ソースコード
 前回実装したWebサーバのソースコードに以下の実装をします。

  • 共有メモリ領域shaerdMemを定義
  • setup関数内で共有メモリ領域shaerdMemを初期化
  • 共有メモリの書き込み/読み出しのWebAPIであるhandleCommand関数の処理を実装
  • 第2回で実装したPWM出力制御処理を追加
  • 共有メモリ上のステータス情報を参照し、変化があれば処理を行うshaerdMemCheck関数を実装
  • loop関数にWebサーバの接続要求待ちを定義
  • loop関数にshaerdMemCheck関数の呼び出しを定義
webserver_parts.ino
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
/*---------------------------------------------------------------------------
//定数定義
---------------------------------------------------------------------------*/
ESP8266WebServer server(80); //Webサーバの待ち受けポートを標準的な80番として定義します

String shaerdMem;            //共有メモリを定義
const int shareMenSize = 512;//共有メモリのサイズを定義

const int pwmPinR = 4;       //PWM出力ピンをIO4で定義(Cerevoのブレイクアウトボードでは10番ピン)
const int pwmPinL = 5;       //PWM出力ピンをIO5で定義(Cerevoのブレイクアウトボードでは14番ピン)
      int status_speed = 0;  //速度の情報
      int status_run_lr = 0; //進行方向の情報
/*---------------------------------------------------------------------------
//サーバリクエスト受信時の正常処理 ルート
---------------------------------------------------------------------------*/
void handleRoot() {
 //ルートにアクセスされた時の処理を書く。
  //ここではESP8266で応答していることと、ESP8266が接続しているアクセスポイントから取得したIPアドレスを返す。
  //DHCPでIPを取得している場合に便利。
  IPAddress myAddr = WiFi.localIP();  
  String mes = "hello from esp8266! IP address:" + String(myAddr[0]) + "." + String(myAddr[1]) +"." +  String(myAddr[2]) +"." +  String(myAddr[3]) + "\n";
  server.send(200, "text/plain", mes);
}
/*---------------------------------------------------------------------------
//サーバリクエスト受信時の正常処理 command.cgi
//
//共有メモリへのデータ書き込み要求
//GET /command.cgi?op=131&ADDR=0&LEN=8&DATA=01234567 HTTP/1.1
//op: 131がデータ書き込みを示す
//ADDR:書き込み開始位置、LEN:書き込みデータの長さ、DATA:書き込むデータ
//共有メモリからデータ読み出し要求
//GET /command.cgi?op=130&ADDR=0&LEN=8 HTTP/1.1
//op: 130がデータ読み出しを示す
//ADDR:読み出し開始位置、LEN:読み出しデータの長さ
---------------------------------------------------------------------------*/
void handleCommand() {
  if(server.method() != HTTP_GET) return;
  String rtnStr = "ERROR";
  String command_cmd;
  String command_addr;
  String command_len;
  String command_data;
  
  for (uint8_t i=0; i<server.args(); i++){
    String strCMD = server.argName(i);
    if(strCMD == "op"){
      command_cmd = server.arg(i);
    }
    else if(strCMD == "ADDR"){
      command_addr = server.arg(i);
    }
    else if(strCMD == "LEN"){
      command_len = server.arg(i);
    }
    else if(strCMD == "DATA"){
      command_data = server.arg(i);
    }
  }

  if(command_cmd == "130"){
      char pbuf[4];
      command_addr.toCharArray(pbuf, sizeof(pbuf));
      int iPos = atoi(pbuf);
      command_len.toCharArray(pbuf, sizeof(pbuf));
      int iLen = atoi(pbuf);

      if(iPos >= shareMenSize){
        server.send(200, "text/plain", rtnStr);
        return;
      }
      if(iLen > shareMenSize - iPos) iLen = shareMenSize - iPos;

      rtnStr = shaerdMem.substring(iPos, iPos + iLen);
  }
  else if(command_cmd == "131"){
      String leftShaerdMem;
      String rightShaerdMem;
            
      char pbuf[4];
      command_addr.toCharArray(pbuf, sizeof(pbuf));
      int iPos = atoi(pbuf);
      command_len.toCharArray(pbuf, sizeof(pbuf));
      int iLen = atoi(pbuf);
      int iLenData = command_data.length();
      
      if(iPos >= shareMenSize){
        server.send(200, "text/plain", rtnStr);
        return;
      }
      if(iLen > shareMenSize - iPos) iLen = shareMenSize - iPos;
      if(iLenData < iLen){
        iLen = iLenData;
      }
      for(int idx = iPos; idx < iPos + iLen; ++idx){
        shaerdMem.setCharAt(idx, command_data[idx - iPos]);
      }
      rtnStr = "SUCCESS";
  }
  server.send(200, "text/plain", rtnStr);
  return;
}
/*---------------------------------------------------------------------------
//サーバリクエスト受信時の異常処理
---------------------------------------------------------------------------*/
void handleNotFound(){
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}
/*---------------------------------------------------------------------------
//初期化処理
---------------------------------------------------------------------------*/
void setup() {
  Serial.begin(115200);
  delay(10);

  pinMode(pwmPinR, OUTPUT);   //PWM出力ピン1を出力に定義
  pinMode(pwmPinL, OUTPUT);   //PWM出力ピン2を出力に定義
  analogWrite(pwmPinR, 0);    //PWM出力ピン1の出力値を0に定義
  analogWrite(pwmPinL, 0);    //PWM出力ピン2の出力値を0に定義
 
  status_speed = 0;           //速度情報を0に初期化
  status_run_lr = 0;          //進行方向情報を0に初期化

  for(int idx = 0; idx < shareMenSize; idx++){
    shaerdMem[idx] = '0';     //共有メモリを初期化
  }

  //自分のSSIDとパスコードを設定する
 //mySSIDとmyPassは任意のものに書き換えること
  Serial.println("WiFi module setting... ");
  WiFi.softAP("mySSID", "myPass");
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());

  //アクセスポイント(myAPSSID)に接続する(20回接続要求して失敗したらエラー表示)
 //myAPSSIDとmyAPPassは自分の環境のものに書き換えること
  Serial.print("Connecting to ");
  WiFi.begin("myAPSSID", "myAPPass");
  int retry_sum = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    retry_sum++;
    if(retry_sum > 20) break;
  }
  if(WiFi.status() == WL_CONNECTED){
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }
  else{
    Serial.println("WiFi not connected");
  }

  //Webサーバの設定を行い、サーバを起動する
  server.on("/", handleRoot);               //ルートに接続要求があった時の処理を指定
  server.on("/command.cgi", handleCommand); //command.cgiに接続要求があった時の処理を指定
  server.onNotFound(handleNotFound);        //設定外に接続要求があった時の処理を指定
  server.begin();                           //Webサーバを起動
  Serial.println("HTTP server started");
}
/*---------------------------------------------------------------------------
//PWM出力制御
//speed_num: 速度(0〜200)
//LR: 1:右方向 0:左方向
---------------------------------------------------------------------------*/
void funcspeed_run(int speed_num, int LR){
  if(status_speed == speed_num && status_run_lr == LR) return;
  if(speed_num < 0 || speed_num > 200) return;
  
  int speed_setnum = speed_num;
  if(speed_setnum > 0) speed_setnum = speed_setnum + 50;
  
  if(LR == 1){
    status_run_lr = 1;
    analogWrite(pwmPinR, speed_setnum*4);
    analogWrite(pwmPinL, 0);
  }
  else{
    status_run_lr = 0;
    analogWrite(pwmPinR, 0);
    analogWrite(pwmPinL, speed_setnum*4);
  }
  status_speed = speed_num;
}
/*---------------------------------------------------------------------------
//共有メモリをチェックする
//shaerdMem [ 0000 0000 ]
//先頭から3文字が速度情報(000〜200)、4文字目が進行方向(1:右方向、0:左方向)を示す。
---------------------------------------------------------------------------*/
void shaerdMemCheck(){
  String tmpStr;
  char pbuf[4];
  tmpStr = shaerdMem.substring(0, 3);
  tmpStr.toCharArray(pbuf, sizeof(pbuf));
  int target_speed = atoi(pbuf);

  tmpStr = shaerdMem.substring(3, 4);
  if(tmpStr == "1"){
    funcspeed_run(target_speed, 1);
  }
  else{
    funcspeed_run(target_speed, 0);
  }
}
/*---------------------------------------------------------------------------
//本体処理
---------------------------------------------------------------------------*/
void loop() {
  server.handleClient(); //Webサーバの接続要求待ち
  shaerdMemCheck();      //共有メモリのステータス情報を参照し変化があれば処理を行う
}

#まとめ
 以上の通り、ESP-WROOM-02をArduino化して、WebAPIを実装しました。
 WebAPIですので、ESP-WROOM-02に無線LANで接続したデバイスのブラウザからアクセスすることで、鉄道模型を制御することができます。
 例えば、以下のURIでアクセスします。

鉄道模型車両を右方向(1)に速度50で走らせる
http://192.168.4.1/command.cgi?op=131&ADDR=0&LEN=8&DATA=05010000
鉄道模型車両を左方向(0)に速度80で走らせる
http://192.168.4.1/command.cgi?op=131&ADDR=0&LEN=8&DATA=08000000
鉄道模型車両を停止させる(どちらでもよい)
http://192.168.4.1/command.cgi?op=131&ADDR=0&LEN=8&DATA=00010000
http://192.168.4.1/command.cgi?op=131&ADDR=0&LEN=8&DATA=00000000
現在の速度と進行方向を参照する
http://192.168.4.1/command.cgi?op=130&ADDR=0&LEN=8

#次回予告
 次回は、今回作成したWebAPIを使って鉄道模型を制御するクライアントアプリを作成します。

#参考資料

#最後に宣伝
 新刊原稿は無事入稿されました!事故がなければ、新刊出ますよ〜。

 コミックマーケット89において、ESP-WROOM-02とFlashAirの電子工作における比較記事を掲載した電子工作(と酒)の同人誌を頒布します。ESP-WROOM-02とFlashAirで比較製作したGゲージ鉄道模型車両の遠隔制御装置の回路図も掲載しています。冬コミにお越しの際は、ぜひお寄りください。また、冬コミ後には通販でも入手できると思います。
12/31(木) 3日目 東メ-08a「空と月」
サ-クルカット.png

6
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?