ESP32(M5StickC)でWPSを使うと設定ファイルも必要ないし便利ですよ

皆さんESP32でWifiを使うときのIDとPASSってソースファイルに直書きしてますか？
ハードコードが嫌だからSDカードやSPIFFSに保存して再起動の度に読みだしてWiFi.begin(ID,PASS)してませんか？
スマホ経由でリモート設定するにしても保存が必要ですよね？

WPSを使うとそんな手間がなくなるよというライフハックです。

## WPSとはなんぞや
WPSって聞いたことあるけど使ったことないって人も多いと思いますが、
一言で言うとルーターのボタンを押すと自動でIDとPASSをルーターが教えてくれて勝手にWiFiが繋がるという機能です

WPSとは？ | IODATA アイ・オー・データ機器
https://www.iodata.jp/support/qanda/answer/s18923.htm

ESP32はWPSに対応してるのでまずWPSで接続するスケッチを作成します

こちらのサンプルからコピペしてきます

arduino-esp32/WPS.ino at master · espressif/arduino-esp32 · GitHub
https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WPS/WPS.ino


## WPSで接続する

詳細はスケッチを見てもらうとして重要なところだけ抜き出します

```cpp

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println("WiFi started");
  WiFi.begin(); // ID,PASS無しで実行する

  int count = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); // 500ms毎に.を表示
    Serial.print(".");
    count++;
    if (count == 10) {
      // 5秒間待ってからWPSを開始する
      // 以下サンプルそのまま
      WiFi.onEvent(WiFiEvent);
      WiFi.mode(WIFI_MODE_STA);
      Serial.println("Starting WPS");
      wpsInitConfig();
      wpsStart();
    }
  }
  Serial.println("\nConnected");
}

void loop() {
  // nothing to do here
}

```

まずサンプルでよくある

```cpp
  WiFi.begin(); // ID,PASS無しで実行する
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); // 500ms毎に.を表示
    Serial.print(".");
```

で接続を失敗させ5秒間待ってからWPSをスタートします

WPSの待機状態に入ってからルーターのWPSボタンを長押しすると自動でWPSのシーケンスが始まり勝手に繋がります(サンプルスケッチそのまま)

たまに失敗することがあるので何回かリセットして繰り返すと繋がります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/3e811ae0-1c7d-8978-00e6-20dbec99bbfe.png)

## WPSで繋がったけどIDとPASSはどうなってる？
というのが疑問だと思いますが先ほどWPSでWifiが繋がった状態で再起動するとどうなるでしょう？

**IDとPASSを記録してないから接続失敗してまたWPSが走る？**

と思われがちですが

**実は再起動するだけでWiFiが繋がります**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/902ee5d9-aa7d-f892-57e4-525b1651767a.png)

なぜかというと
**接続に成功すると「WiFiライブラリが自動でNVSにIDとPASSを記録する」**
からです

ESP32にはNVSという特殊な記憶領域がありまして、
デフォルトではWiFiライブラリが自動でID,PASSをNVSに書き込み、
**「WiFi.begin()でID,PASSが指定されなかった場合NVSを参照してID,PASSを読み込む」** という実装になっています

なのでWPSを使う使わないに関係なく「再起動ごとに設定ファイルからID,PASSを読みだして使用する」という処理は不要です

設定ファイルを使うにしてもWiFi.begin()を引数なしで実行して接続失敗したら読み出すという処理にした方が無駄に読み込みが発生しないので良いと思います

## ヘッダファイルにまとめる
WPSを使いたいだけなのにサンプルから変数や関数をコピーしないといけないのは大変なので
WPS部分をヘッダファイルにまとめました

WPSを使いたい時にincludeしてwpsConnect()を使うだけでよくなります

```cpp:wpsConnector.h
#pragma once
#include <Arduino.h>
#include "esp_wps.h"
#include "WiFi.h"

#define ESP_WPS_MODE WPS_TYPE_PBC
#define ESP_MANUFACTURER "ESPRESSIF"
#define ESP_MODEL_NUMBER "ESP32"
#define ESP_MODEL_NAME "ESPRESSIF IOT"
#define ESP_DEVICE_NAME "ESP STATION"

void wpsInitConfig();
void wpsStart();
void wpsStop();
String wpspin2string(uint8_t a[]);
void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info);
void wpsConnect();
```

