ESP32 SPIFFS 用 Easy WebSocket ライブラリを作ってみました

ESP32 ( ESP-WROOM-32 )

LEDリアルタイム調光スケッチの入力

では、まず、先ほど開いたサンプルスケッチを編集します。

ESP32_SPIFFS_EasyWebSocket_LED_Sample01

これは、赤、青、緑のLED をスマホでリアルタイム調光するだけのスケッチです。
OLED SSD1331 のリアルタイム制御スケッチは後で紹介します。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <WiFi.h>
#include <WiFiMulti.h>
#include "ESP32_SPIFFS_EasyWebSocket.h" //beta ver 1.60

const char* ssid = "xxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxx"; //ご自分のルーターのパスワードに書き換えてください

const char* HTM_head_file1 = "/EWS/LIPhead1.txt"; //HTMLヘッダファイル1
const char* HTM_head_file2 = "/EWS/LIPhead2.txt"; //HTMLヘッダファイル2
const char* HTML_body_file = "/EWS/dummy.txt"; //HTML body要素ファイル(ここではダミーファイルとしておく)
const char* dummy_file = "/EWS/dummy.txt"; //HTMLファイル連結のためのダミーファイル

ESP32_SPIFFS_EasyWebSocket ews;
WiFiMulti wifiMulti;

IPAddress LIP; //ローカルIPアドレス自動取得用

String ret_str; //ブラウザから送られてくる文字列格納用
String txt = "text send?"; //ブラウザから受信した文字列を ESP32から再送信する文字列

int PingSendTime = 10000; //ESP32からブラウザへPing送信する間隔(ms)

long ESP32_send_LastTime;
int ESP32_send_Rate = 300;
byte cnt = 0;

#define ledPin1 5
#define ledPin2 17
#define ledPin3 16

//*************セットアップ*************************
void setup(){
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.print(F("Connecting to "));
  Serial.println(ssid);

  wifiMulti.addAP(ssid, password);

  Serial.println(F("Connecting Wifi..."));
  if(wifiMulti.run() == WL_CONNECTED) {
      Serial.println("");
      Serial.println(F("WiFi connected"));
      Serial.println(F("IP address: "));
      LIP = WiFi.localIP(); //ESP32のローカルIPアドレスを自動取得
      Serial.println(WiFi.localIP());
  }
  delay(1000);

  ews.EWS_server_begin();

  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);

  Serial.println(); Serial.println("Initializing SPIFFS ...");

  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS failed, or not present");
    return;
  }

  Serial.println("SPIFFS initialized. OK!");

  sigmaDeltaSetup(0, 312500);
  sigmaDeltaSetup(1, 312500);
  sigmaDeltaSetup(2, 312500);
  sigmaDeltaAttachPin(ledPin1,0);
  sigmaDeltaAttachPin(ledPin2,1);
  sigmaDeltaAttachPin(ledPin3,2);
  sigmaDeltaWrite(0, 0); // LED OFF
  sigmaDeltaWrite(1, 0); // LED OFF
  sigmaDeltaWrite(2, 0); // LED OFF

  TaskHandle_t th; //ESP32 マルチタスク ハンドル定義
  xTaskCreatePinnedToCore(Task1, "Task1", 4096, NULL, 5, &th, 0); //マルチタスク core 0 実行

  ESP32_send_LastTime = millis();
}
//*************メインループ*************************
void loop() {
  websocket_handshake();

  if(ret_str != "_close"){
    if(millis()-ESP32_send_LastTime > ESP32_send_Rate){
      if(cnt > 3){
        cnt = 0;
      }
      websocket_send(cnt, txt);
      cnt++;
      ESP32_send_LastTime = millis();
    }
    ret_str = ews.EWS_ESP32CharReceive(PingSendTime);
    if(ret_str != "\0"){
      Serial.println(ret_str);
      if(ret_str != "Ping"){
        if(ret_str[0] != 't'){
          int ws_data = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
          switch(ret_str[4]){
            case '!':
              ESP32_send_Rate = ws_data;
              break;
            case 'B':
              LED_PWM(1, 0, floor(ws_data/2));
              break;
            case 'G':
              LED_PWM(1, 1, floor(ws_data/2));
              break;
            case 'R':
              LED_PWM(1, 2, floor(ws_data/2));
              break;
            case '_':
              LED_PWM(2, 0, floor(ws_data/2));
              break;
            case 'A':
              LED_PWM(3, 0, ws_data);
              break;
            case 'O':
              LED_PWM(4, 0, ws_data);
              break;
          }
        }else if(ret_str[0] == 't'){
          txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1);
          Serial.println(txt);
        }
      }
    }
  }else if(ret_str == "_close"){
    ESP32_send_LastTime = millis();
    ret_str = "";
  }
}

