ESP32 -DevKitC ( ESP-WROOM-32 )でスマホとWi-Fi リアルタイム双方向同時通信

ESP32 ( ESP-WROOM-32 )

スケッチの入力

では、実際にスケッチを入力していきます。

コメント欄でご指摘いただき、サンプルスケッチの 160行目の "■" を "*" へ変更しました。
"■" が表示されない環境があるようなので、修正しました。
(2017/6/19)

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

#include <ESP32_SD_EasyWebSocket.h> //beta ver 1.51 
 
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ファイル連結のためのダミーファイル
 
const uint8_t cs_SD = 5; //SDcard CS(ChipSelect)

SD_EasyWebSocket ews;
 
IPAddress LIP; //ローカルIPアドレス自動取得用
 
String ret_str; //ブラウザから送られてくる文字列格納用
 
int PingSendTime = 10000; //ESP32からブラウザへPing送信する間隔(ms)
 
long ESP32_send_LastTime;
int ESP32_send_Rate = 20;
 
int min_adc = 650; //ブラウザに表示するグラフのADC最小値。環境によって変えてください。
int max_adc = 3800; //ブラウザに表示するグラフのADC最大値。環境によってかえてください。
float adc_deg; //ESP32 の ADC値の範囲
 
#define ledPin1 12 //GPIO #12
#define ledPin2 13 //GPIO #13
#define ledPin3 14 //GPIO #14
#define CdS_cel 36 //GPIO #36
 
