有機EL ( OLED ) SSD1331 を WebSocket でリアルタイム制御する
では、前回の記事と今回の WebSocket を合体させて、OLED ディスプレイに表示させた文字スクロールをリアルタイムコントロールしてみたいと思います。
サンプルスケッチは以下の通りです。
お試し実験なので、ディスプレイにノイズが出たりしますが、ご容赦ください。
解説は省略させていただきます。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/* The MIT License (MIT) * Copyright (c) 2018 Mgo-tec */ #include <WiFi.h> #include <WiFiMulti.h> #include "ESP32_SPIFFS_EasyWebSocket.h" //beta ver 1.60 #include "ESP32_SSD1331.h" //Beta ver 1.71 #include "ESP32_SPIFFS_ShinonomeFNT.h" //Beta ver 1.3 #include "ESP32_SPIFFS_UTF8toSJIS.h" //Beta ver 1.2 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 char* UTF8SJIS_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく const char* Shino_Zen_Font_file = "/font/shnmk16.bdf"; //全角フォントファイル名を定義 const char* Shino_Half_Font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義 const uint8_t SCLK_OLED = 14; //SCLK const uint8_t MOSI_OLED = 13; //MOSI (Master Output Slave Input) const uint8_t MISO_OLED = 12; //これは実際は使っていない。MISO (Master Input Slave Output) const uint8_t DC_OLED = 21; //OLED DC(Data/Command) const uint8_t RST_OLED = 4; //OLED Reset const uint8_t CS1_OLED = 15; //CS (Chip Select ピン) ESP32_SSD1331 ssd1331(SCLK_OLED, MISO_OLED, MOSI_OLED, CS1_OLED, DC_OLED, RST_OLED); ESP32_SPIFFS_ShinonomeFNT SFR; ESP32_SPIFFS_EasyWebSocket ews; WiFiMulti wifiMulti; IPAddress LIP; //ローカルIPアドレス自動取得用 String ret_str; //ブラウザから送られてくる文字列格納用 String txt = "text send?"; //ブラウザから受信した文字列を ESP32から再送信する文字列 int PingSendTime = 10000; //ESP32からブラウザへPing送信する間隔(ms) bool get_http_req_status = false; //ブラウザからGETリクエストがあったかどうかの判定変数 long ESP32_send_LastTime; int ESP32_send_Rate = 300; byte cnt = 0; #define ledPin1 5 #define ledPin2 17 #define ledPin3 16 //-------------------------- enum { MaxTxtByte = 200 }; uint8_t sj_txt[MaxTxtByte] = {0}; //Shift_JISコード格納 uint8_t font_buf[MaxTxtByte][16] = {0}; uint16_t sj_length = {0}; //Shift_JISコードの長さ uint16_t sj_cnt = {0}; //Shift_JISコード半角文字数カウント uint8_t Zen_or_Han = {0}; //全角半角判断数値 uint32_t SclTime1 = 0; uint32_t Speed = 10; uint8_t Direction = 0; uint8_t H_Size = 1, V_Size = 1; uint8_t red = 7, green = 7, blue = 3; //256color, Max red=7, green=7, blue=3 boolean scl_Reset = false; boolean scl_stop = false; boolean Reverse = false; //*************セットアップ************************* 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 SD card..."); 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 ssd1331.SSD1331_Init(); ssd1331.Display_Clear(0, 0, 95, 63); //3つのSPIFFSファイルを同時オープン SFR.SPIFFS_Shinonome_Init3F(UTF8SJIS_file, Shino_Half_Font_file, Shino_Zen_Font_file); //ライブラリ初期化。2ファイル同時に開く Serial.println(); txt = "ESP32 SSD1331 サイズアップ日本語文字縦横スクロール、WebSocket制御♪ "; sj_length = SFR.SjisShinonomeFNTread_ALL(txt, sj_txt, font_buf); Zen_or_Han = SFR.Sjis_Zen_or_Han(sj_txt[0], sj_txt[1]); TaskHandle_t th; //ESP32 マルチタスク ハンドル定義 xTaskCreatePinnedToCore(Task1, "Task1", 4096, NULL, 5, &th, 0); //マルチタスク core 0 実行 SclTime1 = millis(); 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); 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 'S': Speed = 20-floor((float)(0.1 * (float)ws_data)); Serial.println(Speed); 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 'L': scl_Reset = true; Direction = 0; break; case 'U': scl_Reset = true; Direction = 1; break; case 'D': scl_Reset = true; Direction = 2; break; case 'H': scl_Reset = true; H_Size = ret_str[5] - 0x30; break; case 'V': scl_Reset = true; V_Size = ret_str[5] - 0x30; break; case '#': if(ret_str[7] == 'a'){ scl_stop = false; }else if(ret_str[7] == 'o'){ scl_stop = true; } break; case '~': if(ret_str[5] == 'n'){ Reverse = false; }else if(ret_str[5] == 'r'){ Reverse = true; } break; case 'C': red = ret_str[5] - 0x30; green = ret_str[6] - 0x30; blue = ret_str[7] - 0x30; break; } }else if(ret_str[0] == 't'){ txt = ret_str.substring(ret_str.indexOf('|')+1, ret_str.length()-1); sj_length = SFR.SjisShinonomeFNTread_ALL(txt+" ", sj_txt, font_buf); Serial.println(txt); } } } }else if(ret_str == "_close"){ ESP32_send_LastTime = millis(); ret_str = ""; } } //************* マルチタスク **************************************** void Task1(void *pvParameters){ uint8_t X0 = 0, X1 = 95, Y0 = 0, Y1 = 63; //ディスプレイ表示座標位置 while(1){ if(!scl_stop && ((millis() - SclTime1) > Speed)){ //スクロールインターバル 7ms if(scl_Reset == true){ Scrolle_Reset(); } if(Direction == 0){ if(ssd1331.HVsizeUp_Scroller_8x16_RtoL(&scl_Reset, Reverse, H_Size, V_Size, X0, X1, Y0, 0, Zen_or_Han, font_buf+sj_cnt, red, green, blue)){ Zen_or_Han = SFR.Sjis_Zen_or_Han_inc(sj_txt, sj_length, &sj_cnt); } }else if(Direction == 1){ if(ssd1331.HVsizeUp_Vscroller_16x16(&scl_Reset, Reverse, H_Size, V_Size, 0, X0, Y0, Y1, 0, Zen_or_Han, font_buf+sj_cnt, red, green, blue)){ Zen_or_Han = SFR.Sjis_Zen_or_Han_inc(sj_txt, sj_length, &sj_cnt); } }else if(Direction == 2){ if(ssd1331.HVsizeUp_Vscroller_16x16(&scl_Reset, Reverse, H_Size, V_Size, 1, X0, Y0, Y1, 0, Zen_or_Han, font_buf+sj_cnt, red, green, blue)){ Zen_or_Han = SFR.Sjis_Zen_or_Han_inc(sj_txt, sj_length, &sj_cnt); } } SclTime1 = millis(); } delay(1); //マルチタスクwhileループでは必ず必要 } } //************************************************************** void Scrolle_Reset(){ sj_cnt = 0; Zen_or_Han = SFR.Sjis_Zen_or_Han(sj_txt[0], sj_txt[1]); ssd1331.Display_Clear(0, 0, 95, 63); } //************************************************************** 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; //※WebSocketへのテキスト送信は110 byte 程度なので、全角35文字程度に抑えること switch(cnt){ case 0: str = "ESP32"; 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.53 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.51","送信"); html_str2 += "<br><br>\r\n"; html_str2 += "Direction \r\n"; html_str2 += ews.EWS_On_Momentary_Button("Light", "←", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("Up", "↑", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("Down", "↓", 80,25,15,"#FFFFFF","#555555"); html_str2 += "<br><br>\r\n"; html_str2 += ews.EWS_On_Momentary_Button("H1", "H_Size1", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("H2", "H_Size2", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("H3", "H_Size3", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("H4", "H_Size4", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("H5", "H_Size5", 80,25,15,"#000000","#AAAAAA"); html_str2 += ews.EWS_On_Momentary_Button("H6", "H_Size6", 80,25,15,"#000000","#AAAAAA"); html_str2 += "<br><br>\r\n"; html_str2 += ews.EWS_On_Momentary_Button("V1", "V_Size1", 80,25,15,"#FFFFFF","#555555"); html_str2 += ews.EWS_On_Momentary_Button("V2", "V_Size2", 80,25,15,"#FFFFFF","#555555"); html_str2 += ews.EWS_On_Momentary_Button("V3", "V_Size3", 80,25,15,"#FFFFFF","#555555"); html_str2 += ews.EWS_On_Momentary_Button("V4", "v_Size4", 80,25,15,"#FFFFFF","#555555"); html_str2 += "<br><br>\r\n"; html_str3 += ews.EWS_On_Momentary_Button("C700", "Red", 80,25,15,"#FFFFFF","#FF0000"); html_str3 += ews.EWS_On_Momentary_Button("C070", "Green", 80,25,15,"#000000","#00FF00"); html_str3 += ews.EWS_On_Momentary_Button("C003", "Blue", 80,25,15,"#FFFFFF","#0000FF"); html_str3 += ews.EWS_On_Momentary_Button("C073", "Cyan", 80,25,15,"#000000","#00FFFF"); html_str3 += ews.EWS_On_Momentary_Button("C703", "Magenta", 80,25,15,"#FFFFFF","#FF00FF"); html_str3 += ews.EWS_On_Momentary_Button("C770", "Yellow", 80,25,15,"#000000","#FFFF00"); html_str3 += ews.EWS_On_Momentary_Button("C773", "White", 80,25,15,"#000000","#FFFFFF"); html_str3 += "<br><br>\r\n"; html_str3 += ews.EWS_On_Momentary_Button("~normal", "Normal", 100,35,15,"#000000","#AAAAAA"); html_str3 += ews.EWS_On_Momentary_Button("~reverse", "Reverse", 100,35,15,"#FFFFFF","#000000"); html_str3 += "<br><br>\r\n"; html_str3 += ews.EWS_On_Momentary_Button("#stop", "STOP", 130,50,15,"#FF0000","#AAAAAA"); html_str3 += ews.EWS_On_Momentary_Button("#start", "START", 130,50,15,"#005500","#AAAAAA"); html_str3 += "<br><br>\r\n"; html_str4 += "<br>LED SPEED... Dim\r\n"; html_str4 += ews.EWS_Canvas_Slider_T("SPEED",200,40,"#777777","#ffffff"); //CanvasスライダーはString文字列に2つまでしか入らない html_str4 += "<br><br>\r\n"; html_str5 += "<br>LED BLUE Dim\r\n"; html_str5 += ews.EWS_Canvas_Slider_T("BLUE",200,40,"#777777","#0000ff"); //CanvasスライダーはString文字列に2つまでしか入らない html_str5 += "<br>LED GREEN Dim\r\n"; html_str5 += ews.EWS_Canvas_Slider_T("GREEN",200,40,"#777777","#00ff00"); //CanvasスライダーはString文字列に2つまでしか入らない html_str6 += "<br>LED RED..... Dim\r\n"; html_str6 += ews.EWS_Canvas_Slider_T("RED",200,40,"#777777","#ff0000"); //CanvasスライダーはString文字列に2つまでしか入らない html_str6 += "<br>LED RGB..... Dim\r\n"; html_str6 += 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); } }
では、これをコンパイル書き込み実行させてみてください。
冒頭の動画のように表示されればOKです。
まとめ
いかがでしたでしょうか。
うまく動きましたでしょうか?
サイズアップした文字スクロールで、ディスプレイに変なノイズが出ますが、これの原因は不明です。
解る方がいらっしゃったら教えてください。
何分、お試し実験なのでご容赦ください。
ESP8266 では SDカードと SPIFFS の WebSocket 両対応でしたが、ESP32 はようやく SPIFFS対応したっていう感じです。
思えば、去年にそれを作って欲しいという要望があってから、長らく時間がかかってしまいました。
お待たせして申し訳ございません。
ただ、今回進歩したのは、ハンドシェイクで使用する SHA ファイルを不要にして、MbedTLS でハンドシェイクできたことです。
これを応用すれば、WebSocket の SSL 化も可能になり、Google Cloud Platform や Amazon IoT とコラボレーションできそうな気がします。
今年はそれが課題ですね。
ということで、今回はここまでです。
ではまた・・・。
コメント
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));
動作確認テストのみなら必要ないかも
いつも貴重なブログでに感謝してます、有難う御座いました。
太田さん
こちらこそ、いつもブログご覧いただき、ありがとうございます。
ちゃんと動いてくれてホッとしました。
家電の制御に使っていただけるとは、感謝感謝です。
固定IP で使ってらっしゃるんですね。
やっぱり、アクセスポイントとのコネクションはユーザーが自由に設定出来た方が良いですね。
もう ESP32 を使ってしまうと、私は ESP8266 には戻れなくなってしまいました。
ESP32 の方がマルチタスクで動作できて、CPU も高速なので、WebSocket通信も確実に速いし、安定してますね。
今年は WebSocket をいろいろ発展させていきたいと思っております。
今後とも当ブログをよろしくお願いいたします。
m(_ _)m
mgo-tec 様
いつも大変参考にさせていただいています。
ESP32版 SPIFFS対応のwebsocket ライブラリの公開
大変ありがたく思います。
リアルタイム調光もうまく使うことができました。
ただSPIFFS版ではcolor_pickerが実装されていませんが、
SPIFFSではなにか技術的に使えなかったのでしょうか?
機能的にはRGBのスライダーで調光できるので問題ないですが、
ちょっと欲がでてしまいました。
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 )電光掲示板をスマホでリアルタイムコントロールしてみた
mgo-tec様
昨日の今日で早速対応していただけるとは、感謝感激です。
たしかにSDカードの方が早いのですが、SPIFFSはESP32単体で
テストできるので、テスト段階では重宝するので使っています。
ありがとうございました。
初めまして!最近arduinoを触りだしていて色々と参考にしています。少しだけ質問があり、書かせていただきます。
void loop 内のif(ret_str != “_close”){の記述なんですが、”_close”の送っている場所などは何処に記述がありますか?
もし、何か送っている場所が何処に書いているのか、また何故”_close”なのか教えてくだされば幸いです。
arduinoで色々と出来るのが楽しいです。これからも頑張って下さい!
インカマスゴリラさん
記事ご覧いただき、ありがとうございます。
この記事はもう3年近く前のもので、今自分のこのコードを見返すと、とっても恥ずかしいほどの素人プログラミングだったんだと落胆しております。
とにかく、私は独学でプログラミングしているので、あまり参考にしない方が良いと思います。
さて、_close のことですが、これは私の自作ライブラリESP32_SPIFFS_EasyWebSocket の ESP32_SD_EasyWebSocket.cpp というファイルに記述していますので参照してみてください。
EWS_ESP32CharReceiveという関数のところです。
また、この記事のソースコードでは95行目のところで、スマホからクローズコマンドが送られて来たら、EWS_ESP32CharReceive関数で”_close”という文字列を返します。
とにかくこの記事のコードやライブラリはかなり未熟な時に書いているので、多々無駄があります。
それに命名規則やコーディング規約もメチャメチャなのでご容赦ください。
それに、その当時は動いたけど、今のArduino core for the ESP32では動かないことも有り得ますのでご了承いただければと思います。