//************* マルチタスク ****************************************
void Task1(void *pvParameters){
  while(1){
    //ここに別のタスクを作成

    delay(1); //マルチタスクwhileループでは必ず必要
  }
}

//**************************************************************
void LED_PWM(byte Led_gr, byte channel, int data_i){
  Serial.println(data_i);
  switch(Led_gr){
    case 1: //1つのLEDを調光制御
      sigmaDeltaWrite(channel, floor(data_i*2.5));
      break;
    case 2: //3つのLEDを順番に点灯、消灯
      if(data_i < 34){
        sigmaDeltaWrite(0, data_i*7);
        sigmaDeltaWrite(1, 0);
        sigmaDeltaWrite(2, 0);
      }else if(data_i > 33 && data_i < 67){
        sigmaDeltaWrite(1, (data_i-33)*7);
        sigmaDeltaWrite(0, 0);
        sigmaDeltaWrite(2, 0);
      }else if(data_i > 66){
        sigmaDeltaWrite(2, (data_i-66)*7);
        sigmaDeltaWrite(0, 0);
        sigmaDeltaWrite(1, 0);
      }
      break;
    case 3: //3つのLED全点灯
      sigmaDeltaWrite(0, 255);
      sigmaDeltaWrite(1, 255);
      sigmaDeltaWrite(2, 255);
      break;
    case 4: //3つのLED全消灯
      sigmaDeltaWrite(0, 0);
      sigmaDeltaWrite(1, 0);
      sigmaDeltaWrite(2, 0);
      break;
  }
}
//*********************************************
void websocket_send(uint8_t count, String str_txt){
  String str;
  //※WebSocketへのテキスト送信は110 byte 程度なので、全角35文字程度に抑えること
  switch(cnt){
    case 0:
      str = str_txt;
      break;
    case 1:
      str = "WebSocket";
      break;
    case 2:
      str = "Hello!!";
      break;
    case 3:
      str = "World!!";
      break;
  }

  ews.EWS_ESP32_Str_SEND(str, "wroomTXT"); //ブラウザに文字列を送信
}
//************************* Websocket handshake **************************************
void websocket_handshake(){  
  if(ews.Get_Http_Req_Status()){ //ブラウザからGETリクエストがあったかどうかの判定
    String html_str1="", html_str2="", html_str3="", html_str4="", html_str5="", html_str6="", html_str7="";

    //※String変数一つにEWS_Canvas_Slider_T関数は2つまでしか入らない
    html_str1 += "<body style='background:#000; color:#fff;'>\r\n";
    html_str1 += "<font size=3>\r\n";
    html_str1 += "ESP-WROOM-32(ESP32)\r\n";
    html_str1 += "<br>\r\n";
    html_str1 += "ESP32_SPIFFS_EasyWebSocket Beta1.60 Sample\r\n";
    html_str1 += "</font><br>\r\n";
    html_str1 += ews.EWS_BrowserSendRate();
    html_str1 += "<br>\r\n";
    html_str1 += ews.EWS_ESP32_SendRate("!esp32t-Rate");
    html_str1 += "<br>\r\n";
    html_str1 += ews.EWS_BrowserReceiveTextTag2("wroomTXT", "from ESP32 DATA", "#555", 20,"#00FF00");
    html_str1 += "<br>\r\n";
    html_str1 += ews.EWS_Status_Text2("WebSocket Status","#555", 20,"#FF00FF");
    html_str1 += "<br><br>\r\n";

    html_str2 += ews.EWS_TextBox_Send("txt1", "Hello Easy WebSocket Beta1.60","送信");
    html_str2 += "<br><br>\r\n";
    html_str2 += "LED \r\n";
    html_str2 += ews.EWS_On_Momentary_Button("ALL", "ALL-ON", 80,25,15,"#000000","#AAAAAA");
    html_str2 += ews.EWS_On_Momentary_Button("OUT", "ALL-OFF", 80,25,15,"#FFFFFF","#555555");
    html_str2 += "<br>\r\n";

    html_str3 += "<br>LED BLUE Dim\r\n";
    html_str3 += ews.EWS_Canvas_Slider_T("BLUE",200,40,"#777777","#0000ff"); //CanvasスライダーはString文字列に2つまでしか入らない
    html_str3 += "<br>LED GREEN Dim\r\n";
    html_str3 += ews.EWS_Canvas_Slider_T("GREEN",200,40,"#777777","#00ff00"); //CanvasスライダーはString文字列に2つまでしか入らない

    html_str4 += "<br>LED RED..... Dim\r\n";
    html_str4 += ews.EWS_Canvas_Slider_T("RED",200,40,"#777777","#ff0000"); //CanvasスライダーはString文字列に2つまでしか入らない
    html_str4 += "<br>LED RGB..... Dim\r\n";
    html_str4 += ews.EWS_Canvas_Slider_T("_RGB",200,40,"#777777","#ffff00");

    html_str7 += "<br><br>\r\n";
    html_str7 += ews.EWS_WebSocket_Reconnection_Button2("WS-Reconnect", "grey", 200, 40, "black" , 17);
    html_str7 += "<br><br>\r\n";  
    html_str7 += ews.EWS_Close_Button2("WS CLOSE", "#bbb", 150, 40, "red", 17);
    html_str7 += ews.EWS_Window_ReLoad_Button2("ReLoad", "#bbb", 150, 40, "blue", 17);
    html_str7 += "</body></html>";

    //WebSocket ハンドシェイク関数
    ews.EWS_HandShake_main(3, HTM_head_file1, HTM_head_file2, HTML_body_file, dummy_file, LIP, html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7);
  }
}

