オブジェの LED カラー を スマホ ブラウザ で WiFi リアルタイム コントロール してみた

ESP8266 ( ESP-WROOM-02 )

7.HTMLヘッダファイルをESP8266 へ SPIFFSアップロードする

EasyWebSocketライブラリの Beta ver 1.48 では、HTMLヘッダファイルを一新しました。

今までは、ESPr Developer ( ESP-WROOM-02, ESP8266 ) のWi-Fi ローカルIPアドレスが変わる度にSPIFFSファイルを書き換えてアップロードしていました。

ローカルIPアドレスが変わってしまう場合というのは、ルーター側でIPアドレスを自動割り当てに設定していて、停電が起きた時や、ルーターの電源を立ち上げ直した時に、ルーター側が自動で再割り当てを行ってしまう為に、アドレスが変わってしまうのです。
その度に書き換えるのがとても面倒だったので、ESP8266側がローカルIPアドレスを自動で読み取るようにしました。
それに伴って、WebSocket通信用のHTMLヘッダを分割して、ESP8266側で取得したIPアドレスを合体(マージ)させてスマホのブラウザへ送信するようにしました。

と、いうことで、その分割ファイルはサンプルスケッチの中にあります。
まず、下図のように「スケッチの例」の Auto_Local_IP_sample を開きます。

次に、「スケッチのフォルダを表示」を開きます。

下図の様にそのフォルダの data フォルダを開きます。

下図の様に、2つのファイルを使います。
HTMhead1.txt
HTMhead2.txt

この2つのファイルは合計で 11kB しかありませんので、Arduino IDE のESP8266ボード設定は
Flash Size: ” 4M ( 1M SPIFFS ) ” 
で良いです。

USBシリアルで ESPr Developer ( ESP-WROOM-02, ESP8266 ) へのSPIFFSアップロード方法は以下のページをご覧ください。

Arduino IDE に ESP8266 SPIFFS ファイルシステムアップローダーをインストールする方法

4M ( 3M SPIFFS ) でアップロードしたい場合は、USBシリアルではアップロードできませんので、その場合はWi-Fi でOTAアップロードということになります。
それは以下のページを参照してください。

4M ( 3M SPIFFS ) をシリアルポートでアップロードできない場合のトラブルシューティング

USBの電流量が問題になる場合がありますので、USBハブはダメです。
必ずパソコン直挿しにしてください。

8.スケッチ(プログラム)の入力

では、スケッチを入力していきます。
ライセンスは、Arduino core for ESP8266 ライブラリは
LGPL ver2.1 です。
ですから、私の自作 EasyWebSocket ライブラリは
LGPL ver2.1 になります。
因みに、Adafruit ライブラリは、
LGPL ver3. です。

では、以下のスケッチをIDEに入力してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/*  
 *  Required EasyWebSocket_SPIFFS library beta ver 1.48
 *  License of EasyWebSocket_SPIFFS libraries is LGPL ver2.1
 *  License of Adafruit NeoPixel libraries is LGPL ver3.
 *  Reference: <http://www.gnu.org/licenses/>
 */
#include <Adafruit_NeoPixel.h>
#include <EasyWebSocket.h> //※beta ver 1.48が必要です
#include <ESP8266WiFi.h>
#include <Hash.h>

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

const char* HTM_head_file1 = "/HTMhead1.txt"; //HTMLヘッダファイル1
const char* HTM_head_file2 = "/HTMhead2.txt"; //HTMLヘッダファイル2

//シリアル通信GPIOピン番号と、LED素子の最大数定義
enum { PIN = 5, MAXPIXELS = 12 };

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(MAXPIXELS, PIN, NEO_GRB + NEO_KHZ800);
EasyWebSocket ews; //EasyWebSocketライブラリ クラス名定義

IPAddress LIP; //ESP-WROOM-02(ESP8266) ローカルIPアドレス自動取得用

String html_str1 = ""; //スマホブラウザに表示させるHTMLタグ文字列を定義
String html_str2 = "";
String html_str3 = "";
String html_str4 = "";
String html_str5 = "";
String html_str6 = "";
String html_str7 = "";

int PingSendTime = 30000; //スマホとWebSocket接続されているか確認するためのPing送信する間隔
String ret_str; //スマホから送信されてくる文字列を定義

