SNSへはこちら

ESP8266でベアメタル(6) - Wi-Fiネットワークを用いてLチカする

ということでやってきました、NonOS SDK を用いて機能を実装してみようの回。今回は、無線ルーターに ESP-WROOM-02 を接続させて、家庭内 LAN 無いから遠隔でLチカをしようとの目的でやりました。

謝罪(唐突)

色々いじっていて分かったことがあります。それはたとえ甘えといっても、SDK を使わないと無理だったということです。無線部分の実装は「俺甘えてんなあ」とぶつぶつ言いながら SDK を使って見たわけですが、それでも想像以上に難しい

つまりこの NonOS SDK を使うことで、「無理!!!!」が「むっず!!!」に変わったということです。確かにこれをレジスタ手打ちしてたらバキバキに心がへし折れていたでしょうね。

...というわけで、関係各所の皆様(?)、この度は SDK を侮蔑するような発言を繰り返して誠に申し訳ございませんでした(誰に謝ってるんだ...)。今後はわざわざ SDK が提供されている理由についてもっと深く考え、上手く活用していく所存であります。SDK バンザイ!ただし Arduino IDE、お前だけは気に入らねぇ。

今回出来たもの

謝罪というお題目を唱えた所で、今回カタカタプログラミングをしてできたものをお見せしましょう。ってか死ぬほど大変だったよこれ。
例によって僕の思い込みとかが圧倒的に炸裂したおかげで、自分からドブに足を突っ込んでしまいました。

動画でお分かりでしょうが、軽く説明。
スマホ(PCでもよい)のブラウザから特定のローカル IP アドレスへアクセス。すると画面に LED ON と LED OFF の2つのボタンが出てくるので、押すと LED が押したボタンのとおりになる、というもの。すごいでしょう。

このマイコンでは TCP/UDP 通信が出来る espconn という何かがあるので、このうち TCP を利用して HTTP 通信を実装しています。

スマホ側では 192.168.0.22 にアクセスしています。これは ESP 側のログを UART で確認した際に出てきた IP アドレスです。 UART でログを監視しながら ESP をリセットさせると、自宅の Wi-Fi AP に接続した際にこの IP アドレスが表示されます。

参考にしたもの・ソースコード公開

今回のこの実装においては、以下を参考にしました。

ではソースコードを Gist 経由で貼っておきます。結構長くなりましたね。

プログラムの解説

ではざっと説明していきます。

初期化

まず user_pre_inituser_init ですが、前者についてはパーティションテーブルという謎のものを登録していると述べました。
後者は、各種ペリフェラルの初期化・設定を行っています。まずは UART を 115200bps で設定して、Wi-Fi AP への接続、os_timer の設定です。

この SDK では main 関数に触ることはできないので、実際に処理を繰り返したい場合はタイマーを使う必要があります。今回は os_timer を使うことにしています。

Wi-Fi AP への接続ですが、特に考えること無く表記のようなコードを書けばいいです。ただ、

    stationConf.bssid_set = 0;
    stationConf.threshold.rssi = 0;

は必須だと思ってください。ホビーユースだと MAC アドレス制限や電波強度の制限は要らないと思うので、無効化してしまいます。というか、自分含めて「なんだコレ!?」という方は取り敢えずこれを書いておきましょう。

wifi_station_connect() がコメントアウトされていると思いますが、リファレンスにも不要と書かれています。どうやら接続用の設定をしてしまえば、何もしなくてもこの user_init 関数を抜けた後に自動的に接続処理がされるようです。

最後に OS タイマーの初期化。100msec ごとに os_timer_cb というコールバック関数が呼ばれるようにしています。os_timer_t 型の構造体が必要らしいので使っていますが、良くわからん。多分ユーザーが気にする必要はないんでしょうね(適当)。

OSタイマーの役割

このタイマーでは LED インジケータの点滅TCP サーバの確立を行っています。マイコンは PC 等から自身の IP アドレスにアクセスされたかを 100msec に 1回監視しています。この際には LED をチカチカさせて接続中であることをユーザーに知らせます。

