ESP-WROOM-02で作るWi-Fiガジェット

[KLab Advent Calendar 2016](http://qiita.com/advent-calendar/2016/klab) 12月11日の記事です。
KLab Games事業本部、大阪オフィスの庄司です。お仕事では、スマートフォンゲームのクライアント開発をしています。

#ESP-WROOM-02の紹介
[ESP-WROOM-02とは、Wi-Fi機能のあるARMマイコン](http://akizukidenshi.com/catalog/g/gM-09607/)で、Arduino IDEでWi-Fi機器が開発できます。とても安価で技適もあり、Arduino経験者にとって開発も容易だということで、去年あたりから流行し始めています。

今回は、ゴールを**「誰でもアクセスして、LEDをチカチカ(Lチカ)できるWi-Fi APを作成してみる」**に設定しました。
ESP-WROOM-02を**Wi-Fi AP**、および**Webサーバ**として動作させます。単体でWi-Fi APとして使うので外部ネットワークには接続できませんが、デバイス自身やSDカードに組み込んだコンテンツを提供可能です。また、CGIのような動的な仕組みを組み込むこともできます。

ちなみに、Wi-Fi APではなくWi-Fiのクライアントとして実装すれば、外部サイトに接続して情報を取得するようなデバイスを作ることができます。

#Captive Portalという仕組み
単純なWi-Fi APを作成して利用者がそこに接続しても、ウェブサーバのホスト名もしくはIPアドレスを知らないとアクセスできません。ゴールの「**誰でもアクセスできるようにする**」という点を考えると、いちいち接続先を教えたりといった面倒なことは避けたいです。

そこで目に付けたのが**Captive Portal**でした。

コンビニやホテルなどに設置されている**公衆Wi-Fiで、ログイン画面を表示させる仕組み**を「[Captive Portal](https://en.wikipedia.org/wiki/Captive_portal)」といいます。多くのスマートフォンOSやPC OSでサポートされています。
Wi-Fiに接続直後、導通チェック用のURLに自動でアクセスし、想定外のコンテンツが帰ってきたらそのコンテンツを利用者に通知してアクセスさせる、という仕組みです。そこでログインID/パスワードを入力したり、サービス利用規約に同意したりすることで、本来のサーバーへと接続できるようになります。

このCaptive Portalの仕組みを利用することで、**Wi-Fi APに接続直後、すぐにオリジナルコンテンツを提示する**ことができます。

ゲーム開発的な余談：
このようにCaptive Portalでは、任意の接続先IPアドレスに対して無関係の応答を返すので、**公衆Wi-Fi APに接続しているときにアプリデータのダウンロードやAPIの呼び出し**をすると**意図しないデータを取得**することがあります。
今回のようなCaptive PortalのダミーAPを立てることで、このような情況での動作確認が容易になります。

#Captive Portalを作ってみる
[ESP-WROOM-02の、Arduinoによる開発環境構築](http://trac.switch-science.com/wiki/esp_dev_arduino_ide)や[ESP-WROOM-02への書き込み時、動作時の配線](https://www.hirotakaster.com/weblog/esp-wroom-02%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B/)については、←のリンクを参照してください。

Arduino IDEの「File > Examples」メニューから、[「DNSServer > Captive Portal」のサンプルコード](https://github.com/esp8266/Arduino/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino)を探して開きます。
コンパイルしてデバイスに書き込むと、「DNSServer CaptivePortal example」というWi-Fi APが開始されます。

##Wi-Fi APの設定変更
サンプルコードのままではパスワードなしで誰でも接続できてしまいます。特に開発中は、ソースコードを変更してAPのSSID名とパスワードを変えておくと良いでしょう。
パスワードは、↓のような感じで第2引数で指定します。

```cpp
WiFi.softAP("testtest", "password");
//WiFi.softAP("SSID name"); // パスワードなしの場合
```
![notify.jpeg](https://qiita-image-store.s3.amazonaws.com/0/146392/f112cb54-f32a-e2d2-1286-3d74e4c05b50.jpeg)

Wi-Fi APには任意のSSID名が付けられるので、日本語の文字列やUnicode絵文字にしておくと見つけやすいです。

##Captive Portalのレスポンス内容
↓先ほどの「Wi-Fiネットワークにログイン」通知を開くと表示される画面
![login.jpeg](https://qiita-image-store.s3.amazonaws.com/0/146392/b460d42d-b620-f3dc-8ec7-72e2ab7c47be.jpeg)
Captive Portalのサンプルコードでは`responseHTML`として定義されている「Hello World」的なHTMLを返しています。

```cpp
String responseHTML = ""
  "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
  "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
  "be redirected here.</p></body></html>";
```
ここを編集することで、APへの初回アクセス時に表示されるコンテンツを変更できます。

ここまでで「接続するだけで固定のコンテンツを表示するWi-Fi AP」が作成できました。もし勉強会などで使う名刺代わりのWi-Fi APを作りたいなら、この部分の変更だけでも充分です。

注意点としては、(スマートフォン等が3GやLTEに接続中でも)Captive Portalで提示される画面からは外部ネットワークには繋がらないので、残念ながら**外部コンテンツへのリンクや画像は動作しません**。このAP内だけでコンテンツが閉じている必要があります。
このほか、セキュリティ的な観点から、JavaScriptで使える機能に制限があったり、Cookieが扱えなかったりします。

↓Googleへのリンクを作成して移動しようとしたところ
![outer-network.jpeg](https://qiita-image-store.s3.amazonaws.com/0/146392/58ece2e7-c803-f9d1-747d-545e626802dd.jpeg)

#APIを作成し、デバイスを操作する
ArduinoベースなのでLチカも簡単です。いつもの`pinMode`とか`digitalWrite`を使うだけです。

リクエストを受け付けるURIとして「/enable」と「/disable」を指定し、それらに対するレスポンスとして、GPIOの状態を変化させてみます。
コードは(ざっくり最低限だと)こんな感じになるでしょう。

```cpp
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);
const int LAMP_PIN {16};

// イベント処理を行う
void handleNotFound() {
  webServer.send(200, "text/html", "<html><body><a href=\"/enable\">enable</a> | <a href=\"/disable\">disable</a></body></html>");
}
void handleEnable() {
  digitalWrite(LAMP_PIN, HIGH);

  // 「/」に転送
  webServer.sendHeader("Location", String("/"), true);
  webServer.send(302, "text/plain", "");
}
void handleDisable() {
  digitalWrite(LAMP_PIN, LOW);

  // 「/」に転送
  webServer.sendHeader("Location", String("/"), true);
  webServer.send(302, "text/plain", "");
}

void setup() {
  pinMode(LAMP_PIN, OUTPUT);

  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); 
  WiFi.softAP("🎄");
  dnsServer.start(53, "*", apIP);
  webServer.onNotFound(handleNotFound);
  webServer.on("/enable", HTTP_GET, handleEnable);
  webServer.on("/disable", HTTP_GET, handleDisable);
  webServer.begin();
}
void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}
```

Wi-Fi APに接続し、表示されるリンクを辿ることで、LEDのオン・オフが出来ます。
今回はAdvent Calendarらしく、クリスマスツリーをチカチカさせてみました。上記の実装のほかに、デバイス内のストレージに点灯回数を保存していて、その値を表示させています。
![light.png](https://qiita-image-store.s3.amazonaws.com/0/146392/10d1ccaf-9b49-e56d-f56e-390379a2a441.png)

#まとめ
今回はAPIを持ったCaptive Portalを作ってみました。
例えばリレーと組み合わせると、部屋の照明の制御などにも応用できそうです。
これと同じ仕組みを個別の機器ごとに組み込むとWi-Fi APだらけになるので、現実的にはWi-Fiクライアント上のHTTPサーバとして実装して、mDNSホスト名で識別してAPIを呼び出して制御する、という感じになるかと思います。

というわけで、このように簡単にWi-Fiガジェットが作れてしまうESP-WROOM-02、冬休みの自由研究にいかがでしょうか。