今回も前回に引き続き、Arduino化したESP-WROOM-02 ( ESP8266 )を使ってスマホブラウザへ Server-Sent Events でストリーミング表示させます。しかも、Wi-Fi 通信です。
スマホアプリは一切使いません。ブラウザのみです。
プログラム開発はArduino IDEだけです。
今回使用した温度湿度センサーモジュールは秋月電子通商さんで販売していたDHT11モジュールを使いました。
WROOMとはシリアル通信を使います。
ケースに収納されていて、コード付きで使いやすそうだったのでこれを選びました。
スマホブラウザへはServer-Sent Eventsに加え、HTML の Canvas 要素を使ってグラフィック表示させ、データロガー的にスクロールさせました。
動画はこちらをご覧ください。
途中でセンサーにドライヤーを当てて、温度、湿度を変化させてます。
こちらはAndroid Google Chromeの動作です。
こちらはiPad mini の iOS Safari です。
なかなかいい感じにスムースにスクロールしてくれてます。
これがWi-Fi通信だということがまたスゴイですね。
このESP-WROOM-02のポテンシャルはホントにスゴイものがあります。
上部のテキスト表示はかなり小さい文字です。
こんな感じの画面です。(Safari)
カメラの設定が悪く、ちょっとピンボケですみません。
テキストデータもストリーミング表示されてます。
前回と同様にNTP時計も表示させてます。
今回は本格的なデータロガーのように時刻を記録させているわけではありません。
あくまで、グラフの視覚表示だけです。
いつか、時刻もSDカードに記録できればいいなぁと思ってます。
今回実験したブラウザやOSは以下の通りです。
(※Internet Explore は対応していない場合があります)
- iOS 8.4 Safari
- Android 4.4.2 Google Chrome 45.0.2454.94
- Android 4.2.2 Google Chrome 44.0.2403.133
HTML の Canvas 要素がブラウザやOSを選ぶようです。
古いブラウザは残念ながら対応していないものもありますのでお気を付けください。
それでは、今回行った作業を順番に説明します。
1.DHT11ライブラリのダウンロード
まず、GitHubのAdafruitさんのページからArduino IDE用のDHT11専用ライブラリをダウンロードします。
こちら のリンクページを開いてください。
次に、そのページの下図のような表示の場所をクリックして、ZIPファイルをダウンロードしてください。
ダウンロードしたら、解凍してください。
解凍すると
DHT-sensor-library-master
というフォルダができるはずです。
その他、Time.h ライブラリが必要ですが、これは前回の記事を参照してください。
2.ライブラリをArduino IDEにインストール
次に先ほど解凍した DHT-sensor-library-master フォルダをArduino IDEプログラムのライブラリフォルダにコピーします。
Arduino IDE ライブラリフォルダはWindows ならば通常は
C:\Program Files\Arduino\libraries
というパスになります。
64bitパソコンならば
C:\Program Files (x86)\Arduino\libraries
です。
そのlibrariesフォルダに DHT-sensor-library-master フォルダをコピーしてください。
次に、Arduino IDE 1.6.5 を起動してください。
するとDHT-sensor-library-masterライブラリがインストールされているはずです。
このあたりのインストール方法はネットでかなりの情報があります。
3.Arduino 1.6.5 のセッティング
※Arduino IDE は必ずversion 1.6.5 を仕様してください。他のバージョンではまず正常に動作しません。ご注意ください。
WROOM ( ESP8266 )の Arduino化 するためにArduino IDE にボードデータをインストールします。
これは、前回の記事 の3段落をご参照してください。ここでは省略させていただきます。
ネットにも多量に情報があります。
注意するところは、バージョンが1.6.5ということです。
Arduino IDEはそれ以上のバージョンは全く別物ですので気を付けてください。
4.準備するもの
ESP-WROOM-02
Amazonのマイクロテクニカ販売のもの
DHT11温湿度モジュール
秋月電子通商
超小型USBシリアル変換モジュール(3.3V対応)
秋月電子通商
3端子レギュレーター TA48M033F 1個
秋月電子通商で購入すると、0.1uF と 47uFコンデンサーが付いてました
47uF電界コンデンサ 35V 1個
0.1uF積層セラミックコンデンサ 2個
ブレッドボード、ジャンパーワイヤー等
パソコン、USBケーブル等
最新のiOSやAndroidスマホ
ブラウザは Safari または
Google Chrome
ピンヘッダ、ジャンパーピン、タクトスイッチ等
●2mmピンヘッダ
●2mmジャンパーピン
●2.54mmピンヘッダ
●リセットスイッチ用超表面実装タクトスイッチ(2mmピッチに合うもの)2個
Wi-Fi ルーター環境
5.WROOMの接続
前回の記事と同様、ESP-WROOM-02 (ESP8266) はamazonさんのマイクロテクニカさん販売のものを使いました。
(※2015年当初とはタイプが異なっています)
これを下図のようにGPIOを接続します。
マイクロテクニカ製のものでしたら、取扱説明書をダウンロードできると思いますので、それを参照してください。
私は2ミリピンヘッダも別に買ってハンダ付けしました。
フラッシュダウンロードモード(書き込みモード)でジャンパーピンをセットします。
本来ならば、HIGHとかLOWレベルは10kΩ以上の抵抗を入れるべきものですが、マイクロテクニカさんの取扱説明書には一切書いてません。
それでも普通に動作しますので、私の場合はヨシとします。
- GPIO #15 LOW
- GPIO #2 HIGH
- GPIO #0 LOW
- EN HIGH
この回路図は古いままで、私自身、良く分からず作成した当時のものです。
6.配線する
次に下図のように配線します。
注意しなければいけないのは、温度湿度センサーのDHT11モジュールの電源は3.3Vに接続することです。
この回路図は古いままで、私自身、良く分からず作成した当時のものです。
7.Arduino IDE 1.6.5 にスケッチを入力する。
サンプルコードは以下の通りです。
例のごとく見切り発車的なプログラムですのでご容赦ください。
あまり細かいところは詰めていませんが、とりあえず動作すると思います。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
/*Arduino IDE 1.6.5 で動作確認済み *Generic exp8266 board *Time.h library(ver 1.3) */ #include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <Time.h>//最新タイムライブラリーをダウンロードしておく #include <DHT.h>//Adafruitのライブラリ #define DHTPIN 5 // WROOMのGPIO 5番をシリアル入力とする #define DHTTYPE DHT11 // DHT 11 DHT dht(DHTPIN, DHTTYPE); const char* ssid = "xxxx";//ご自分のルーターのSSIDを入力してください const char* password = "xxxx";//ご自分のルーターの boolean SSE_on = false;//Server-Sent Events設定が済んだかどうかのフラグ WiFiServer server(80); //-------NTPサーバー定義---------------- unsigned int localPort = 2390; // local port to listen for UDP packets //IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server IPAddress timeServerIP; // time.nist.gov NTP server address const char* ntpServerName = "time.nist.gov"; const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets WiFiUDP udp; long LastTime = 0; //*****************セットアップ********************** void setup() { Serial.begin(115200);//このシリアル通信はモニター用 delay(10); Serial.println("DHT11 test!"); dht.begin(); // Connect to WiFi network Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); // Start the server server.begin(); Serial.println("Server started"); // Print the IP address Serial.println(WiFi.localIP()); //NTPサーバーでタイムを取得 udp.begin(localPort); WiFi.hostByName(ntpServerName, timeServerIP); setSyncProvider(getNtpTime); delay(3000); } //メインループ*********************************************** void loop() { HTTP_Responce(); delay(1);//これは重要!!これがないと動作しない。 } //**********************Server-Sent Events レスポンス関数************** void HTTP_Responce() { WiFiClient client = server.available();//クライアント生成は各関数内でしか実行できないので注意 while(client){ Serial.println("new client--------------"); String req = client.readStringUntil('\r'); if (req.indexOf("GET / HTTP") != -1){//ブラウザからリクエストを受信したらこの文字列を検知する Serial.print(req); //ブラウザからのリクエストでAccept-Language文字列の行まで読み込む while(req.indexOf("Accept-Language") == -1){ req = client.readStringUntil('\r'); Serial.print(req); } //文字列が多いので2つに分けてる。 String str1; String str2; //-------ここからHTTPレスポンスのHTMLとJavaScriptコード str1 = "HTTP/1.1 200 OK\r\n"; str1 += "Content-Type:text/html\r\n"; str1 += "Connection:close\r\n\r\n";//1行空行が必要 str1 += "<!DOCTYPE html>\r\n"; str1 += "<html>\r\n"; str1 += "<head>\r\n"; str1 += "<meta name=\"viewport\" content=\"initial-scale=0.4\">\r\n"; str1 += "<script type=\"text/javascript\">\r\n"; //STOPボタンを押したら切断する関数 str1 += "function fnc1(){source.close();};\r\n"; str1 += "</script>\r\n"; //InternetExploreの場合 str1 += "<!--[if lt IE 9]>\r\n"; str1 += "<script src=\"http://html5shiv.googlecode.com/svn/trunk/html5.js\"></script>\r\n"; str1 += "<![endif]-->\r\n"; //ここからJavaScript str1 += "<script>\r\n"; str1 += "onload = function() {\r\n"; str1 += "glid00();\r\n"; str1 += "glid01();\r\n"; str1 += "};\r\n"; //グラフのインデックス表示(湿度) str1 += "function glid00() {\r\n"; str1 += "var canvas00 = document.getElementById('canvas00');\r\n"; str1 += "var ctx00 = canvas00.getContext('2d');\r\n"; str1 += "ctx00.save();\r\n";//おまじない str1 += "ctx00.fillStyle = 'rgb(255,255,255)';\r\n"; str1 += "ctx00.textBaseline = \"middle\";\r\n"; str1 += "ctx00.font = \"40px 'Arial'\";\r\n"; str1 += "ctx00.fillText('Humidity',200,20,200);\r\n"; str1 += "ctx00.fillText('100%',0,50,90);\r\n"; str1 += "ctx00.fillText('80%',0,150,90);\r\n"; str1 += "ctx00.fillText('60%',0,250,90);\r\n"; str1 += "ctx00.fillText('40%',0,350,90);\r\n"; str1 += "ctx00.fillText('20%',0,450,90);\r\n"; str1 += "ctx00.fillText('0%',0,550,90);\r\n"; str1 += "ctx00.restore();\r\n";//おまじない str1 += "};\r\n"; //グラフのインデックス表示(温度) str1 += "function glid01() {\r\n"; str1 += "var canvas01 = document.getElementById('canvas01');\r\n"; str1 += "var ctx01 = canvas01.getContext('2d');\r\n"; str1 += "ctx01.save();\r\n";//おまじない str1 += "ctx01.fillStyle = 'rgb(255,255,255)';\r\n"; str1 += "ctx01.textBaseline = \"middle\";\r\n"; str1 += "ctx01.font = \"40px 'Arial'\";\r\n"; str1 += "ctx01.fillText('Temperature',200,20,250);\r\n"; str1 += "ctx01.fillText('50*C',0,50,90);\r\n"; str1 += "ctx01.fillText('40*C',0,150,90);\r\n"; str1 += "ctx01.fillText('30*C',0,250,90);\r\n"; str1 += "ctx01.fillText('20*C',0,350,90);\r\n"; str1 += "ctx01.fillText('10*C',0,450,90);\r\n"; str1 += "ctx01.fillText('0*C',0,550,90);\r\n"; str1 += "ctx01.restore();\r\n";//おまじない str1 += "};\r\n"; //Server-Sent Events の設定 str1 += "var source=new EventSource(\""; str1 += String(WiFi.localIP());//ルーターのローカルIPアドレスを自動取得 str1 += "\");\r\n"; //SSEのテキスト時刻表示 str1 += "source.addEventListener('msg_1',function(event){\r\n"; str1 += "var ms1 = document.getElementById('msgs1');\r\n"; str1 += "ms1.innerHTML = event.data;});\r\n"; str1 += "source.addEventListener('msg_2',function(event){\r\n"; str1 += "var ms2 = document.getElementById('msgs2');\r\n"; str1 += "ms2.innerHTML = '___' + event.data;});\r\n"; str1 += "source.addEventListener('msg_3',function(event){\r\n"; str1 += "var ms3 = document.getElementById('msgs3');\r\n"; str1 += "ms3.innerHTML = 'NTP time Up Date ( ' + event.data + ' )';});\r\n"; //SSEのテキスト湿度表示とCanvas要素をひとくくりのイベントとして発生 str2 += "source.addEventListener('msg_4',function(event){\r\n"; str2 += "var ms4 = document.getElementById('msgs4');\r\n"; str2 += "ms4.innerHTML = 'Humidity=' + event.data + ' %';\r\n"; str2 += "var canvas4 = document.getElementById('canvas4');\r\n"; str2 += "var ctx4 = canvas4.getContext('2d');\r\n"; str2 += "var grad4 = ctx4.createLinearGradient(0,0,0,500);\r\n"; str2 += "ctx4.save();\r\n";//おまじない str2 += "ctx4.drawImage(canvas4,1,0);\r\n"; str2 += "ctx4.fillStyle = 'rgb(0,0,0)';\r\n"; str2 += "ctx4.fillRect(0, 0, 3, 500);\r\n"; // str2 += "ctx4.clearRect(0, 0, 4, 1300);\r\n";//これはなぜか使えなかった!! str2 += "grad4.addColorStop(0,'rgba(255,0,0,1)');\r\n"; str2 += "grad4.addColorStop(0.6,'rgba(255,255,0,0.5)');\r\n"; str2 += "grad4.addColorStop(0.8,'rgba(77,255,77,0.5)');\r\n"; str2 += "grad4.addColorStop(1,'rgba(0,0,255,0.5)');\r\n"; str2 += "ctx4.fillStyle = grad4;\r\n"; str2 += "ctx4.fillRect(0, 500, 3,-event.data*5);\r\n"; str2 += "ctx4.restore();\r\n";//おまじない str2 += "});\r\n"; //SSEのテキスト温度表示とCanvas要素をひとくくりのイベントとして発生 str2 += "source.addEventListener('msg_5',function(event){\r\n"; str2 += "var ms5 = document.getElementById('msgs5');\r\n"; str2 += "ms5.innerHTML = '___' + 'Temperature=' + event.data + ' *C';\r\n"; str2 += "var canvas5 = document.getElementById('canvas5');\r\n"; str2 += "var ctx5 = canvas5.getContext('2d');\r\n"; str2 += "var grad5 = ctx5.createLinearGradient(0,0,0,500);\r\n"; str2 += "ctx5.save();\r\n";//おまじない str2 += "ctx5.drawImage(canvas5,1,0);\r\n"; str2 += "ctx5.fillStyle = 'rgb(0,0,0)';\r\n"; str2 += "ctx5.fillRect(0, 0, 3, 500);\r\n"; str2 += "grad5.addColorStop(0,'rgba(255,0,0,1)');\r\n"; str2 += "grad5.addColorStop(0.6,'rgba(255,255,0,0.5)');\r\n"; str2 += "grad5.addColorStop(0.8,'rgba(77,255,77,0.5)');\r\n"; str2 += "grad5.addColorStop(1,'rgba(0,0,255,0.5)');\r\n"; str2 += "ctx5.fillStyle = grad5;\r\n"; str2 += "ctx5.fillRect(0, 500, 3,-event.data*10);\r\n"; str2 += "ctx5.restore();\r\n";//おまじない str2 += "});\r\n"; str2 += "</script>\r\n"; str2 += "</head>"; //SSEやCanvasはヘッダー内に入れ、ここからはHTML Body str2 += "<body bgcolor='black'>\r\n"; //テキスト表示の設定 str2 += "<FONT size=\"5\" color=\"#9999FF\">\r\n"; str2 += "<span id=\"msgs1\">Event wait 1</span> \r\n"; str2 += "<span id=\"msgs2\">Event wait 2</span>\r\n"; str2 += "<div id=\"msgs3\">Event wait 3</div>\r\n"; str2 += "<span id=\"msgs4\">Event wait 4</span>\r\n"; str2 += "<span id=\"msgs5\">Event wait 5</span>\r\n"; str2 += "</FONT>\r\n"; //Canvas要素のグラフ設定(湿度) str2 += "<div style=\"position: relative;\">\r\n"; str2 += "<canvas id=\"canvas00\" width=\"800\" height=\"600\" style=\"position: absolute; left: 0; top: 100px; z-index: 0;\">\r\n"; str2 += "<p>This browser is a canvas element non-compliant</p>\r\n"; str2 += "</canvas>\r\n"; str2 += "<canvas id=\"canvas4\" width=\"700\" height=\"500\" style=\"border: 1px white solid; position: absolute; left:100px; top: 150px; z-index: 1;\">\r\n"; str2 += "</canvas><br>\r\n"; str2 += "</div>\r\n"; //Canvas要素のグラフ設定(温度) str2 += "<div style=\"position: relative;\">\r\n"; str2 += "<canvas id=\"canvas01\" width=\"800\" height=\"600\" style=\"position: absolute; left: 0; top: 700px; z-index: 0;\">\r\n"; str2 += "</canvas>\r\n"; str2 += "<canvas id=\"canvas5\" width=\"700\" height=\"500\" style=\"border: 1px white solid; position: absolute; left:100px; top: 750px; z-index: 1;\">\r\n"; str2 += "</canvas>\r\n"; str2 += "</div>\r\n"; //Server-Sent Eventをブラウザ側から停止するボタン str2 += "<input type=\"button\" id=\"SSE_stop\" value=\"SSE STOP\" style=\"width:200px; height:50px; font-size:30px;\" onclick=\"fnc1()\">\r\n"; str2 += "</body>\r\n"; str2 += "</html>\r\n"; delay(1000);//1秒待ってレスポンスをブラウザに送信 client.print(str1); client.print(str2); delay(1);//これが重要!これが無いと切断できないかもしれない。 str1 = ""; str2 = ""; SSE_on = true;//Server-Sent Event 設定終了フラグ client.stop(); Serial.println("\nGET HTTP client stop--------------------"); req =""; SSE_Responce(); } req =""; } } //**************Server-Sent Events データ送信関数**************************** void SSE_Responce() {//HTTPレスポンス1度目を送信したら、すぐにブラウザから2回目のGETリクエストが来る while(1){//無限ループ WiFiClient client = server.available();//クライアント生成は各関数内でしか実行できないので注意 while(client){ String req = client.readStringUntil('\r'); if(req.indexOf("GET") != -1){//2回目のGETを検知したらServer-Sent Eventsレスポンス送信 Serial.println("GET in--------------------"); Serial.print(req); while(req.indexOf("Accept-Language") == -1){ req = client.readStringUntil('\r'); Serial.print(req); } if(SSE_on == true){ Serial.println("\nsse responce send--------------------"); String sse_resp; //ストリーム配信をブラウザが認識するためのレスポンス sse_resp = "HTTP/1.1 200 OK\r\n"; sse_resp += "Content-Type:text/event-stream\r\n";//SSE使用時に必ずサーバー側からブラウザへこれを返す sse_resp += "Cache-Control:no-cache\r\n"; sse_resp += "\r\n";//必ずこの空行が必要 client.print(sse_resp); delay(3000);//ここの秒数はもう少し少なくても問題ない Serial.println(sse_resp); String sse_data1; String sse_data2; Serial.println("sse data send--------------------"); String str_h; String str_m; String str_s; String sync_h="?"; String sync_m="?"; String sync_s="?"; LastTime = millis(); while(client){//Event Sourceデータの無限ループストリーム送信 if(hour()<10){//一桁の数値を二桁にする str_h = "0" + String(hour()) ; }else{ str_h = String(hour()); } if(minute()<10){ str_m = "0" + String(minute()) ; }else{ str_m = String(minute()); } if(second()<10){ str_s = "0" + String(second()) ; }else{ str_s = String(second()); } //30秒毎にNTPサーバーから時刻をゲットしてArduinoタイムを修正 if(millis()-LastTime > 30000){ WiFi.hostByName(ntpServerName, timeServerIP); setSyncProvider(getNtpTime); LastTime = millis(); sync_h = str_h; sync_m = str_m; sync_s = str_s; } int h = dht.readHumidity();//センサーから湿度を取得 int t = dht.readTemperature();//センサーから温度を取得(摂氏) // Check if any reads failed and exit early (to try again). if (isnan(h) || isnan(t)) { Serial.println("Failed to read from DHT sensor!"); return; } sse_data1 = "event:msg_1\n";//ブラウザへ送るeventを発生させて改行コードをつける sse_data1 += "data:"; sse_data1 += String(year())+"/"+String(month())+"/"+String(day());//data:の後に送りたいデータをつける sse_data1 += "\n\n";//イベントを発生させるためには必ず改行コード2回連続をつける sse_data1 += "event:msg_2\n"; sse_data1 += "data:"; sse_data1 += str_h+":"+str_m+":"+str_s; sse_data1 += "\n\n"; sse_data1 += "event:msg_3\n"; sse_data1 += "data:"; sse_data1 += sync_h + ":" + sync_m + ":" + sync_s;//NTPサーバから取得した時刻を表示 sse_data1 += "\n\n"; sse_data1 += "event:msg_4\n"; sse_data1 += "data:"; client.print(sse_data1); client.print(h); sse_data2 += "\n\n"; sse_data2 += "event:msg_5\n"; sse_data2 += "data:"; client.print(sse_data2); client.print(t); client.print("\n\n"); sse_data1 = ""; sse_data2 = ""; delay(100); } client.stop(); Serial.println("Client.Stop-----------------"); SSE_on = false; break; } } req = ""; } } } //************NTPサーバータイム取得関数***************************** const int timeZone = 9; // 日本時間 //const int timeZone = 1; // Central European Time //const int timeZone = -5; // Eastern Standard Time (USA) //const int timeZone = -4; // Eastern Daylight Time (USA) //const int timeZone = -8; // Pacific Standard Time (USA) //const int timeZone = -7; // Pacific Daylight Time (USA) time_t getNtpTime() { while (udp.parsePacket() > 0) ; // discard any previously received packets sendNTPpacket(timeServerIP); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { delay(1);//これを入れないと更新できない場合がある。 int size = udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } return 0; // return 0 if unable to get the time } //******************NTPリクエストパケット送信**************************** void sendNTPpacket(IPAddress& address) { // Serial.println("sending NTP packet..."); // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: udp.beginPacket(address, 123); //NTP requests are to port 123 udp.write(packetBuffer, NTP_PACKET_SIZE); udp.endPacket(); }
- 10行目 DHT11のシリアル信号を受信するGPIOピン番号を設定するところです。 Arduino本体ではこの設定でいいのですが、これでWROOMが動くかどうか疑問でした。これはネットで情報が全く無いので、とりあえずやってみたら動きました。
- 15、16行目でご自分のルーターのSSIDとパスワードを入力してください。
- 72行目のdelay(1); は重要で、これが無いとなぜか動きませんでした。 メインループがたったこれだけなんです。
- 93~234行目までがブラウザからWROOMへGET要求されたらレスポンスするHTMLおよびJavaScriptです。 あまりに文字列が多いので、String str1; だけでは処理してくれません。 よって、文字列を2つに分けて送信したら問題なく動作しました。 Server-Sent Events や JavaScript のCanvas要素などは今回は<head>内に記述してます。
Canvas要素の使い方はネットでかなりの情報がありますが、Arduinoで使う場合にはいろいろと工夫しなければなりませんでした。 かなり自己流です。
Canvas要素は独特な使い方があり、基本的にディスプレイの左上が原点の(0,0)です。
それから相対位置や絶対位置などの設定などいろいろとあり、正直言って私もまだ勉強中です。 Canvas要素についてはこの記事内ではとても書ききれませんのでご容赦ください。
スクロールのポイントは以前の記事でもあげましたが、170行目や190行目の
drawImage です。この1文を加えるだけでスクロールしてくれる優れものです。
要するに、現在の画面をキャプチャして次に読み込んだ時にはそれをずらして再生してくれるわけです。 これは有り難い機能ですね。
また、グラデーションの色分け設定はけっこう面倒です。もっと簡単な方法がないものですかねぇ~。 - 216行目からのHTMLはグラフのインデックス表示とCanvasスクロールグラフィックをレイヤーで重ねています。CanvasにHTMLテキストを同時表示できませんでしたので、インデックス表示をCanvasのfillTextで表示させてます。
- 231行目はServer-Sent Events をブラウザ側から停止させるボタン設定です。
サーバーのWROOM側から停止させるのがServer-Sent Eventsの流儀らしいのですが、やはりブラウザ側から停止させたいのでこのようにしました。 - いくつかCanvas要素のおまじない文がありますが、私はこの意味がイマイチ理解しておりません。
- 316、317行目でセンサー値を読み取ってます。
- 325~348行でブラウザへストリーミングデータを無限ループ送信してます。Delay(100);でスクロール速度を落としてます。
以上です。
Server-Sent Events のリクエスト、レスポンスの流れは過去記事を参照してください。いつかまとめたいとは思っていますが、なかなか手が回っておりません・・・。
Arduino UNOではこれだけの文字列処理はメモリーの大幅オーバーで動作できません。
WROOMはArduino MEGAを凌ぐメモリーですので、全然平気みたいです。これまたスゴイですねぇ~・・・。
8.コンパイルおよびWROOMに書き込み
コンパイルに成功するとこういう画面になります。
グローバル変数が65%ですが、何もないプログラムをコンパイルしても54%ですから、わずか11%しか増えてません。スゴイですね・・・。
ちなみに、コンパイルできないときにはこんなエラーが出る時があります。
この場合はUSBケーブルを抜いて挿し直し、再びコンパイルしてみてください。
10.動作させる
USBケーブルを抜き、WROOMのGPIO 0番ピンをHIGHにセットしてFLASHブートモード(読み込み専用)にします。
次にUSBケーブルをPCと接続して、Arduino IDEのシリアルモニタを起動させます。
その次にWROOMのリセットボタンを押します(またはRSTピンをGNDと短絡させる)。
するとこういうメッセージになります。
こうなればルーターとのコネクション完了です。
次にスマートフォンのWi-Fiを有効にして、ルーターと接続できる状態にしておきます。
ブラウザのURL入力欄に
192.168.0.21
などと、シリアルモニターで表示されていたルーターが割り当てたアドレスを入力すると下図のように温度、湿度グラフが表示されると思います。
最上部のテキストはCanvas要素ではなく、JavaScriptのEvent発生テキストです。
上の図が湿度(Humidity)、下の図が温度(Temperature)です。
ドライヤーを当てるとリアルタイムに急激に温度が上がり、湿度が急激に下がっていると思います。
けっこうリアルタイムに追従してくれて、期待通りの表示をしてくれますね。なかなかよいではないですか・・・。
この ESP-WROOM-02 はなかなかスゴイ能力ですね。これだけのプログラムはArduino UNOでは到底不可能です。
というわけで、今回は以上です。
どうでしょうか?
うまく表示されましたでしょうか?
もし、うまくできなければ、お問い合わせフォームやコメントなどでご連絡ください。
まだまだ細かいところを詰めてませんが、過去記事のように度々修正していくつもりです。
今回はここまでにします。
それではまた・・・。
この記事以降、WROOMについてかなり進展しました。
しかも、スマートフォンとWi-Fiリアルタイムストリーミング通信が双方向でできるようになりました!!
以下のリンクをご覧ください。
●双方向ストリーミング通信WebSocket でスマホから Arduino化 WROOM のLEDを調光してみる
●Arduino化 WROOM で WebSocket データ送受信方法
●Arduino化 WROOM で WebSocket ハンドシェイク 確立方法
●スイッチサイエンスさんESPr Developer ( ESP-WROOM-02 開発ボード )の使い方をザッと紹介
更に最近ではこんなこともできるようになりました
Amazon.co.jp 当ブログのおすすめ
コメント
60の手習いでいじり始めました。
大変参考になる投稿で感謝しています。
複数のHTTPリクエストに応えられる様にする修正のヒントを頂けないでしょうか。
西川さん
記事をご覧いただき、ありがとうございます。
複数のHTTPリクエストとは、1台のスマホから1つのESP-WROOM-02 に向かって、ブラウザから別々のGETリクエストを送るということでしょうか?