アクセスされたと認識した場合(関数中盤の if 文)、espconn_ で始まる関数を用いることによって TCP 通信を確立します。これでこのマイコンが TCP サーバーになりました。

espconn_regist_sentcb 等では、コールバック関数を登録しています。この SDK は割り込みによるコールバック処理で動作を記述するのが普通なようです。それぞれ受けから順番に、

  1. データ転送が完了した時
  2. データをクライアントから受信した時
  3. TCP 接続確立時

のコールバック関数を登録します。なお、「espconn_ の初期化は os_timer_cb の段階でやっておけばよかったじゃないか」と思われるかも知れませんが、リファレンスには「Wi-Fi 接続してから設定してちょ」との記述がありましたので、それに従っている次第です。

TCP通信のコールバック関数とか

ここからが記事の本題です。まずは HTTP のお勉強をします。

HTTP 通信

僕もよくわからないので適当なことを言うかも知れませんが、通常の HTTP 通信では TCP を利用し、HTTP ヘッダを送ることでサーバーにリクエストしたりします。例えば僕の PC から ESP8266 にブラウザでアクセスすると以下のようなデータが受信されるようです。なお、改行文字は CR+LF です。

GET / HTTP/1.1
Host: 192.168.0.22
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Purpose: prefetch
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8,zh-TW;q=0.7,zh;q=0.6


実際マジで意味がわからないのですが、少なくとも1行目では「HTTP1.1 で、サーバーのルートに GET リクエスト送るぜ」と言っています。そう言えば GET とか POST とかありましたねぇ...
ひとまずこれを受信したら、マイコン側から「200 OK」の HTTP ヘッダによってデータを送るようにすれば良いようです。

ここで注意なのは、この SDK の espconn_ で始まるもののうち TCP 通信用の API は、あくまでも汎用的な TCP 通信を実現するためのものであって、HTTP 通信を実装することには特化していないということです。これはどういうことかと言うと、僕自身 TCP と HTTP を混同していたので、HTTP ヘッダを SDK 側がよしなにやってくれるのだなと勘違いしていたのです。嗚呼愚かなり。文字通り TCP 用の関数群ですので、HTTP 処理は自分で書く必要があります。ここが沼ってたところでした。まじでつらい。

ではどのようなヘッダを送ればよいかですが、以下のような感じでいいようです。適当です。

HTTP/1.1 200 OK
Content-Length: %d
Content-Type: text/html
Connection: Closed

上の %d は、この直後に続くデータバイト数です。HTTP ではこの後に HTML コードを続けて書くようです。例えばこんな感じに。

HTTP/1.1 200 OK
Content-Length: 28
Content-Type: text/html
Connection: Closed
<html><h1>Hello!</h1></html>

実際にこちらを送信してみると、確かに表示されました。

データ受信コールバック関数

では、実際のコールバック関数の動作に入っていきます。この関数内では、リクエストに対してデータの送信と LED のオンオフを行っています。

まず、ルートに対してデータを要求された場合です。ヘッダの1行目に / HTTP/1.1 を含んだ場合ですね。こちらは HTML ソースを送ります。

<html>
    <head>
        <meta name="viewport" content="width=device-width">
    </head>
    <form method="post">
        <input type="submit" name="led" value="LED ON">
        <input type="submit" name="led" value="LED OFF">
    </form>
</html>

そういえば <body> 書いてないですね...まあいいっか。
このソースではボタンを押すと led=LED+ONled=LED+OFF の POST が送信され、ページがリロードされます。

そういえばこのボタンを押した時の挙動が Chrome と Safari は異なりますね。Chrome の場合は HTTP ヘッダを含めてデータを送るけれど、Safari は パラメータのみを POST で送信するようです。

送信完了コールバック関数

やることは単純。もうデータは送ってしまったので、接続を close します。

接続コールバック関数

何もしていません。

まとめ

ということで、だいたいお分かりいただけましたかね?多分わからないですよね...ちょっとややこしいし、僕自身あまり理解していません。

ですが、日本語で NonOS SDK を使った例がインターネット上にはほぼゼロなので、ざっとご紹介させていただきました。

そういえばリファレンスをざっと見していると mDNS があるじゃないですか!こういう別機能もやってみたいです。