//NeoPixel LED三原色定義および初期化
uint8_t red[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t green[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t blue[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

//現在表示されているNeoPixelのRGBを定義および初期化
uint8_t red_now[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t green_now[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t blue_now[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

//隣のLEDとの明るさの差をRGBそれぞれについて定義および初期化
float red_dif[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
float green_dif[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
float blue_dif[MAXPIXELS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

int cnt = 0; //256段階でLEDをフェードさせるためのカウント

//LED点灯を回転させるための定義
boolean Rot_ON = false;
uint32_t RotationTime;
uint8_t RotationSpeed = 0;

//********************セットアップ***********************************
void setup() {
  //ブラウザへ送信するHTML body要素部分を作る
  //※Canvas_Sliderは一つのString変数に2つ以内
  html_str1 = "<body style='background:#000; color:#fff;'>\r\n";
  html_str1 += "<font size=3>\r\n";
  html_str1 += "ESP-WROOM-02(ESP8266)\r\n";
  html_str1 += "<br>\r\n";
  html_str1 += "EasyWebSocket 1.48 Sample\r\n";
  html_str1 += "</font><br>\r\n";
  html_str1 += ews.EWS_BrowserSendRate();
  html_str1 += "<br>\r\n";
  html_str1 += ews.EWS_Status_Text2("WebSocket Status","#555555", 20,"#FF00FF");
  html_str1 += "<br><br>\r\n";
  html_str2 += ews.EWS_On_Momentary_Button("OFF", "ALL-Clear", 100,25,15,"#FFFFFF","#FF0000");
  html_str2 += "<br><br>\r\n";
  html_str2 += ews.EWS_On_Momentary_Button("RotON", "Rotatin ON", 150,25,15,"#FFFFFF","#005500");
  html_str2 += "<br><br>\r\n";
  html_str2 += ews.EWS_On_Momentary_Button("rotOFF", "Rotation OFF", 150,25,15,"#FFFFFF","#000055");
  html_str2 += "<br><br>\r\n";
  html_str3 += "Brightness.\r\n";
  html_str3 += ews.EWS_Canvas_Slider_T("Brightness",255,40,"#777777","#ffff00");
  html_str3 += "<br><br>\r\n";
  html_str3 += "Speed.\r\n";
  html_str3 += ews.EWS_Canvas_Slider_T("Speed",200,40,"#777777","#7777ff");
  html_str3 += "<br><br>\r\n";
  html_str4 += ews.Color_Picker(  0,  75, "#000000", "LED08");
  html_str4 += ews.Color_Picker(  0, 130, "#000000", "LED09");
  html_str4 += ews.Color_Picker( 30, 160, "#000000", "LED10");
  html_str4 += ews.Color_Picker( 60, 190, "#000000", "LED11");
  html_str4 += ews.Color_Picker( 90, 190, "#000000", "LED00");
  html_str4 += ews.Color_Picker(120, 160, "#000000", "LED01");
  html_str5 += ews.Color_Picker(150, 130, "#000000", "LED02");
  html_str5 += ews.Color_Picker(150,  75, "#000000", "LED03");
  html_str5 += ews.Color_Picker(120,  40, "#000000", "LED04");
  html_str5 += ews.Color_Picker( 90,  10, "#000000", "LED05");
  html_str5 += ews.Color_Picker( 60,  10, "#000000", "LED06");
  html_str5 += ews.Color_Picker( 30,  40, "#000000", "LED07");
  html_str6 += "<br><br><br><br><br><br><br><br><br><br><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", "#ccc", 150, 40, "red", 17);
  html_str7 += ews.EWS_Window_ReLoad_Button2("ReLoad", "#ccc", 150, 40, "blue", 17);
  html_str7 += "</body>\r\n</html>\r\n";

  ews.AP_Connect(ssid, password); //ルーター(アクセスポイント)と接続

  LIP = WiFi.localIP(); //ESP8266のローカルIPアドレスを自動取得
  
  pixels.begin(); // This initializes the NeoPixel library.
  pixels.setBrightness(255); //Max 255

  RotationTime = millis();
}
//********************メインループ***********************************
void loop() {
  ews.EWS_Dev_AutoLIP_HandShake_str(HTM_head_file1, LIP, HTM_head_file2, html_str1, html_str2, html_str3, html_str4, html_str5, html_str6, html_str7);
  
  int i;

  if(ret_str != "_close"){   
    ret_str = ews.EWS_ESP8266CharReceive(PingSendTime); //EasyWebSocketライブラリ。ブラウザから文字列受信
    
    if(ret_str != "\0"){
      Serial.println(ret_str);
      if(ret_str != "Ping"){
        uint8_t ws_data = (ret_str[0]-0x30)*100 + (ret_str[1]-0x30)*10 + (ret_str[2]-0x30);
        switch(ret_str[4]){
          case 'B':
            pixels.setBrightness(ws_data);
            for(i=1; i<MAXPIXELS; i++){
              pixels.setPixelColor(i, pixels.Color(red_now[i], green_now[i], blue_now[i])); //NeoPixelライブラリ
            }
            pixels.show();
            break;
          case 'S':
            RotationSpeed = round(float((200-ws_data)/20));
            break;
          case 'O':
            for(i=0; i<MAXPIXELS; i++){
              red[i] = 0; green[i] = 0; blue[i] = 0;
              red_now[i] = 0; green_now[i] = 0; blue_now[i] = 0;
              red_dif[i] = 0; green_dif[i] = 0; blue_dif[i] = 0;
              pixels.setPixelColor(i, pixels.Color(red[i], green[i], blue[i])); //NeoPixelライブラリ
            }
            pixels.show();
            WebSocket_Browser_ColButton_Value_Send("LED", MAXPIXELS, red, green, blue);
            break;
          case 'R':
            Rot_ON = true;
            Serial.print("Rot_ON="); Serial.println(Rot_ON);
            RotationTime = millis();
            break;
          case 'r':
            Rot_ON = false;
            Serial.print("Rot_ON="); Serial.println(Rot_ON);
            WebSocket_Browser_ColButton_Value_Send("LED", MAXPIXELS, red_now, green_now, blue_now);
            break;
          case 'L':
            uint16_t led_num = (ret_str[7]-0x30)*10 + (ret_str[8]-0x30);
            red[led_num] = (ret_str[10]-0x30)*100 + (ret_str[11]-0x30)*10 + (ret_str[12]-0x30);
            green[led_num] = (ret_str[14]-0x30)*100 + (ret_str[15]-0x30)*10 + (ret_str[16]-0x30);
            blue[led_num] = (ret_str[18]-0x30)*100 + (ret_str[19]-0x30)*10 + (ret_str[20]-0x30);

            red_dif[0] = float(red[0] - red[11])/256;
            green_dif[0] = float(green[0] - green[11])/256;
            blue_dif[0] = float(blue[0] - blue[11])/256;
            
            for(i=1; i<MAXPIXELS; i++){
              red_dif[i] = float(red[i] - red[i-1])/256;
              green_dif[i] = float(green[i] - green[i-1])/256;
              blue_dif[i] = float(blue[i] - blue[i-1])/256;
            }

            red_now[led_num] = red[led_num];
            green_now[led_num] = green[led_num];
            blue_now[led_num] = blue[led_num];
            
            cnt = 0;

            pixels.setPixelColor(led_num, pixels.Color(red[led_num], green[led_num], blue[led_num])); //NeoPixelライブラリ
            pixels.show(); //NeoPixelライブラリ
            
            Serial.print("led_num= "); Serial.println(led_num);
            Serial.print("red = "); Serial.println(red[led_num]);
            Serial.print("green = "); Serial.println(green[led_num]);
            Serial.print("blue = "); Serial.println(blue[led_num]);
            Serial.print("red_dif[0]="); Serial.println(red_dif[0]);
            break;
        }
        ret_str = "";
      }
    }     
  }else if(ret_str == "_close"){
    ret_str = "";
  }
  
  if(Rot_ON){
    if(millis() - RotationTime > RotationSpeed){
      int16_t rr, gg, bb;
      for(i=0; i<MAXPIXELS; i++){
        rr = red[i] - round(red_dif[i] * cnt);
        if(rr > 255){
          red_now[i] = 255;
        }else if(rr < 0){
          red_now[i] = 0;
        }else{
          red_now[i] = rr;
        }
        gg = green[i] - round(green_dif[i] * cnt);
        if(gg > 255){
          green_now[i] = 255;
        }else if(gg < 0){
          green_now[i] = 0;
        }else{
          green_now[i] = gg;
        }
        bb = blue[i] - round(blue_dif[i] * cnt);
        if(bb > 255){
          blue_now[i] = 255;
        }else if(bb < 0){
          blue_now[i] = 0;
        }else{
          blue_now[i] = bb;
        }
        pixels.setPixelColor(i, pixels.Color(red_now[i], green_now[i], blue_now[i])); //NeoPixelライブラリ
      }
      pixels.show(); //NeoPixelライブラリ

      cnt++;
      
      if(cnt == 256){
        uint8_t dummy_red[MAXPIXELS], dummy_green[MAXPIXELS], dummy_blue[MAXPIXELS];
        for(i=0; i<MAXPIXELS; i++){
          dummy_red[i] = red[i];
          dummy_green[i] = green[i];
          dummy_blue[i] = blue[i];
        }
        red[0] = dummy_red[11];
        green[0] = dummy_green[11];
        blue[0] = dummy_blue[11];
        for(i=1; i<MAXPIXELS; i++){
          red[i] = dummy_red[i-1];
          green[i] = dummy_green[i-1];
          blue[i] = dummy_blue[i-1];
        }
        red_dif[0] = float(red[0] - red[11])/256;
        green_dif[0] = float(green[0] - green[11])/256;
        blue_dif[0] = float(blue[0] - blue[11])/256;
        for(i=1; i<MAXPIXELS; i++){
          red_dif[i] = float(red[i] - red[i-1])/256;
          green_dif[i] = float(green[i] - green[i-1])/256;
          blue_dif[i] = float(blue[i] - blue[i-1])/256;
        }
        cnt = 0;
      }
      RotationTime = millis();
    }
  }
  yield();
}
//***************ブラウザへのカラーボタンValue値送信関数*******************
void WebSocket_Browser_ColButton_Value_Send(String ID, int16_t Max_num, uint8_t* rv, uint8_t* gv, uint8_t* bv){
  char rc[3], gc[3], bc[3], id_num[3];
  String c_code, ID_str;
  
  for(int i=0; i<Max_num; i++){
    //数値を16進数文字列に変換
    sprintf(rc, "%02x", rv[i]);
    sprintf(gc, "%02x", gv[i]);
    sprintf(bc, "%02x", bv[i]);
    //id番号は10進数文字列に変換
    sprintf(id_num, "%02d", i);
    
    c_code = '#' + String(rc) + String(gc) + String(bc); //HTMLカラーコード文字列を作る
    ID_str = ID + String(id_num);
    //ブラウザへWebSocket通信で文字列を送信
    ews.EWS_ESP8266_Str_SEND(c_code, ID_str); //EasyWebSocketライブラリ。ブラウザへ文字列送信
  }
}

【解説】

EasyWebSocket は過去の記事で解説しておりますので、所々説明を省かせていただきます。

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

●15-16行:
SPIFFSでアップロードしたHTMLヘッダ分割ファイルを定義しています。
必ず先頭にスラッシュを置くことです。

●19行:
NeoPixelとシリアル通信するピン番号と、NeoPixel LED素子の数を定義します。

●21行:
Adafruit NeoPixel ライブラリのクラス定義

●24行:
ESP8266ライブラリでローカルIPアドレスを自動取得したものを格納

●38-45行:
NeoPixel のLED の3原色をそれぞれ、256段階の数値で格納しておく変数を定義。
12個の配列にしておきます。
グローバル変数領域にしておくことで、ローテーションをストップしたりしてもメモリから呼び出すことができます。

●48-50行:
隣のLEDの明るさの差を格納しておきますが、float型にしておきます。
なぜかというと、フェードイン、フェードアウトするときに256段階にしたいので、256で割って、四捨五入した段階ずつフェードインしたいのです。
そのため、後述するround関数を使うので、それにはfloat型でないと正常に動作しないという理由があるからです。

●63-102行:
115行目のEasyWebSocketライブラリ関数でスマホブラウザへ送信するHTMLのbody要素内をString変数に格納しておきます。
Canvas_Slider 関数は吐き出す文字列が多い為、一つのString変数に2つまでしか入れることができないことに注意してください。
Canvas_Slider はマウスでは操作できません。
マウスで操作したい場合は
EWS_Mouse_Slider_T などの関数を使用してください。

●85-96行:
EasyWebSocket で今回新たに追加した関数です。
HTML の Color Picker 文字列を吐き出します。
最初に述べた、

<input type="color" value="#FF1234" >

というような文字列を吐き出します。

Color_Picker(  相対座標X,  相対座標Y, “カラーコード”, “ID文字列”);

相対座標は、カラーピッカーを置いた行からの座標になります。画面の左上からではありません。
座標で決定するので、テキストにダブって表示される場合があります。
テキストとうまく合わせて、97行のように適度に改行を入れてください。
ID文字列とスマホブラウザのカラーピッカーボタンのIDと連動するようになってます。
261-278行の関数でスマホへ送信されたカラーピッカーのValue値が更新されて色が変わるようになってます。
LED素子の位置と、スマホブラウザの表示位置が異なるために、番号順とはいきませんでした。
これは各自の環境に合わせて調節してみてください。
iOSの場合は、後述しているようにカラーピッカーの表示が大きいので、別途位置調節してください。

●106行:
これは、ESP8266ボードライブラリで、ESPr Developer ( ESP-WROOM-02, ESP8266 ) のローカルIPアドレスを自動取得しています。

●115行:
EasyWebSocket_SPIFFSライブラリで1.48から追加したものです。
ブラウザからGETリクエストが発生したら、HTMLヘッダ分割ファイルと、ローカルIPアドレスとbody要素をマージ(合体)して、スマホのブラウザへ送信して、WebSocket通信を確立(ハンドシェイク)させています。

●120行:
スマホブラウザからWebSocket通信で送られてきたテキストデータを受信して、ret_strへ格納します。
この関数はbeta ver 1.48 でブラウザからのClosing メッセージを表示できるようにしてます。

●122-194行:
スマホから明るさ調節スライダーを操作すると
109|Brightness;
という感じでテキストデータが送られてきます。
最初の3文字は0-255段階の数値で、その文字列を数値に変えて setBrightness 関数に代入してます。
4バイト目(5文字目)の ‘B’ という文字をswich関数で分類してます。
ローテーションスピードや、ストップ、オンオフボタンも同じ要領で分類してます。
カラーピッカーは
100|LED09,000,255,255;
のような文字列で送られてきます。
4バイト目の ‘L’ という文字でLED のカラーピッカーと判別して、その後の2桁の数値がLED番号、その後の3桁の数値がそれぞれRed, Green, Blue という並びです。
Char型文字列を数値化するには、関数を使うよりも、それぞれの桁から0x30を引いた方が計算が速いと思います。
163-171行では、隣のLEDの明るさとの差を取り、256段階でフェードイン、アウトさせたい為に、256で割る必要があります。
少数以下は四捨五入する round 関数を使いますが、その引数はfloat型にしないと正しく計算されませんのでご注意ください。
これは、Twitter のまりすさんから教わりました。
まりすさん、ありがとうございます。m(_ _)m

●152-156行:
Rotation OFF ボタンが押されたら、261-278行の関数を呼び出して、今点灯しているLED のRed値、Green値、Blue値をスマホブラウザへ送信します。

●196-257行:
ローテーションONボタンが押されたら、256段階で隣のLEDとフェードインアウトしていきます。
cnt が256に達したら、LED の色を隣にコピーさせてます。

●261-278行:
今回のプログラムの目玉は、私的にはここがポイントですね。
現在点灯しているNeoPixel LED のRed値、Green値、Blue値をHTML形式のカラーコード文字列に変換してから、LED番号もマージしてスマホに送ります。
すると、HTMLヘッダのJavaScript で分類して、カラーピッカーのvalue値が変更され、ボタンの色が変わります。

コメント

  1. john c mclean より:

    Hi,

    Do you do custom work?

    Really like the yahoo news wroom oled

    Do you sell them? Do you have them in English.

    I need about 100 boards with color OLED

    Thank you

    John

    • mgo-tec mgo-tec より:

      Thank you very much for your very grateful comment.

      Unfortunately, I do not sell.
      About Yahoo news OLED, English version is also under consideration.
      If it can be done, I will inform you in this comment section.
      Code will be made public.
      Please wait for a while, I think that it will take some time.

  2. Kim hyung bae より:

    “オブジェの LED カラー を スマホ ブラウザ で WiFi リアルタイム コントロール してみた” これはSoftAP Modeでは動かないでしょうか?

    • mgo-tec mgo-tec より:

      Kim hyung bae さん

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

      このプログラムコードは随分昔に作ったもので、今ちゃんと動くかどうか怪しいです。
      プログラムコードを今見返すと、当時は未熟で、お恥ずかしい限りです。

      さて、SoftAP モードですが、

      ews.AP_Connect(ssid, password);

      というところを、以下に書き換えてみて下さい。

      ews.SoftAP_setup(ssid, password);

      ssid やパスワードは9文字以上に設定すれば良かったかと思います。

      ただ、softAPモードは速度が遅く、到達距離も短いです。
      以上、試してみて下さい。
      動くかどうか分かりませんけど。

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