ESP32 WiFiにつながらないとき

# 1. WiFiルータにつなぐ

ESP32をWiFiルータに接続する時、[マニュアル](https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/wifi.html#wi-fi-sta-example)に倣い下記のようなコードを書きますが、なぜか全然つながらないことがあります。開発ボードのリセットスイッチを押して起動し直すと すんなりつながったりして、<b>なんなんだろう?</b>と思った経験がありませんか?

```c++
Serial.print("WiFi connecting");
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print('.');
}
Serial.println("\nWiFi connected.");
```

今回は結論を先に書きます。今後は下記のようなコードに変えましょう。つながらないイライラが解消します。


```c++:5秒待ってつながらない時は、reconnect()で再接続
bool done = true;
WiFi.begin(ssid, pass);
while (done) {
    Serial.print("WiFi connecting");
    auto last = millis();
    while (WiFi.status() != WL_CONNECTED && last + 5000 > millis()) {
        delay(500);
        Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
        done = false;
    } else {
        Serial.println("retry");
        WiFi.disconnect();
        WiFi.reconnect();
    }
}
Serial.println("\nWiFi connected.");
```

# 2. つながらない時は何が起こっているのだろう?

冒頭のコードの実行ログをシリアルモニタで確認してみました。
`STA_CONNECTED`イベントが発生していながらも、**status**が`WL_CONNECTED`にならないため、ずーっと`.`が続きます。

```console
20:55:54.382 -> [     3][D][esp32-hal-cpu.c:244] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
20:55:54.825 -> [   450][I][esp32-hal-psram.c:96] psramInit(): PSRAM enabled
20:55:54.892 -> [   483][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 0 - WIFI_READY
20:55:54.960 -> WiFi connecting[   574][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 2 - STA_START
20:55:54.994 -> [   616][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 4 - STA_CONNECTED
20:55:55.476 -> ..........................................................................................
```

下記は数秒でつながった時のログ。比較すると、上は、IPアドレスの払い出しを待っているように見えます。（固定IPならこんなことにならないのかも？）

```console
20:58:32.511 -> [     3][D][esp32-hal-cpu.c:244] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
20:58:32.955 -> [   450][I][esp32-hal-psram.c:96] psramInit(): PSRAM enabled
20:58:32.955 -> [   483][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 0 - WIFI_READY
20:58:33.056 -> WiFi connecting[   572][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 2 - STA_START
20:58:33.089 -> [   615][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 4 - STA_CONNECTED
20:58:33.572 -> ......[  3989][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 7 - STA_GOT_IP
20:58:36.509 -> [  3989][D][WiFiGeneric.cpp:991] _eventCallback(): STA IP: 192.168.21.114, MASK: 255.255.255.0, GW: 192.168.21.1
20:58:36.577 -> .
20:58:36.577 -> WiFi connected.
```

:::note warn
つながらない時は、DHCPプロトコルのシーケンスのどこか途中で止まっているのでしょうか?　TCP/IPレベルのパケットトレースを確認すればより詳しくわかると思いますが、今回はそこまでしていません。
:::

今回、この記事にまとめるにあたり、ESP32のハードリセットによりWiFi接続までの所要時間を測ってみました。（`WiFi connecting`から`WiFi connected`までのシリアルモニタのタイムスタンプ時間［**秒**］）

|回|所要時間||回|所要時間||回|所要時間|
|-:|-:|-|-:|-:|-|-:|-:|
| 1|  0.519||21|  3.530||41|  7.521|
| 2|  0.508||22|  7.509||42|**135.502**|
| 3|  1.529||23|  0.508||43|  0.489|
| 4|  0.521||24|  3.521||44|  3.527|
| 5|  0.515||25|  0.487||45|  0.515|
| 6|  0.510||26|  3.509||46|  0.489|
| 7|  3.000||27|  0.515||47|  0.517|
| 8|  0.490||28|  0.485||48|  0.512|
| 9|  0.511||29|  3.535||49|  3.488|
|10|  0.506||30|  3.521||50|  0.483|
|11|  0.513||31| **30.514**||51|  0.513|
|12|  0.518||32|  0.514||52|  0.520|
|13|  3.512||33|  7.510||53|  0.517|
|14|  3.508||34|  7.516||54|  0.511|
|15| **45.520**||35| **60.502**||55|  0.487|
|16|  0.501||36| **60.489**||56|  0.519|
|17|  0.517||37|**120.526**||57|  0.510|
|18|  0.524||38|**150.539**||58|  7.505|
|19|  0.511||39|  0.514||||
|20|  0.495||40|  0.522||||



ほとんどが数秒ですが、少ないながら長時間を要す時もありました。

|所要時間|回数|割合|備考	|
|:-:|:-:|:-:|:-|
|1秒以下	|35	|60%|平均0.508秒|
|3秒以下	|2	|3%|平均2.265秒|
|5秒以下	|9	|16%|平均3.517秒|
|8秒以下	|5	|9%|7.505秒、7.509秒、7.510秒、7.516秒、7.521秒|
|10秒以上|7	|12%|30.514秒、45.520秒、60.489秒、60.502秒、<br>120.526秒、135.502秒、150.539秒|
| 全体 |58|平均11.985秒|10秒以上の7回が全体平均を押し上げている|

10秒以上の7回は永久につながらないのかと思うほどでしたが、内部でリトライしているのでしょうか、いつかはつながる結果となりました。でも、2分半（150秒）はかかりすぎです。
さらに要約すると『およそ8割は5秒以下でつながっている』ということで、冒頭の5秒の根拠としました。

|所要時間|回数|割合|備考|
|:-:|:-:|:-:|:-:|
|5秒以下	|46	|79%|　平均1.173秒|
|5秒超え	|12	|21%|平均53.429秒|

58回とサンプル数は少ないですが、『<b>5秒待ってつながらない時は、reconnectした方が早くつながる。</b>』が今回の結論。


# 3. 先人に学ぶ

WiFiにつながらないこの現象に遭遇したとき、回避策をネットで検索したところ、次の二つの記事を見つけました。

https://qazsedcftf.blogspot.com/2019/01/esp32arduino.html

https://scrapbox.io/fab-wiki/ESP32でWiFiが切れている時に再接続する

一つ目の『[**ソフトウェアリセット**](https://qazsedcftf.blogspot.com/2019/01/esp32arduino.html
)』の記事は、何回かループしても接続できない場合は、プログラムから`リセット`する内容です。

```c++:ソフトウェアリセット（10回のループでつながらないならリセット）
int lpcnt=0 ;
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) { 
    delay(500);
    lpcnt +=1 ;
    if (lpcnt>10) { ESP.restart(); }
    Serial.print(".");
}
```

手動でリセットスイッチを押す手間が省けますね。


二つ目の『[**WiFiが切れている時に再接続する**](https://scrapbox.io/fab-wiki/ESP32でWiFiが切れている時に再接続する)』記事は、`一定時間後に再接続する`という内容です。
冒頭のコードは、この記事を参考にさせていただきました。ありがとうございます。

```c
unsigned long currentMillis = millis();
// if WiFi is down, try reconnecting
if ((WiFi.status() != WL_CONNECTED) && (currentMillis - previousMillis >=interval)) {
  Serial.print(millis());
  Serial.println("Reconnecting to WiFi...");
  WiFi.disconnect();
  WiFi.reconnect();
  previousMillis = currentMillis;
}
```

# 4. 検証

今回のコードがどれほど有効なのか検証しました。

|所要時間|回数|割合|備考|
|:-:|:-:|:-:|:-|
|1秒以下	|55	|59%|平均0.507秒|
|4秒以下	|16	|17%|平均2.998秒|
|6秒以下	|22	|24%|平均5.502秒<br>全数がリトライによるもの|
|全体|93|平均2.117秒|

・1秒以下が全体の6割という点は、前回と同じ。
・5秒を超えた22回の全数がリトライによるものですが、6秒以下となっています。
つまりリトライ後は1秒でつながったとう結果で、効果絶大であると思います。
リトライが発生した22回のログを検証しましたが、リトライ後はすべて`.`が1個しか出ていませんでした。

```console:リトライ発生時
21:24:19.128 -> [     3][D][esp32-hal-cpu.c:244] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
21:24:19.571 -> [   450][I][esp32-hal-psram.c:96] psramInit(): PSRAM enabled
21:24:19.606 -> [   482][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 0 - WIFI_READY
21:24:19.684 -> [   574][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 2 - STA_START
21:24:19.718 -> WiFi connecting[   620][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 4 - STA_CONNECTED
21:24:20.214 -> ..........retry
21:24:24.730 -> WiFi connecting[  5585][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 5 - STA_DISCONNECTED
21:24:24.730 -> [  5585][W][WiFiGeneric.cpp:950] _eventCallback(): Reason: 8 - ASSOC_LEAVE
21:24:24.730 -> [  5616][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 4 - STA_CONNECTED
21:24:24.770 -> [  5656][D][WiFiGeneric.cpp:929] _eventCallback(): Arduino Event: 7 - STA_GOT_IP
21:24:24.811 -> [  5657][D][WiFiGeneric.cpp:991] _eventCallback(): STA IP: 192.168.21.114, MASK: 255.255.255.0, GW: 192.168.21.1
21:24:25.206 -> .
21:24:25.206 -> WiFi connected.
```


これなら、閾値を5秒ではなく、1秒にした方が全体平均は2秒以下になるのでは？と推定し、実験してみました。コードは5秒を1秒に変えるだけです。

```diff_cpp:1秒待ってつながらない時は、reconnect()で再接続
bool done = true;
WiFi.begin(ssid, pass);
while (done) {
    Serial.print("WiFi connecting");
    auto last = millis();
- //while (WiFi.status() != WL_CONNECTED && last + 5000 > millis()) {
+   while (WiFi.status() != WL_CONNECTED && last + 1000 > millis()) {
        delay(500);
        Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
        done = false;
    } else {
        Serial.println("retry");
        WiFi.disconnect();
        WiFi.reconnect();
    }
}
Serial.println("\nWiFi connected.");
```

|所要時間|回数|割合|平均|
|:-:|:-:|:-:|:-|
|1秒以下	|26	|68%|0.504秒|
|1秒超え	|12	|32%|1.503秒<br>全数がリトライによるもの|
|全体|38||0.819秒|

・1秒以下が全体の7割弱という傾向は、これまでと同じ。
・1秒を超えた12回の全数がリトライによるものですが、2秒未満となっています。
サンプル数が38回と少ないですが、全体平均が1秒以下になり、これが本当の正解か?

# 5. さらなる改善?

もしかしたら、ステータス`WL_CONNECTED`を確認する前に、`reconnect`したら、もっと改善できるのかも?と考え、コードを以下のように変更して実験しました。

```cpp:最初からreconnect()
bool done = true;
WiFi.begin(ssid, pass);
while (done) {
    Serial.print("WiFi connecting");
    WiFi.disconnect();
    WiFi.reconnect();
    auto last = millis();
    while (WiFi.status() != WL_CONNECTED && last + 1000 > millis()) {
        delay(500);
        Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
        done = false;
    } else {
        Serial.println("retry");
    }
}
Serial.println("\nWiFi connected.");
```

実は、1度もリトライが発生しなくなるのでは?と想定していましたが、結果は違いました。

|所要時間|回数|割合|平均|
|:-:|:-:|:-:|:-|
|1秒以下	|26	|74%|0.499秒|
|2秒以下	| 9	|26%|1.508秒<br>全数がリトライによるもの|
|全体|35||0.758秒|

全35回のうち、9回リトライが発生しました。リトライの割合は減りましたが、全体平均はさほど改善が見られません。
また、いきなり`disconnect`、`reconnect`も不自然ですし、この方法は却下します。

# 6. 最終結論

以上の実験から、今回は『<b>1秒待ってつながらない時は、reconnectした方が早くつながる。</b>』を最終結論とします。（冒頭の**5秒説**を訂正いたします。）
通常は**0.5**秒でつながり、たまにリトライが発生しても、**1.5**秒でつながります。劇的な改善です。



```c++:1秒待ってつながらない時は、reconnect()で再接続
bool done = true;
WiFi.begin(ssid, pass);
while (done) {
    Serial.print("WiFi connecting");
    auto last = millis();
    while (WiFi.status() != WL_CONNECTED && last + 1000 > millis()) {
        delay(500);
        Serial.print(".");
    }
    if (WiFi.status() == WL_CONNECTED) {
        done = false;
    } else {
        Serial.println("retry");
        WiFi.disconnect();
        WiFi.reconnect();
    }
}
Serial.println("\nWiFi connected.");
```

ただし、使用しているWiFiルータにも依ると思われますので、閾値は参考とお考えください。
（今回は、[Aterm WG1200HS4 PA-WG1200HS4](https://www.aterm.jp/product/atermstation/product/warpstar/wg1200hs4/)、 IEEE802.11g(2.4GHz帯)を使用）

以上、何かの参考になれば嬉しいです。