【解説】

●5-6行目:
ご自分のルーター環境の SSID と パスワードに書き換えてください。

●8-11行:
SPIFFSフラッシュにアップロードした WebSocket通信用、HTML 分割ヘッダファイルを指定しています。
EWSフォルダ下にアップロードしたので、/EWS/ と記入することを忘れないでください。

●13行:
自作ライブラリのクラス名を指定します。
好きな名前が可能ですが、ここでは ews としています。

●14行:
以前、WiFiMulti にできないかと問合せがあったので、今回は WiFiMulti にしました。

●16行:
ルーターが自動で割り当てたローカルIPアドレスを格納するための定義です。

●21行:
スマホとWebSocket通信を継続するために ESP32 から Ping信号を送信する間隔を決めます。
とりあえず、10秒毎で良いと思います。

●24行:
ESP32 からスマホへデータを送信する間隔を決めます。
ここではテスト用のテキストを送信しているだけなので、デフォルトで300ms としておきます。
後でスマホ側で変えられます。

●27-29行:
LEDを接続するGPIO の番号を決めます。

●36-49行:
ルーター(アクセスポイント)と wifiMulti ライブラリで接続します。
別に wifiMultiでなくてもOKです。
47行目でルーターが自動割り当てしたローカルIPアドレスを取得しています。
ここで取得した IP アドレス、分割された HTMLヘッダファイルとマージしてスマホに送信して、WebSocketコネクション確立します。

●52行:
ESP32 ( ESP-WROOM-32 )をサーバーにする関数です。

●67-75行:
LED を調光するための SigmaDelta変調を行う設定です。

●77-78行:
WebSocket通信は、ESP32 デュアルコアを利用したマルチタスクを使うと安定しますので、ここでタスクハンドル定義しておきます。
ESP32 のマルチタスクを使う方法は以下の記事を参照してください。
Arduino – ESP32 のマルチタスク ( Dual Core ) を試す

●83-134行:
WebSocket 通信でブラウザと通信する部分についてはメインループ内で制御します。
84行の関数は、201-248行で関数化していて、スマホからGETリクエストがあった場合に、ESP32 から HTMLをスマホへ吐き出し、WebSocket ハンドシェイク(コネクション確立)させます。
WebSocket ハンドシェイクについては以下の記事を参照してください。
Arduino化 WROOM で WebSocket ハンドシェイク 確立方法

87-94行でESP32 側からスマホブラウザへテキストデータを送信しています。
ESP32_send_Rate はデフォルトで 300ms としていますが、通信トラフィックが混み過ぎるとフリーズしてしまうので、あまり短くし過ぎない方が良いです。

95行で、ブラウザから送信されてくるテキストデータを受信します。
テキスト情報として1回で送信される最大バイト数は120byte程度です。
UTF-8文字列の場合40文字以下だと思ってください。
そのテキストはスマホ側のスライダーが操作されると、青色 LED の場合、

061|BLUE;

というテキストが送られてきます。
最初の3桁の数値は全長200pixel のスライダーの値で、61/200 なので、明るさの最大値が100% とすると、明るさ数値は 30% に計算させねばなりません。
最初の3桁の文字列数値を数値に変換しているのが、100行目です。
ret_str[4] で4バイト目の文字を判別して、どのLED が選ばれたかを判別します。
BLUE という文字は、219行のブラウザのCanvas_Slider の ID文字列、”BLUE” と対になっています。