void setup() 
{
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(CdS_cel, INPUT); //cdsセルを接続するポート GPIO #36
     
  ews.AP_Connect(ssid, password);
  delay(1000);
   
  LIP = WiFi.localIP(); //ESP32のローカルIPアドレスを自動取得
 
  Serial.println(); Serial.println("Initializing SD card...");
 
  if (!SD.begin(cs_SD, SPI, 40000000, "/sd")) {
    Serial.println("Card failed, or not present");
    return;
  }
 
  Serial.println("card 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
 
  adc_deg = (max_adc - min_adc)/20; //ブラウザに表示するグラフを20分割
   
  ESP32_send_LastTime = millis();
}
 
void loop() {
  websocket_handshake();
   
  if(ret_str != "_close"){
    if(millis()-ESP32_send_LastTime > ESP32_send_Rate){
      websocket_send();
      ESP32_send_LastTime = millis();
    }
 
    ret_str = ews.EWS_ESP32CharReceive(PingSendTime);
    if(ret_str != "\0"){
      Serial.println(ret_str);
      if(ret_str != "Ping"){
        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 == "_close"){
    ESP32_send_LastTime = millis();
    ret_str = "";
  }
    yield(); //これ重要かも
}
//**************************************************************
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(){
  int adc = analogRead(CdS_cel); //cdsセルセンサー値を読み取る。GPIO #36
  int adc_g = floor(((float)(adc - min_adc)) / adc_deg);
  String graph_str = "";
   
  if(adc_g <= 0 ){
    adc_g = 0;
    graph_str = "OFF";
  }else{
    for(int i=0; i<adc_g; i++){
      graph_str += "*";
    }
  }
   
  ews.EWS_ESP32_Str_SEND(graph_str, "wroomTXT"); //ブラウザに文字列を送信
}
//************************* Websocket handshake **************************************
void websocket_handshake(){  
  if(ews.Get_Http_Req_Status()){
    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 += "SD_EasyWebSocket Beta1.51 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_str1 += "LED \r\n";
    html_str1 += ews.EWS_On_Momentary_Button("ALL", "ALL-ON", 80,25,15,"#000000","#AAAAAA");
    html_str1 += ews.EWS_On_Momentary_Button("OUT", "ALL-OFF", 80,25,15,"#FFFFFF","#555555");
    html_str1 += "<br>\r\n";
     
    html_str2 += "<br>LED BLUE... Dim\r\n";
    html_str2 += ews.EWS_Canvas_Slider_T("BLUE",200,40,"#777777","#0000ff"); //CanvasスライダーはString文字列に2つまでしか入らない
    html_str2 += "<br>LED GREEN Dim\r\n";
    html_str2 += ews.EWS_Canvas_Slider_T("GREEN",200,40,"#777777","#00ff00"); //CanvasスライダーはString文字列に2つまでしか入らない
     
    html_str3 += "<br>LED RED..... Dim\r\n";
    html_str3 += ews.EWS_Canvas_Slider_T("RED",200,40,"#777777","#ff0000"); //CanvasスライダーはString文字列に2つまでしか入らない
    html_str3 += "<br>LED RGB..... Dim\r\n";
    html_str3 += ews.EWS_Canvas_Slider_T("_RGB",200,40,"#777777","#ffff00");
    
    html_str4 += "<br><br>\r\n";
    html_str4 += ews.EWS_WebSocket_Reconnection_Button2("WS-Reconnect", "grey", 200, 40, "black" , 17);
    html_str4 += "<br><br>\r\n";  
    html_str4 += ews.EWS_Close_Button2("WS CLOSE", "#bbb", 150, 40, "red", 17);
    html_str4 += ews.EWS_Window_ReLoad_Button2("ReLoad", "#bbb", 150, 40, "blue", 17);
    html_str4 += "</body></html>";

    //WebSocket ハンドシェイク関数
    ews.EWS_HandShake_main(3, cs_SD, 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);
  }
}

【解説】

●1行目:
自作ライブラリのインクルードです。

●3-4行目:
ここで、xxxxのところをご自分のルーターのSSID とパスワードに書き換えてください。

●6-9行目:
先ほどコピーした micro SDHC カード内のファイルを定義します。
dummy.txtファイルにご自分でHTMLタグを入力しても良いかもしれません。

●11行目:micro SDカードスロットの CSピン ( Chip-Select )を定義します。

●13行目:
自作ライブラリSD_EasyWebSocket のクラス名を定義します。
ews というところを好きな名前にすることができます。

●15行目:
ESP-WROOM-32 ( ESP32 )のWi-Fi ローカルIPアドレスのハンドル名を定義します。

●17行目:
ブラウザからWebSocket通信で送られてくるテキスト形式文字列を格納するためのものです。

●19行目:
ESP-WROOM-32 ( ESP32 )からPingコマンドを送信する間隔を決めます。
ここでは10秒としています。
Ping をブラウザに送信して、Pongが返って来なければ、ESP32側で強制切断します。

●22行目:
ここでは、ESP32 からデータを送信する間隔時間を格納する変数を初期化しています。
最初は20msとしておきます。

●24-26行目:
ESP32 のADコンバーターは12bit なので、電圧値を0-4095 までの値で返します。
LEDが消灯していても、CdSセルは周囲の明るさによって最低電圧値が変わるので、ご自分の環境によってこの値を変えてください。

●28-30行目:
LED を接続するGPIO ピンを定義します。

●35-38行目;
LEDに接続するピンはOUTPUTモードにし、CdSセルに接続するADCピンはINPUTモードにします。

●40行:
ESP-WROOM-32 ( ESP32 )とルーターをコネクションする関数です。

●43行:
ここで、ルーターが割り当てたESP-WROOM-32 ( ESP32 )のローカルIPアドレスを自動取得します。

●47行:
ここで、SDカードライブラリを初期化します。
そこをGPIOピン番号に変えることによって、CSピンを変えることができるようです。
ここは、SD.begin() としても良いのですが、デフォルトの4MHzとなってしまいます。
ここでは40MHz としてみました。

●54-62行:
LED を PWM制御して調光するためのピン設定です。
Arduino UNO では analogWrite などでPWM制御できますが、ESP32 ではsigmaDelta という関数を使うことになります。
54-56行でそれぞれのピンの周波数を決めて、57-59行でGPIOピン番号とsigmaDelta関数のチャンネル番号を定義して、60-62でLEDに出力します。
ここではLEDを消灯してます。
GPIOピン番号とsigmaDelta関数のチャンネル番号とが別々というところがミソですね。

●64行:
CdSセル光センサー値の範囲を20分割します。
後でブラウザにグラフを表示するために使います。

●70行:
メインループ内にこの自作関数を置くことによって、ブラウザとWebSocket ハンドシェイク(コネクション確立)が済んだかどうかを常に監視しています。
167-211行にあります。

●72-111行目:
121行目でブラウザから送信されてくる文字列を格納し、それ以降で文字列から文字を抽出して、条件分岐して処理しています。
ブラウザ側で、Blue のLED が70%点灯というスライダー値を送信したら、
070|BLUE
という文字列をESP32で受信します。
“BLUE” という文字列は68行目でブラウザのボタンのHTMLタグで使われるID文字列です。
そのボタンが押されたら、ID文字列とスライダーの値をESP32側へ送信してくれます。
この文字列を分解して、それぞれLED を制御しています。
Canvas Slider は0-200の値で返ってくるので、Max100%にするために2で割っています。
85行目でブラウザのセレクトボックスからESP32 の送信間隔時間を取得します。
それを73-76行目で判断して、ブラウザに明るさグラフを表示するデータを送信する間隔を決めています。
この時間が小さすぎると、ESP32から怒涛のようにブラウザにデータを送信するので、WebSocketがフリーズする場合があります。
ただ、フリーズしても、Ping送信でPongが返って来なければ強制切断します。

●115-148行:
ここは、LEDをPWM制御で点灯させるための条件分岐です。

●150-165行:
SVPピン( GPIO #36 )で読み取った電圧値を20分割して、その分の文字、”*” を連ねてString文字列としてブラウザに送信します。
164行目の自作ライブラリ関数で実際に送信しています。
“wroomTXT” というID文字列と182行目のブラウザ側のHTMLタグのID文字列と一致させています。

●167-211行:
EasyWebSocket ライブラリの Beta 1.51 から、HTML文字列をローカル関数内で定義することができるようになりました。
それによって、このローカル関数を抜けるとメモリを解放してくれて、SRAM消費の削減になります。
168行目の Get_Http_Req_Status関数は EasyWebSocket ライブラリ Beta 1.51 から追加した関数です。
ブラウザから GET リクエストがあると、ture を返します。
そうしたら、HTML文字列を代入します。
ブラウザに送信するHTMLタグのうち、先ほどコピーしたmicro SDHC カード内のファイルにはヘッダ要素が入っており、ここでは、body要素をString変数に格納して、後でマージしてブラウザに送信します。
ここでSRAMメモリを使わずとも、micro SDHC カードに全て格納しても良いです。
だだ、この場合は自作のHTML文字列吐き出し関数があるので、HTMLを一から組むよりかは楽かと思います。
関数の使い方は過去の当ブログの EasyWebSocket 記事を確認してみて下さい。

因みに、Canvas_Slider関数は文字列が多いため、String変数一つに2つまでしか入れることが出来ません。よって、html_str変数を複数に分けて格納しています。

今回新たに追加した関数が180行目の

EWS_ESP32_SendRate(“ID name”)

です。
これは、ESP32側からブラウザに送信する時間間隔を、スマホ側(ブラウザ側)でSelect Boxでリアルタイムに決めることができます。

スマホによっては、送信間隔を0ms としてしまうとフリーズしてしまので、これを使ってスマホ側で調整できるようにします。

#FF00FF みたいな文字列は色を24bitカラーで指定しています。HTML標準の表記です。

●209行:
EasyWebSocket beta 1.51 から追加になった関数です。
従来のHandShake 関数を分割したものです。
要するに、ここでブラウザとのWebSocket通信ハンドシェイク(コネクション確立)を行います。
最初の数字は3としてください。
この数値は、HTML文字列の送信数やファイル送信数を決めるモード番号です。
詳細は以下のページを参照してください。

EasyWebSocket ライブラリ Beta 1.51 をアップしました

コンパイル実行

まず、コンパイル実行する前に、ご自分のルーターの設定を見直し、ESP-WROOM-32 ( ESP32 )がセキュリティーで弾かれない様にしておいてください。

では、いよいよ、ESP32 -DevKitC で Arduino IDE スケッチをコンパイルしてみましょう。
ESP32-DevKitC の場合は基本的にリセットボタン等を押さなくても書き込みできます。
ただし、リセット自動遷移回路の不具合で、書き込みできない場合が多々あります。
その場合はArduino IDE のメニューの「ツール」でUpload Speed を 115200 にすると書き込める場合があります。
私の場合は921600では度々失敗するので、115200にしたら安定して書き込めました。
ENピンとGNDの間に0.1μFコンデンサを入れると良いという情報がありましたが、私の場合はうまくいきませんでした。
今はOTAでも書き込めるようになったので、安価な製品だからということで、あまり追求せずに諦めています。

コンパイル書き込み成功したら、シリアルモニターを115200bps で起動してください。
文字が表示されていなかったら、ENピンボタン(リセットボタン)を押してください。
リセットボタンは下図の場所です。

シリアルモニターにはこのように表示されます。

ここで表示されたESP-WROOM-32 ( ESP32 )のローカルIPアドレスをスマホのブラウザのURL入力欄に入力してください。
あとは上記の動画のように操作します。

※「複数の SD.h が見つかりました」と警告が出る場合は、インストールした Arduino IDE フォルダの中の libraries フォルダ内の SDフォルダを削除するか、別のフォルダに移動してください。
Arduino 標準の SDライブラリと競合してしまうようです。

動作確認が取れているスマホは以下の通りです。

Android 7.0  Google Chrome
iOS 10.2.1  Safari

Microsoft Edge は動作しないので注意してください。
ブラウザは Google Chrome をお勧めします。

タッチパネルのないパソコンでマウスではCanvasSlider は動作しませんのでご注意ください。

ESP8266の時はAndroidがフリーズが少なく絶好調だったのですが、ESP32 の場合は iOS の方が絶好調でした。
1~2世代前のiPad を使いました。
ハード側の通信能力にも左右されるっぽいですね。

Android Google Chrome操作画面は下図の様になります。

ESP32 の転送Rate初期値は、スケッチ上で20ms にしていますが、フリーズするならばもっと大きい値にしてください。
その代わり、光センサーグラフの反応が遅くなります。
フリーズしたら、10秒後にPingをブラウザに送信するので、Pongが返って来なければ強制切断されます。
そうしたら、WS-Recconect ボタンを押すと再接続されます。

まとめ

いかがでしたでしょうか。
以上、ESP32 -DevKitC ( ESP-WROOM-32 開発ボード )とスマホでWebSocketリアルタイム双方向同時通信でした。
もし、不具合等あればコメント等でご連絡いただけると助かります。

私はまだこのWebSocket でセキュア通信をやったことがありません。
これからの時代はそういう通信が必須になりますので、いつかライブラリを作ろうとは思っています。
また、WANで WebSocket をやったことがないので、これも課題ですね。

Arduino core for the ESP32 はまだまだ発展途上で、CPUが80MHz以上がありません。
今後のクロックアップに期待したいです。

それと、SPIFFSライブラリもありません。
それができれば、何も文句ないのですがね・・・。

ただ、SRAMがESP8266より5倍大きいので、CPUまでフリーズしてしまうことは無くなりました。
それに、3.3V 12bit ADコンバーターが付いているのもとても有難いです。
これからは、ESP-WROOM-32 ( ESP32 )主体で開発していこうと思っています。

ではまた・・・。

コメント

  1. 太田 耕正 より:

    ブログ ソースコード 160行 graph_str += “■”; をコピーペーストして実行すると
    10011101
    Invalid UTF-8 in text frame—-Closing Message
    ——————Close Command Send

    ——————Client.STOP
    _close
    となります。
    ブログの実行画面では”*******”が表示されてますね。
    graph_str += “*”;で正常に動作出来ました。

    • mgo-tec mgo-tec より:

      太田さん

      ご指摘ありがとうございます。
      ■の方が見た目が良いかなと思って “*” から “■” に変えていました。
      やっぱりこういう全角記号は正常に動作しない端末があることをあまり考えてませんでした。
      早速修正させていただきました。
      ご報告、たいへん感謝感謝です。
      m(_ _)m

  2. puru より:

    お疲れ様です。
    先日はお問い合わせのメールを送ってしまい失礼いたしました。改めて質問させていただきます。
    この記事では光センサーで値の追従を行っていますが、ESP32内蔵もしくは外付けで取ったADCの値をSSE通信で受信端末側に表示させることは可能でしょうか?
    #includeを使用した場合にanalogRead()の命令が使えなくなるようなことを海外のQ&Aで見かけましたがmgo-tec様の意見をお聞きしたいです。

    • mgo-tec mgo-tec より:

      puruさん

      コメント欄にお越しいただき、ありがとうございます。
      ESP32を使ったServer-Sent Eventsサンプルスケッチは、ESP8266用の以下の記事の一番最後の方に追記しておきました。

      WROOM 単体に Arduino スケッチで Wi-Fi ストリーミング

      これは、ESP32のNTP時計値をスマホにリアルタイムで送り続けるものですが、時間の値をanalogRead値に替えれば行けると思います。
      そのページのコメント投稿欄も参照してみてください。
      他の方が既に試されていました。

      因みに、ESP32のADCは、0V~1Vだったと思いますので、それ以上の電圧は減圧しなければなりません。
      また、ADC入力専用GPIOピンを使う事です。
      includeしたら使えなくなるというのは、何をincludeしたらでしょうか?
      私には意味が良く分かりませんでした。

      以上、これで試してみて下さい。

  3. Michael Yanagiba より:

    ESP32の通信の記事を探していてたどり着きました。私は回路屋(だった)ので、LEDを全点灯しないというところが気になり回路図を拝見しました。試してはいないのですが、おそらくRTNラインの100kΩが原因です。抵抗値が大きすぎて、かつ、CRDで電圧制限がかかって、店頭に必要な最小電流が流せないと思われます。もし、一灯づつで光るなら、ダイオードK端ですべてをつなげてから抵抗一つにするのではなく、三つのダイオードK端から抵抗100kΩ(を三つにして)それぞれに接続し、抵抗のRTN側をGNDで一つにすれば光るはずです。実際のところ、100kΩで制限していますので、CRDは不要のはずです(LEDの電圧が0Vだったとしても、3.3/100kなのでuAオーダーです)。 CRDは定電流を作るために高い電圧をかけなければならず、電圧制限に使うにしても、LEDとCRDで分圧してしまうので、LEDの点灯条件の足かせになります。

    • mgo-tec mgo-tec より:

      Michael Yanagibaさん

      記事をご覧いただき、ありがとうございます。
      この記事を書いた当初はとりあえず光ればいいや的に作ったので、あまり深く考えていませんでした。
      というか、私はアマチュア素人なので、その勘所がパッと頭に浮かびませんでした。
      全点灯しないのを追求せずに次の工作へ進んでしまいました。

      この記事も随分前に書いていて、すっかり忘れていました。
      CdSセルが手元に1MΩしかなかったので、その分圧で100kΩにした記憶があります。

      私が書いた絵回路図も、今見ると混乱しますね。
      ちょっと疑問なのですが、LEDには
      GPIO(HIGH3.3V) → CRD → LED → GND
      という電流の流れなので、

      >ダイオードK端ですべてをつなげてから抵抗一つにするのではなく

      というところが良く解りません。
      抵抗1つにしてはおらず、そのままGNDにつながっていると思うのですが。。。
      100kΩで分圧しているのは、CdSセルだけだと思うのですが、私が何か勘違いしていますか???

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