M5StickCのWiFi接続情報を、スマホで入力できるようにする

## はじめに

こんにちは。**電気毛布を自動制御**しようと頑張っているエンジニア[@tmitsuoka0423](https://twitter.com/tmitsuoka0423)です。
以前、[obnizを使った電気毛布制御システム](https://qiita.com/tmisuoka0423/items/582ff0c303abe8570ee5)を作成しました。

手元に M5StickC があったので、上記の記事で作成したプログラム同等のスケッチを作成し、快適な睡眠生活を送っていました。
また、[Ambient](https://ambidata.io/)というサービスを使い、測定した布団の温度などを可視化して楽しんでいました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/ff6af59e-b344-733d-8018-87776cc634e2.png" width=700>

## Wi-Fi接続のために、毎回ソースを書き換える？

ある日、フィードバックをもらうため知人に電気毛布システムを貸すことになったのですが、そこで問題が発生しました。
M5StickC に書き込んでいるスケッチに、**私の家のネットワーク接続情報がハードコーディング**されていたのです。
これでは知人の家のネットワークにつながりません。

```c:ネットワークに接続する
  String ssid = "hoge";
  String password = "hogehoge";
  WiFi.begin(ssid, password);
```

幸い、知人は IoT にとても明るいエンジニアでしたので必要なソースコードを渡し、**自分でSSIDとパスワードを書き換えてM5ScickCに書き込んでくれ**とお願いできました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/144bb567-ddce-7714-d24a-45e081d73bc2.png" wdith=700>

このシステムは、実は**Arduinoスケッチを修正して、M5StickCに書き込める人しか使えない**仕組みになってしまっていたのです。
製品化を考える上で、使える人という観点であまり制約を設けたくありません。

そこで本記事では、スマホからネットワーク接続情報を入力できる仕組みとソースコードを紹介します。

## ネットワーク接続までの流れ

接続の流れとしては、大まかに**2ステップ**となります。
1. スマホと M5StickC を Wi-Fi 接続し、ネットワーク接続情報を M5StickC に送る
2. M5StickC をネットワークに接続する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/3a042fd4-d1a4-9298-f0da-4c24241c473b.png" width=700>
順番に見ていきましょう。

なお、ソースコードはすべて[[m5stickc-wifi-entry-sample-public]](https://github.com/mono0423/m5stickc-wifi-entry-sample-public)にアップしていますので、全量はこちらからご確認ください。
記事では、ソースコードをピックアップして説明します。

### ⓪起動時はアクセスポイントモード

M5StickC は実はアクセスポイントとして振る舞うことができます。

以下のように書くと、M5StickC がアクセスポイントとなり
- SSID: "M5S_AP"
- Password: "12345678"
で、スマホや PC から Wi-Fi 接続できるようになります。

```c:アクセスポイントとして振る舞う
  WiFi.softAP("M5S_AP", "12345678");
```

### （1）スマホからM5StickCにWi-Fi接続する

スマホで Wi-Fi 一覧を見ると、`M5S_AP`があるはずです。
パスワードを入力して接続します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/c0a1c212-13ca-6695-37dd-aee06fc7468b.png" width=700>

### （2）ネットワーク接続情報入力ページを表示する、（3）送信する

`M5S_AP`に接続した状態で、`http://192.168.4.1/`にアクセスすると以下のような画面が表示されるので、家にあるネットワークの SSID とパスワードを入力します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/ae1c541d-33a3-d2b3-4715-4814798c5eb0.png" width=700>
表示されている画面は、M5StickC から HTML を返却しています。
`接続するボタン`をタップすると、form が GET で送信されます。

```c:レスポンスを返却する
  WiFiServer server(80);
  WiFiClient client = server.available();
  String s = "";
  s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
  s += "<!DOCTYPE html>";
  s += "<html lang=\"ja\">";
  s += "  <head>";
～～～～～～～～～～～～～～～～～～～～(略) ～～～～～～～～～～～～～～～～～～～～
  s += "    <main>";
  s += "      <form method=\"GET\" action=\"/connect\">";
  s += "        <input type=\"text\" name=\"ssid\" placeholder=\"SSID\" />";
  s += "        <input type=\"password\" name=\"password\" placeholder=\"Password\" />";
  s += "        <button type=\"submit\">接続する</button>";
  s += "      </form>";
  s += "  </body>";
  s += "</html>";
  client.print(s);
  client.flush();
  client.stop();
```

### （4）M5StickCをネットワークに接続する

スマホで入力したネットワーク接続情報を受け取るには以下のようなコードを書きます。

```c:GETリクエストを受け取る
  String req = client.readStringUntil('\r');
  int addr_start = req.indexOf(' ');
  int addr_end = req.indexOf(' ', addr_start + 1);
  String path = req.substring(addr_start + 1, addr_end); // path -> "/connect?ssid=<SSID>&password=<Password>"
```

String 型の変数`path`に`SSID`と`パスワード`が入ってくるので、正規表現かなにかで抜き出し、以下のコードを書きます。

```c:M5StickCをネットワークに接続する
  WiFi.begin(ssid, password);
```

ネットワーク接続に成功した場合、M5StickC のモニタに`Connected!`と表示されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/434abbe7-4b72-c2e1-4556-0268deb7f74d.png" width=700>
シリアルモニタで、ローカル IP アドレスも割り当てられていることが確認できました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/162d5d2e-3cfc-bfbc-9950-e761a72220e2.png" width700>

## 蛇足

- よく思い出したら、[スマートコンセントHS105](https://www.tp-link.com/jp/home-networking/smart-plug/hs105/)を Wi-Fi に接続するときに似たような手順で接続していた（HS105 はスマホアプリだった）
- なんとなく裏側の実装がわかったと同時に、"誰でも IoT 製品を Wi-Fi に接続する方法"としてある種の答え合わせができた気がする。
- ライブラリとして公開していきたい