124-127行目にあるように、ブラウザのテキストボックスから文字列が送られてきた場合は、

txt1|Hello Easy WebSocket

という形で送信されてくるので、最初が ”t” の文字だった場合はテキスト文字列データだと判別します。
txt1 という文字列は、221行のテキストボックスのID文字列、”txt1” と対になっています。

ブラウザからのデータ送信方法については、以下の記事を参照してみてください。

Arduino化 WROOM で WebSocket データ送受信方法

●137-143行:
もし、WebSocket 通信とは別にセンサーデータを処理したい場合などは、別タスクにすれば、WebSocket通信中でも処理が止まらなくて良いと思います。
例えば、ディスプレイに文字をスクロール表示させている時などです。

●146-178行:
ここで、LED 調光を制御しています。

●180-199行:
ESP32 ( ESP-WROOM-32 )からスマホブラウザへデータを送信する関数です。
198行で実際に送信しています。
“wroomTXT” は、216行のブラウザのForm枠内のID “wroomTXT” と対になっています。

●201-248行:
ブラウザから GETリクエストがあった場合、ブラウザへ返す HTMLファイルをここで生成します。
あまりに多量の文字列なので、グローバル変数領域にhtml_str文字列を宣言しない方が賢明です。
ローカル関数のスタック領域で確保して、コネクション確立したらメモリを開放できる、この方式がベストだと思います。
可能であれば、HTMLファイル全てをSPIFFS から読み取っても良いかもしれません。
いろいろ試してみて下さい。

HTML ヘッダ部分は SPIFFS から読み取って送信していて、<body>部分は html_str に文字列を格納して送信しています。
Canvas_Slider関数は多量の文字列を格納しているので、String文字列に格納できるのは2つまでですのでご注意ください。

ESP32 からのデータ送信間隔は214行目でセレクトボックスを作成するHTMLを作っていますので、ブラウザから送信間隔をリアルタイムに変えられます。

その他の使い方は過去の記事に散らばって記述してありますので、検索窓で検索してみてください。
ここでは記事の都合と、私の個人的な疲労の関係上、省略させていただきます。

コンパイル書き込み実行

コンパイル書き込みする前に、まずは、ご自分のルーターを起動しておいて、ESP-WROOM-32 ( ESP32 )が接続できるようにファイアウォール等の設定をしておいてください。

環境がすべて整ったら、コンパイル書き込み実行させてみてください。

ちなみに、最新版(2018/1/10時点) Arduino IDE では、1回目のコンパイル書き込みで必ず失敗します
間髪入れずにもう一度コンパイル書き込みすると問題無く書き込むことができます。
これの原因は不明です。

では、書き込み後、間髪入れずにシリアルモニターを 115200 bps で起動してください。
下図の様になれば、スマホとの接続OKです。

そうしたら、スマホブラウザのURL入力欄にローカルIPアドレスを入力してください。

ここでは、162.168.0.14 とシリアルモニターに表示されたので、それを入力します。
すると、下図の様に、数秒時間を置いて、下図の様な画面が出て、WebSocket CONNECTED と出ればOKです。

※Disconnected と出た場合は、下方のWS-Reconnet ボタンを押してみて下さい。
URL欄に再入力した場合は Disconnected になってしまう場合があります。
この原因は不明です。

青色LED を調光した場合のシリアルモニターは、下図のような感じで表示され、ブラウザがテキスト形式でデータが送信されていることが分かると思います。

あとは、冒頭で紹介した動画のような動作になっていればOKです。
次では、OLED ディスプレイに日本語フォントを表示させながらのリアルタイムコントロールについてのコードを紹介します。

