1. はじめに
WiFiのSSID/Keyを設定する手段としてtzapu/WiFiManager1を利用しています。正常時は特に問題はなく快適です。ここでは、回復処理についていくつか試行錯誤した内容を記載しています。
M5StickC, M5StickC Plus, M5Atomの以下の環境で試行錯誤しました。
- Arduino-IDE
1.8.19 (Widnows11)
- Boards Manager:
M5Stack official version 2.0.4
- Library Manager:
M5StickC 0.2.8
M5StickC Plus 0.0.8
M5Atom 0.1.0
WiFiManager 2.0.12-beta
WiFi: Wireless Fidelity
SSID: Service Set IDentifier
2. WiFiManagerのインスタンス宣言
WiFiManagerをnon blocking modeで使用する場合、WiFiManagerのインスタンスをグローバルで宣言する必要がある2と説明にあります。WiFiManagerは、Configuration Portalを起動してこれが完了すると自身も終了する模様です。Configration Portalを起動することなくWiFi接続できた場合、WiFiManagerは動作しつづける模様です。これらのことは、WiFi接続状態が変化した際にWiFiManagerがメッセージを出すかどうかで確認できます。
一方、blocking modeで使用する場合は、WiFiManagerのインスタンスを必ずしもグローバルで宣言する必要はありません。setup()内でWiFiManagerを完結させたい場合、setup()内でインスタンス宣言するほうが望ましいと言えます。setup()が終了した時点でWiFiManagerを破棄することができます。
- non blocking mode: 処理を、他の処理と並行して行う
- blocking mode: 処理が終わるまで、次の処理に移らない
- Configuration Portal: 一時的にWiFiのアクセスポイントとなり、SSIS/Keyを設定するWebページを公開する。
3. 一時的なWiFi接続断からの回復
以下は、arduino-esp32のWiFiType.h3からwl_status_tの定義を抜き出しました。
typedef enum {
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
WL_IDLE_STATUS = 0,
WL_NO_SSID_AVAIL = 1,
WL_SCAN_COMPLETED = 2,
WL_CONNECTED = 3,
WL_CONNECT_FAILED = 4,
WL_CONNECTION_LOST = 5,
WL_DISCONNECTED = 6
} wl_status_t;
WiFi接続状態でアクセスポイントである無線ルーターの電源を抜くと、以下の様にステータスが変化しました。
WL_CONNECTED → WL_CONECTION_LOST → WL_NO_SSID_AVAIL
この状態で無線ルーターの電源を投入すると、以下の様にステータスが変化しWiFi接続が回復しました。
WL_NO_SSID_AVAIL → WL_IDLE_STATUS → WL_CONNECTED
以上から、WL_NO_SSID_AVAIL状態については、SSID/Keyが有効である限り、WiFiの回復機能が有効です。
4. WiFi切断状態からの回復
WL_DISCONNECTED状態からのWiFi接続には、WiFi.reconnect()を実行する必要があります。WiFi.reconnect()は、EEPROMに保存されているSSID/Keyを使用します。保存されていない場合、WiFi.reconnect()はfalseを返します。以下は、arduino-esp32のWiFiSTA.cpp4からの抜粋です。reconnect()のなかでesp_wifi_disconnect()とesp_wifi_connect()が呼ばれています。各々、WiFi.disconnect()とWiFi.begin()で使用されている機能です。
/**
* will force a disconnect and then start reconnecting to AP
* @return true when successful
*/
bool WiFiSTAClass::reconnect()
{
if(WiFi.getMode() & WIFI_MODE_STA) {
if(esp_wifi_disconnect() == ESP_OK) {
return esp_wifi_connect() == ESP_OK;
}
}
return false;
}
EEPROM: Electrically Erasable Programmable Read-Only Memory
WiFiManagerでのWiFi接続失敗
WiFiManagerの一連の処理でWiFi接続ができない状況としては、経験的に以下が想定できます。
- EEPROMに保存されたSSID/Keyで、アクセスポイントの都合などで一時的に接続できなかった。このため、立ち上がってきたconfiguration portalを無視した
- EEPROMにSSID/Keyが保存されていないか、古いなどそのままでは接続できない。このためconfiguration portalでSSID/Keyを設定しようとしたが失敗した
WiFiManagerでWiFi接続に失敗した場合、WL_DISCONNECTEDまたはWL_CONNECT_FAIL状態となります。WL_DISCONNECTED状態に対しては以下の回復処理が考えられます。
- WiFi.reconnect()を実行し、EEPROMに保存されたSSID/Keyでの接続を試みる。
- WiFi.reconnect()に失敗した場合、システムをリブートする。
リブートによりシステムの不安定要素が減り、confuguration portalによるSSID/Keyの再設定も可能になります。WL_CONNECT_FAILの場合、詳細原因は未追及ながらリブートによる解消が期待できます。
localtimeの影響
現地時間localtimeは、システム時計の値に基づきますが、これが2017年以降でないとWifi.reconnect()を実行してもWL_DISCONNECTED状態から抜け出せないという不可解な現象に悩まされています。localtimeを取得するgetLocalTime()は、2016年以前の場合Falseを返す仕様であり関連性がありそうですが、詳細を突き詰めるには至っていません。
対策としては以下を考えました。
- システム起動時にRTCから現在時刻を設定する
- RTCがないかまたはRTCが有効でない場合、システム起動時に2017年以降の仮の時刻を設定する
上記2.の場合、localtimeが正しくないですがgetLocalTime()はtrueを返します。localtimeの有効性は別途管理する必要があります。以下は、esp32-hal-time.c5からのgetLocalTime()の抜粋です。
bool getLocalTime(struct tm * info, uint32_t ms)
{
uint32_t start = millis();
time_t now;
while((millis()-start) <= ms) {
time(&now);
localtime_r(&now, info);
if(info->tm_year > (2016 - 1900)){
return true;
}
delay(10);
}
return false;
}
5. 実装例
電波の届かないところにある電波時計の時刻を合わるため、NTPに同期した時刻に基づいて疑似的にJJY信号を発信する装置6を作成しました。WiFi, NTP, およびRTCを合わせて7試行錯誤して作成した処理の流れは以下です。
- RTCの時刻が有効の場合はシステムに設定する(localtime有効)。有効でない場合は仮の時刻(2017年以降)を設定する
- WiFiに接続する。WiFiManagerをblocking modeで使用し、処理終了後は破棄する
- WiFiの状態にかかわらず、NTPを起動する
- 処理ループにおいて、WiFiがWL_CONNECT_FAILの場合、原因解消を期待してリブートを行う
- WL_DISCONNECTEDの場合はWiFi.reconnectを行う。3回実行して回復しない場合はリブートする
- NTPの時刻同期が成功(localtime有効)したらRTCを更新する
- localtimeが有効な場合のみ、JJY信号を送信する
6. おわりに
不可解な現象についてソースを参照しながら原因を突き止めることが重要ですが、時間がかかってしまいます。間違いや改善など皆様からアドバイスを頂ければ幸いです。