ESP8266の冒険は続く(画像付きのサイトにする)
お正月というにはもう日が経ちすぎてしまいましたが、遅ればせながらみなさま明けましておめでとうございます。本年最初のブログ記事を掲載いたします。内容は昨年暮れからのESP8266まわりのお話の続きです。やりだすと止まらない性質(たち)で、すっかりこのWiFiモジュールにはまっています。
少し脱線気味なのですがArduino IDEで、とうとうページに画像の出るWebサーバーまでESP8266で作ってしまいました。ESP8266は結構大きなフラッシュメモリを持っていますので、これをファイル替わりに使うと、ちょっとしたJPEGデータを入れたページを作ることが出来ます。
しかしさらに欲を出してSDカードにまで拡張しようとして頓挫しました。やっぱりハードがからむとArduinoと言えどもそう簡単には動いてくれません。ともあれ、暮れからここまでのESP8266によるWebサーバーの実験の数々をご紹介いたします。
Arduinoは楽だけど、こんな電子工作で良いのだろうか(1/3/2016)
新年が来た。今年は家族が酷い風邪をひいてちょっとお正月どころではなかったのだが、電子工作は絶好調である。ESP8266に簡単なウェブサーバーを入れ、ネットを通してLEDの点滅が出来るまでになった。ウェブには他にも沢山の応用例が紹介されまだまだ色々なことができそうである。
出来たと言っても、今度のサーバーのLEDの点滅は、IPアドレスのあとにいちいち違ったリンク名を加えないと変わらない(/gpio/0か/gpio/1)。ここまで来たら一般の画面と同様、ON/OFFのボタンのクリックでLEDが点いたり消えたりするようにしたい。
GitHubなどからサーバーのサンプルコードを片っ端から持ち込んで調べる。Arduinoのスケッチは、基本はオブジェクト指向言語のC++なので、Nodeのときにも愚痴ったように、この種の言語はデータの抽象化が大規模なので、ソースはとても追いにくい。
そのうえライブラリーの公開がどうも一部だけなので(探せばあるのだろうが)、少し詳しく調べようとするとすぐ壁にぶつかって先に進めなくなる。それでも、ウェブ記事の中で、これからやろうとしている、hrefタグの文字列のクリックで候補を選ばせるようなっているスケッチを見つけた(ここのサーボモーター制御のところ)。
半日かかってHTMLをもう一度勉強し直し、このソースを参考に、スケッチを修正し始めた。そのコーディングの最中、長年疑問に思っていた謎が突然解決したのである。謎というのは、ファイルの概念のないサーバーでどうやって別の画面へリンクしているのかが分からなかった。
画面そのものは、あのタグの沢山入ったHTMLテキストをプログラムで作れば出るはずだが、画面から画面のリンクや、画像送出のロジックをファイルなしにどうやって実現するのかが謎だったのだ。
考えてみれば、HTMLのタグ<href=リンク先>や<src=ファイル名>は、そのリンクをクリックすれば、クライアントはリンク先をこれにしたGET要求レスポンスをサーバーに返してくる。これらは単なる文字列なので、これを調べるだけで、サーバーはこれから何をすべきかが判断できる。
そうか、何もファイルを用意する必要はないのだ。わかってしまえば何ということもない話だが、わからないときはこんなものである。今までもやもやしていた謎がすっかり晴れた。目から鱗が落ちた気分である。
それはともかく、参考にしたスケッチを少しだけいじって、LEDの点滅が画面の文字のクリックで出来るように修正した。出来た。ちゃんと動いた。あきれた。7年前にドイツのソースコードを導入して、えんこらえんこら作ったAC電源コントローラーのしかけは、ものの見事に1時間もしないうちにESP8266の中で完動した。
これはESP8266がすごいということに加え、Arduinoのしかけが優れているということなのだろう。シリアルポートの開設にしても、GPIOの設定にしても、全くハードを知る必要がない。何も考えないで楽々とI/Oディバイスを動かせる。
それに文字列の検索でも、文字列の中から所定の単語を拾い出す多様な関数が最初から用意されており、簡単にリンク先を選別するコードが書ける。文字数やデータタイプを気にする必要もない。
しかし、これで本当に電子工作をやったことになるのだろうか。車の楽しみと同じなのかもしれない。昔は、中古自動車を整備して箱根の峠を超えられるかどうかが、一人前のドライバーの証しだと言われていた(昔のポンコツ車は坂で簡単にオーバーヒートして箱根を登りきれなかったのである)。
電子工作でも同じだ。AVRやARMで、データシートを穴が開くまで読み、制御レジスターを設定し、せっせとコーディングしていた。UARTだけでも字化けせず通信出来るまで結構な手間と努力を必要としたのである。成功すればそれだけで美味しい酒が飲めた。車と同じで、電子工作の楽しみの場所がどんどん変わっているのだと考えるしかない。
フラッシュメモリーにファイルシステムを構築。本格的サーバーへ(1/8/2015)
Webサーバーはファイルが使えれば本格的になる。ウェブによれば、ESP8266でもSDカードが読み書き出来るようだ。しかしハードの実装はそう簡単ではない。その前に、ESP8266が持っている豊富なフラッシュメモリーを利用したフラッシュファイルシステムを試してみることにした。
index.htmlファイル位ならこの程度で十分だし、ちょっとした画像がESP8266のページに載るとお洒落ではないだろうか。日本語のサイト(http://blog.boochow.com/article/427214036.html)に、このフラッシュファイルシステム(SPIFFS)を丁寧に説明しているところがあったので、やってみる気になった。
導入はこのサイトにあるように、1)まず、Arduino IDEのバージョンを開発用に換え、2)フラッシュメモリにファイルシステムをアップロードするプラグインをArduino IDEに加える。3)それを使ってESP8266のフラッシュにファイルシステムを作る。4)それを対象に、いくつかの関数群で、ファイルを登録したり、削除したり、中身を読み書きする。
早速、指示通り作業を進める。2)のArduinoのプラグインのダウンロードとインストールまでは順調に進んだ。Arduinoの「ツール」メニューに、ESP8266 Sketch Data Uploadというプラグインのメニューが出来る。
しかし次のステップ3)で止まった。プラグインを実行するとエラーメッセージが出て、ファイルシステムが出来ない。プラグインのバイナリ(.jarファイル)を入れる場所を替えたり(ユーザーディレクトリ内でもArduinoのシステムディレクトリでも動く)、プラグインのダウンロードをしなおしたり、色々やるが同じエラーだ。
簡単にあきらめないのが所長の取り柄である。あきらめずに試行錯誤するうち、遂にプラグインが通り、ファイルのアップロードに成功した。原因はプラグインのバージョンが最新のArtduino IDEと合っていなかったためであった。
日本語サイトが紹介した、プラグインのURLは、http://arduino.esp8266.com/ESP8266FS-1.6.5-1105-g98d2458.zipで、9/9/2015のタイムスタンプがあるが、これは少し古く、最新のSPIFFSのサイトにあるESP8266FS-0.1.3.zip(タイムスタンプは11/13/2015)だとArduino1.6.7でも動く。
このプラグインは、カレントスケッチのフォルダー(現在の作業場所で.inoファイルのあるところ)の中に作ったdataフォルダーに収容されたファイルすべてをフラッシュメモリー内にインストールする。うまく行ったかどうかは、このdataフォルダー内のファイルの名前がアップロードの際に表示されるので簡単に確認できる。
この最新のプラグインの提供サイトは、最初の日本語サイトで紹介されたがリンクが行方不明(404)になっているサイトらしく、これを発見できたのは、例の「エラーメッセージの丸ごとサーチ」によるところが大きい。同様なエラーで悩んでいる人がポストした海外のフォーラムが見つかり、その中で紹介されていたサイトが上記のGitHubのサイトだった。
フラッシュ内のディスク読み出しは成功(1/9/2015)
3)のファイル組み込みがうまく行ったので、次の4)のステップに入る。ESP8266のスケッチで、フラッシュファイルシステムを起動させ、ファイルが読めるかどうかのテストである。
先ほどのdataフォルダーに適当なテキストファイルを入れた。Arduino IDEのプラグインを始動させ、フラッシュに書き込む。そのあと、このテキストファイルを読むスケッチを書く。表示先はシリアルモニターである。動かしてみた。問題なくテキストファイルの内容がモニターに表示された。
よーし、快調だ。このファイルシステムの最終目的が決まっていないので何をもって成功と言えるのか、はっきりしないところだが、少なくともやろうと決めたことが確実に実現していくことは、とても気分が良いものである。次はいよいよHTMLの中への画像埋め込みである。
ESP8266は、2MBのフラッシュメモリがあり、通常は半分も使っていない。さしずめ、JavaScriptなどを使った派手な制御画面のindex.htmlを作ることが第一の目標だが、もうひとつは画像の表示である。ページの背景画像にもなる。ただアップロードが遅い(11520bps 1.4KB/s)ので、テストのため小さな画像を探す。
昔、携帯にいれていた先代の猫の絵が見つかったのでこれを選ぶ(12KB)。HTMLにおける画像の表示のロジックを調べ始める。どうもこれまでのArduinoスケッチのWebサーバーに関する関数群(メソッド、メンバー)の対象はすべてが文字列で、jpegファイルのようなバイナリーファイルは出来ないようだ。
また、ネットで色々調べる。原理は、先ほどのGET要求レスポンスからファイル名を拾って、文字列のときと同様にファイルの中味を送れば良いはずだ。しかし具体的なコード例が見つからない。やっとpythonのページでやりかたを見つけた。
何だ、やり方はとても単純だった。content type: image/jpegなどのヘッダーデータの改行文字のあとに、バイナリを送れば良いだけだ。しかし、これまでの使っていたArduino IDEのWebサーバーの関数で試してみたが、みなうまく動かない。
JPEGファイルの送出が難しい。バイナリ―データを送れない(1/14/2016)
ESP8266の紹介サイトでも、このあたりになると日本語サイトは少ない。英語サイトにはあるのだろうが、つい無精して日本語版ばかり探す。ATコマンドでの動作など、入門部分を紹介するサイトは山ほどあるのだが、こうしたちょっと先に進んだ具体的な応用例を紹介するページは見当たらない。
一方では、ここのサイトのように、かなり高度な開発を紹介しているところもある。IOTの最先端の部分でもあり、情報は少し閉じた所で流れているのかもしれない。まあ、ESP8266で画像を出すホームページの需要などは殆どないだろうから、当たり前と言えば当たり前か。
ESP8266のWebサーバーのコードは、何種類かあって(ArduinoのEtherシールドからの派生もあるようだ)、その差を調べようとするが、参考になるサイトは見つからない。こうなると、紹介されているサイトのソースコードをしらみつぶしに調べて、バイナリを送っているコード例を探すしかない。で、片っ端から中味を調べる。
すると、このサイト(ソース後半)で、こういう使い方をしているところが見つかった。
client.write(dataFile, HTTP_DOWNLOAD_UNIT_SIZE);
まず、printではなくwriteというメソッド(メンバー関数?)と、このHTTP_DOWNLOAD_UNIT_SIZEという定数があやしい。clientというwebサーバーのオブジェクトの仕様がどこを捜しても見当たらないので確実ではないが、どうみてもこれは、HTTP_DOWNLOAD_UNIT_SIZEの長さ単位にバイナリファイルを送っているように見える。
だめもとである。このdataFileオブジェクトに例の猫の絵のファイルを入れ、テストしてみた。
ビンゴ!である。ESP8266のLEDのon/off指示画面の下に見事に猫の画像が表示された。いやあ、嬉しい。表示速度は少し遅いが、まぎれもなく先代の猫の絵だ。
画像付きホームページ完成。スケッチソース公開(1/18/2016)
いくつかのサイトのスケッチソースを参考にしながら、画像付きのLED点滅のホームページが完成した。制御方式は、最初はイベント駆動のような形をしたserver.on("リンク文字列",割り込みエントリ)で、割り込み先を作り、loop()の中は割り込みハンドラーだけにする方式だったが、途中で変えた。
割り込み方式では、エラーが、どうやっても取れなかったことや(プロセスが終了しない)、共通する処理をすべての割り込み部に書く必要があり、コードに無駄が多かったので、普通のloop(){}の中にすべての処理を入れる方式に変えた。
スケッチソースを参考までに以下に掲載する。この前に、jpegファイルは、esp8266.jpgとして前記のフラッシュファイルシステムに登録しておかないと動かないのでご注意。不慣れなC++コーディングなので無駄なところがあることはご勘弁ねがいたい(とりあえずはこれで動いている)。
/* フラッシュファイルシステムを使った画像の出るサーバー * 1/11/2016 (C) 2016 LABO Gataro * ref. http://qiita.com/tadfmac/items/17448a2d96bd56373a66 */ #include <Arduino.h> #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <FS.h> #define BUFFER_SIZE 16384 const char *ssid = "XXXXXXX"; //"ここには無線ルーターのSSIDを記述"; const char *password = "YYYYYY"; //"ここには無線ルーターのパスワードを記述"; unsigned char buf[BUFFER_SIZE]; String s; int val; File jpegFile; WiFiClient client; //ESP8266WebServer server(80); WiFiServer server(80); boolean readFFS(){ jpegFile = SPIFFS.open("/esp8266.JPG", "r"); if(!jpegFile){ Serial.print("Failed to open /esp8266.jpg"); return false; } size_t size = jpegFile.size(); if(size >= BUFFER_SIZE){ Serial.print("File Size Error:"); Serial.println((int)size); }else{ Serial.print("File Size OK:"); Serial.println((int)size); } return true; } void Drawjpg(){ jpegFile = SPIFFS.open("/esp8266.JPG", "r"); // test test test size_t totalSize = jpegFile.size(); delay(1); Serial.print("JPEG proc size="); Serial.print(String(totalSize) + "\n"); client.write(jpegFile, HTTP_DOWNLOAD_UNIT_SIZE); // send a binary file jpegFile.close(); } void setup() { Serial.begin(115200); delay(10); pinMode(2, OUTPUT); digitalWrite(2, 0); Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); SPIFFS.begin(); // フラッシュFSを開く if(!readFFS()){ Serial.println("Read FFS error"); } while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } // Solid IP http://ashiyu.cocolog-nifty.com/blog/2015/08/indexhtml-8dfa.html WiFi.config(IPAddress(192,168,NNN,MMM),WiFi.gatewayIP(),WiFi.subnetMask()); Serial.println(""); Serial.println("WiFi connected"); server.begin(); Serial.println("Server started"); Serial.println(WiFi.localIP()); } void loop() { client = server.available(); if(!client) return; Serial.println("new client"); while(!client.available()){ delay(1); } String req = client.readStringUntil('\r'); Serial.println(req); client.flush(); if(req.indexOf("/gpio/0") != -1 ){ val = 0; } else if(req.indexOf("/gpio/1") != -1 ){ val = 1; } else if(req.indexOf("/esp8266.jpg") != -1) { s = "HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\n\r\n"; client.print(s); Drawjpg(); client.flush(); delay(1); Serial.println("JPEG was sent"); return; } s =""; s += "<html lang='ja'><meta charset='UTF-8'>\r\n"; s +="<style> a{ font-weight: bold; color: #FF6666;} </style>"; s += "<h1>ESP8266 リモートLEDスイッチ</h1>"; s += "<p>押されたスイッチは "; s += (val)?"ON":"OFF"; s += "<ul>"; s += "<li><a href='/gpio/0/'>OFF</a></li>\n"; s += "<li><a href='/gpio/1/'>ON</a></li>\n"; s += "</ul> \n"; s += "<p> ここに画像を入れるテストをします </p>\n"; s += "<img alt=\"graph here\" src=\"/esp8266.jpg\" />"; //"内の\はエスケープ s += "</html>\n"; client.print(s); digitalWrite(2, val); delay(1); Serial.println("Client disconnected"); client.flush(); }
SDカードの実装ができない(1/24/2016)
このあとさらに、SDカードの実装まで挑戦した。以前使っていたリニアPCMプレーヤーのテストベンチから、SDカードアダプターをとりだしESP8266につける。大した配線量ではない、簡単にすんだ。
次はソースだ。ソースは、すでに色々なところで公開されている。大元のGitHubまで戻り、sdcardinfoのスケッチを頂く。コンパイルも無事終了した。勇躍テストである。適当なSDカードを入れて電源ON。
残念、初期化がエラーで返ってきた。さあ、これからどうする。SDクラスライブラリの解析は資料もないし不可能だ。この種の言語は動けば便利だが、動かないときは全く手も足も出ない。とりあえずはオシロを持ち出して初期化の波形を調べる。
調べた結果は、ESP8266からのCSや、CMD(MOSI)は正常に出ているが、SDカード側からはレスポンスがないということがわかった。うーむ、これはハードか。時々状態が変わるときがあるので、接触不良の可能性もある。
オシロではこのあたりが限界だ。ロジアナで徹底的に調べるにしても情報が不足している。やっぱりハードがからむとArduinoでも一筋縄では行かない。このあたりで一区切りつけてブログに報告することにしよう。
| 固定リンク
| コメント (2)
| トラックバック (0)
最近のコメント