```cpp:wpsConnector.cpp
#include "wpsConnector.h"

static esp_wps_config_t config;

void wpsInitConfig() {
  config.wps_type = ESP_WPS_MODE;
  strcpy(config.factory_info.manufacturer, ESP_MANUFACTURER);
  strcpy(config.factory_info.model_number, ESP_MODEL_NUMBER);
  strcpy(config.factory_info.model_name, ESP_MODEL_NAME);
  strcpy(config.factory_info.device_name, ESP_DEVICE_NAME);
}

void wpsStart() {
  if (esp_wifi_wps_enable(&config)) {
    Serial.println("WPS Enable Failed");
  }
  else if (esp_wifi_wps_start(0)) {
    Serial.println("WPS Start Failed");
  }
}

void wpsStop() {
  if (esp_wifi_wps_disable()) {
    Serial.println("WPS Disable Failed");
  }
}

String wpspin2string(uint8_t a[]) {
  char wps_pin[9];
  for (int i = 0; i < 8; i++) {
    wps_pin[i] = a[i];
  }
  wps_pin[8] = '\0';
  return (String)wps_pin;
}

void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info) {
  switch (event) {
  case ARDUINO_EVENT_WIFI_STA_START:
    Serial.println("Station Mode Started");
    break;
  case ARDUINO_EVENT_WIFI_STA_GOT_IP:
    Serial.println("Connected to :" + String(WiFi.SSID()));
    Serial.print("Got IP: ");
    Serial.println(WiFi.localIP());
    break;
  case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
    Serial.println("Disconnected from station, attempting reconnection");
    WiFi.reconnect();
    break;
  case ARDUINO_EVENT_WPS_ER_SUCCESS:
    Serial.println("WPS Successfull, stopping WPS and connecting to: " +
                   String(WiFi.SSID()));
    wpsStop();
    delay(10);
    WiFi.begin();
    break;
  case ARDUINO_EVENT_WPS_ER_FAILED:
    Serial.println("WPS Failed, retrying");
    wpsStop();
    wpsStart();
    break;
  case ARDUINO_EVENT_WPS_ER_TIMEOUT:
    Serial.println("WPS Timedout, retrying");
    wpsStop();
    wpsStart();
    break;
  case ARDUINO_EVENT_WPS_ER_PIN:
    Serial.println("WPS_PIN = " + wpspin2string(info.wps_er_pin.pin_code));
    break;
  default:
    break;
  }
}

void wpsConnect() {
  WiFi.onEvent(WiFiEvent);
  WiFi.mode(WIFI_MODE_STA);
  Serial.println("Starting WPS");
  wpsInitConfig();
  wpsStart();
}
```


```cpp
#include "WiFi.h"
#include "wpsConnector.h"

setup() {
  WiFi.begin();

  int count = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); // 500ms毎に.を表示
    Serial.print(".");
    count++;
    if (count == 10) {
      // 5秒間待ってからWPSを開始する
      // 一度WiFiを切断してwpsConnect()を使う
      WiFi.disconnect();
      wpsConnect();
    }
  }
}
```
## まとめ
ルーターを変更したりしてIDとPASSが変わってしまった場合、
・ソースのID,PASSを書き換えて書き込み直す
・スマホからマイコンにアクセスしてID,PASSを入力する
・SDカードを抜いてID,PASSを書き換える
という作業が発生して凄くめんどくさいことになります

ですが、WiFI.begin()を引数なしで実行→接続失敗したらWPSを実行というフローにしておくと
マイコンを再起動する→ルーターのWPSボタンを押すという作業だけでWiFiに接続することができるので相当手間が省けるのではないかと思います

再設定はそんなに起きることはないとは思いますがとりあえず「こんなこともあろうかと」WPSを仕込んでおけば未来の自分が助かるかもしれません(ﾟ∀ﾟ)

ちなみにWPS試してみたいけどWiFiに繋いじゃったから勝手に繋がって試せないよ！というときは
WiFi.disconnect(false,true)を実行するかM5BurnerでファクトリリセットすればNVSを含むフラッシュ領域が初期化されるので試せるようになります