コメント

  1. 太田 耕正 より:

    ESP32 SPIFFS 用 Easy WebSocket ライブラリの公開有難う御座います。
    LEDリアルタイム調光スケッチと、有機EL SSD1331をリアルタイム制御スケッチ共に動作確認出来ました。
    全角文字のスクロール、スムーズな動作に感激です。SPIFFSのメモリサイズアップも問題なく出来ました。
    赤、青、緑のLED をスマホでリアルタイム調光もESP02にくらべ反応も早く感じます。
    私は家電の制御にSPIFFS 用 Easy WebSocketを利用してます。
    余談ですがIPアドレスは固定にて使用してます、スケッチの void setup(){ の
    次に、2行追加して モニターに表示されたIPアドレスに書き換えてます、XXXの部分
    WiFi.mode(WIFI_STA); // 無線LANをSTAモードに設定

    WiFi.config(IPAddress(XXX, XXX, XXX, XXX), IPAddress(XXX, XXX, XXX, 1), IPAddress(255, 255, 255, 0));
    動作確認テストのみなら必要ないかも
    いつも貴重なブログでに感謝してます、有難う御座いました。

    • mgo-tec mgo-tec より:

      太田さん

      こちらこそ、いつもブログご覧いただき、ありがとうございます。

      ちゃんと動いてくれてホッとしました。
      家電の制御に使っていただけるとは、感謝感謝です。

      固定IP で使ってらっしゃるんですね。
      やっぱり、アクセスポイントとのコネクションはユーザーが自由に設定出来た方が良いですね。

      もう ESP32 を使ってしまうと、私は ESP8266 には戻れなくなってしまいました。
      ESP32 の方がマルチタスクで動作できて、CPU も高速なので、WebSocket通信も確実に速いし、安定してますね。

      今年は WebSocket をいろいろ発展させていきたいと思っております。
      今後とも当ブログをよろしくお願いいたします。
      m(_ _)m

  2. mkoba より:

    mgo-tec 様

    いつも大変参考にさせていただいています。
    ESP32版 SPIFFS対応のwebsocket ライブラリの公開
    大変ありがたく思います。
    リアルタイム調光もうまく使うことができました。
    ただSPIFFS版ではcolor_pickerが実装されていませんが、
    SPIFFSではなにか技術的に使えなかったのでしょうか?

    機能的にはRGBのスライダーで調光できるので問題ないですが、
    ちょっと欲がでてしまいました。

    • mgo-tec mgo-tec より:

      mkobaさん

      記事をご覧いただき、ありがとうございます。
      しばらく WebSocket を使っていなかったので、思い出すにちょっと時間がかかりました。

      実は、最近は M5Stack ばかり使っていて、M5Stack の場合は SPIFFS を使うよりも micro SD カードを使った方が圧倒的に処理が速いので、SPIFFS 版の EasyWebSocket を保守していなかったということが実情です。
      M5Stack に限らず、ESP32開発ボードでも同じで、micro SD カードの方が圧倒的に速いです。
      ですから、もう SPIFFS の保守を止めようかと思っていたくらいです。

      でも、mkobaさんからリクエストがあって、特に技術的に難しいこともなかったので、color_picker関数を追加しておきました。
      beta ver 1.61 です。

      https://github.com/mgo-tec/ESP32_SPIFFS_EasyWebSocket

      使い方は、以下の記事を参照してください。
      ただし、SDカード用ですが、使い方は同じです。

      M5stack ( ESP32 )電光掲示板をスマホでリアルタイムコントロールしてみた

  3. 匿名 より:

    mgo-tec様

    昨日の今日で早速対応していただけるとは、感謝感激です。
    たしかにSDカードの方が早いのですが、SPIFFSはESP32単体で
    テストできるので、テスト段階では重宝するので使っています。

    ありがとうございました。

  4. インカマスゴリラ より:

    初めまして!最近arduinoを触りだしていて色々と参考にしています。少しだけ質問があり、書かせていただきます。
    void loop 内のif(ret_str != “_close”){の記述なんですが、”_close”の送っている場所などは何処に記述がありますか?
    もし、何か送っている場所が何処に書いているのか、また何故”_close”なのか教えてくだされば幸いです。
    arduinoで色々と出来るのが楽しいです。これからも頑張って下さい!

    • mgo-tec mgo-tec より:

      インカマスゴリラさん

      記事ご覧いただき、ありがとうございます。

      この記事はもう3年近く前のもので、今自分のこのコードを見返すと、とっても恥ずかしいほどの素人プログラミングだったんだと落胆しております。
      とにかく、私は独学でプログラミングしているので、あまり参考にしない方が良いと思います。

      さて、_close のことですが、これは私の自作ライブラリESP32_SPIFFS_EasyWebSocket の ESP32_SD_EasyWebSocket.cpp というファイルに記述していますので参照してみてください。
      EWS_ESP32CharReceiveという関数のところです。

      また、この記事のソースコードでは95行目のところで、スマホからクローズコマンドが送られて来たら、EWS_ESP32CharReceive関数で”_close”という文字列を返します。

      とにかくこの記事のコードやライブラリはかなり未熟な時に書いているので、多々無駄があります。
      それに命名規則やコーディング規約もメチャメチャなのでご容赦ください。
      それに、その当時は動いたけど、今のArduino core for the ESP32では動かないことも有り得ますのでご了承いただければと思います。

タイトルとURLをコピーしました