見出し画像

プログラムと電子工作・再コンパイルなしでWiFi接続

M5StickC Plus は標準で WiFi に接続する機能が搭載されています。SSID、パスワードをソースコードに埋め込むと、初めての WiFi 環境で使用するときには、コード変更と再コンパイルが必要となります。つまり PC が必須となります。

そこで初めての WiFi 環境で簡単に WiFi 接続できる機能を実装しました。
2つの機能に対応します。

  1. WiFi ルータの WPS を使用する。

  2.  スマートフォンから SSID、パスワードを設定する。

おおよその処理手順は次のようになります。

  1. 前回の SSID、パスワードで WiFi 接続を試みる。

  2. ダメだったら、WPSを起動する。

  3. 押しボタンA が押されたら、自身を WiFi アクセスポイントとして Web サーバを立てる。

  4. スマートフォンなどで Web サーバにアクセスし、WiFi ルータの SSID、パスワードを入力する。

  5. 入力された SSID、パスワードで WiFi 接続を試みる。


目標

初めての WiFi 環境でも、スマートフォンさえあれば、WiFi 接続できる機能を実装します。

部品・機材

使用する部品は次のとおりです。「Hello, World!」と同じですが、Wi-Fi ルータとスマートフォンが必要です。

電子部品

  • M5StickC Plus 1台

開発用機材

  • PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ

  • UB-A・USB-C ケーブル

  • Wi-Fi ルータ

  • スマートフォン

開発手順

  1. PC と M5StickC Plus を USBケーブルで接続する。

  2. Arduino-IDE でスケッチ wifisetup.ino を開く。

  3. 同じフォルダに wificonnect.h、wificonnect.cpp、wpsConnector.h、wpsConnector.cpp を保存する。

  4. 検証・コンパイルする。

  5. M5StickC Plus に書き込む。

スケッチ

すごく長いですが、wpsConnector.h、wpsConnector.cpp 以外のスケッチを載せます。

wifisetup.ino

#include <M5StickCPlus.h>
#include <WebServer.h>
#include "wificonnect.h"

#include <nvs_flash.h>

//#define M5STICKC
#define M5STICKCPLUS
//#define M5STACK

#if defined(M5STICKC)
    #define LCDROTATION (3)
    #define DEFAULT_BACKCOLOR (BLACK)
    #define DEFAULT_TEXTSIZE (1)
    #define DEFAULT_LINEHEIGHT (8)
    #define LINE_0 (DEFAULT_LINEHEIGHT*0)
    #define LINE_1 (DEFAULT_LINEHEIGHT*1)
    #define LINE_2 (DEFAULT_LINEHEIGHT*2)
    #define LINE_3 (DEFAULT_LINEHEIGHT*3)
    #define LINE_4 (DEFAULT_LINEHEIGHT*4)
    #define LINE_5 (DEFAULT_LINEHEIGHT*5)
    #define LINE_SSID (DEFAULT_LINEHEIGHT*0)
    #define LINE_PASS (DEFAULT_LINEHEIGHT*1)
    #define LINE_URL  (DEFAULT_LINEHEIGHT*3)
    #define X_QR (40)
    #define Y_QR (20)
#elif defined(M5STICKCPLUS)
    #define LCDROTATION (3)
    #define DEFAULT_BACKCOLOR (BLACK)
    #define DEFAULT_TEXTSIZE (2)
    #define DEFAULT_LINEHEIGHT (15)
    #define LINE_0 (DEFAULT_LINEHEIGHT*0)
    #define LINE_1 (DEFAULT_LINEHEIGHT*1)
    #define LINE_2 (DEFAULT_LINEHEIGHT*2)
    #define LINE_3 (DEFAULT_LINEHEIGHT*3)
    #define LINE_4 (DEFAULT_LINEHEIGHT*4)
    #define LINE_5 (DEFAULT_LINEHEIGHT*5)
    #define LINE_SSID (DEFAULT_LINEHEIGHT*0)
    #define LINE_PASS (DEFAULT_LINEHEIGHT*2)
    #define LINE_URL  (DEFAULT_LINEHEIGHT*4)
    #define X_QR (80)
    #define Y_QR (60)
#elif defined(M5STACK)
    #define LCDROTATION (1)
    #define DEFAULT_BACKCOLOR (BLACK)
    #define DEFAULT_TEXTSIZE (2)
    #define DEFAULT_LINEHEIGHT (15)
    #define LINE_0 (DEFAULT_LINEHEIGHT*0)
    #define LINE_1 (DEFAULT_LINEHEIGHT*1)
    #define LINE_2 (DEFAULT_LINEHEIGHT*2)
    #define LINE_3 (DEFAULT_LINEHEIGHT*3)
    #define LINE_4 (DEFAULT_LINEHEIGHT*4)
    #define LINE_5 (DEFAULT_LINEHEIGHT*5)
    #define LINE_SSID (DEFAULT_LINEHEIGHT*0)
    #define LINE_PASS (DEFAULT_LINEHEIGHT*1)
    #define LINE_URL  (DEFAULT_LINEHEIGHT*4)
    #define X_QR (80)
    #define Y_QR (60)
#endif

const char* APP_NAME = "wificonnect";  /*アプリ識別子、15文字以下、英数字と_*/
/*APP_NAMEは、Preferencesに保存する値のアプリを識別するIDとなる。*/

WiFiConnect wificonnect(APP_NAME);
WebServer server(80);  /*WebServerオブジェクト、port 80*/

//------------------------------------------------------------------------------
//  setup()
void setup() {
    //  電源ON時に 1回だけ実行する処理をここに書く。
    //  LCDを初期化し、アプリ名を書く。
    M5.begin();
    M5.Lcd.setRotation(LCDROTATION);
    M5.Lcd.setTextSize(DEFAULT_TEXTSIZE);
    M5.Lcd.fillScreen(DEFAULT_BACKCOLOR);
    M5.Lcd.setCursor(0, LINE_0);
    M5.Lcd.print("wificonnect");

    Serial.begin(115200);
    delay(2000);

    //  WiFiに接続する。
    connectWifi();

    /*WiFi接続した後の処理を実行する。*/
    /*例えば*/
    M5.Lcd.setCursor(0, LINE_2);  /*3行目*/
    M5.Lcd.print("btnA to clear nvs.");
}
//------------------------------------------------------------------------------
//  loop()
void loop() {
    //  自動的に繰り返し実行する処理をここに書く。

    /*WiFi接続した後の処理を実行する。*/
    /*例えば*/
    static bool nvs_erased = false;

    //  ボタンの状態を更新する。
    M5.update();

    //  上ボタン(ボタンA)をチェックする。
    if (M5.BtnA.wasPressed() && !nvs_erased) {
        //  --- 上ボタンが押された。
        //  不揮発性メモリの名前空間すべてのメモリを消去する。
        nvs_flash_erase();      // erase the NVS partition and...
        nvs_flash_init();       // initialize the NVS partition.
        nvs_erased = true;

        M5.Lcd.setCursor(0, LINE_2);  /*3行目*/
        M5.Lcd.print("nvs all cleared.  ");
        Serial.print("nvs was all cleared.\n");
    }

    delay(1);
}
//------------------------------------------------------------------------------
//  connectWifi()、printDotLCD() は必須。
//------------------------------------------------------------------------------
//  WiFiに接続する。
void connectWifi()
{
    //  ステーションモードで WiFi接続を試みる。
    wificonnect.connectSTA();
    M5.Lcd.setCursor(0, LINE_0);
    M5.Lcd.print("Search WiFi");

    Serial.println("*** Search WiFi access point with station mode.");

    while (1) {
        M5.update();

        WiFiConnect::wifi_mode_t wifimode = wificonnect.mode();
        if (!wificonnect.isConnected()) {
            //  --- WiFi未接続
            switch (wifimode) {
            case WiFiConnect::WIFI_STA:
                //  1000msごとに「.」を表示する。
                printDotLCD(1000);

                //  前回の設定で WIFIアクセスポイントに接続するまで 10秒待つ。
                if (wificonnect.timeElapsed(10)) {
                    //  --- 10秒経った。WiFiに接続できない。
                    //      WPSで接続するか、SSID、パスワードを手動で設定する
                    //      必要がある。
                    if (!wificonnect.isWPSgoing()) {
                        //  --- WPSは開始していない。
                        //  WPSを開始する。
                        wificonnect.connectWPS();

                        M5.Lcd.setCursor(0, LINE_1);
                        M5.Lcd.println("Push router WPS");
                        M5.Lcd.println("BtnA->SSID/PASS");

                        Serial.println("*** WPS starts.");
                        Serial.println("  Push the router WPS switch,");
                        Serial.println("  or M5StickC button-A.");
                    }
                    else {
                        //  --- WPSで接続を試行中。
                        if (M5.BtnA.wasPressed()) {
                            //  --- BtnA(上ボタン)が押された。
                            Serial.println("*** Button-A was pressed.");

                            //  WPSでの接続を中断する。
                            wificonnect.stopWPS();

                            /*
                            カスタマイズしたページを作成することも可能。
                            wificonnect.webIndex(index_html);
                            wificonnect.webNotFound(not_found_html);
                            wificonnect.webPage("/about.html", about_html);
                            */
                            wificonnect.connectAP();

                            M5.Lcd.fillScreen(DEFAULT_BACKCOLOR);
                            M5.Lcd.setCursor(0, LINE_SSID);
                            M5.Lcd.printf("SSID: %s", wificonnect.AP_SSID());
                            M5.Lcd.setCursor(0, LINE_PASS);
                            M5.Lcd.printf("Password: %s", wificonnect.AP_Password());
                            M5.Lcd.setCursor(0, LINE_URL);
                            M5.Lcd.print("URL:");
                            M5.Lcd.qrcode(wificonnect.webUrl(), X_QR, Y_QR, 58, 3);
                            /*(x,y)=(X_QR,Y_QR) width=58, qr_version=3*/

                            Serial.println("*** Web site opens.");
                            Serial.printf("  AP SSID: %s\n", wificonnect.AP_SSID());
                            Serial.printf("  AP Password: %s\n", wificonnect.AP_Password());
                            Serial.printf("  Access %s\n", wificonnect.webUrl());
                        }
                    }
                }
                break;
            case WiFiConnect::WIFI_AP:
                delay(1);
                break;
            default:
                M5.Lcd.fillScreen(BLUE);
                M5.Lcd.setCursor(0, LINE_0);
                M5.Lcd.print("ERROR:\nWiFi not available");
                while (1) {
                    delay(1);
                }
                break;
            }
        }
        else {
            //  --- WiFi接続
            switch (wifimode) {
            case WiFiConnect::WIFI_STA:
                M5.Lcd.fillScreen(DEFAULT_BACKCOLOR);
                M5.Lcd.setCursor(0, LINE_0);
                M5.Lcd.println("*** WiFi connected");
                M5.Lcd.print("IP addr: ");
                M5.Lcd.println(wificonnect.ipAddr());

                Serial.println("*** WiFi connected.");
                Serial.print("  IP addr: ");
                Serial.println(wificonnect.ipAddr());

                //  ステーションモードで接続完了時の処理を実行する。
                wificonnect.doneConnecting();

                delay(2000);
                return;  /*FIN, end of wificonnect*/
                break;
            case WiFiConnect::WIFI_AP:
                //  アクセスポイントモードのときは、Webサーバが実行中。
                //  ブラウザからのアクセスを受け付ける。
                wificonnect.handleWebClient();
                delay(1);
                break;
            default:
                M5.Lcd.fillScreen(BLUE);
                M5.Lcd.setCursor(0, LINE_0);
                M5.Lcd.print("ERROR:\nWiFi not available");
                while (1) {
                    delay(1);
                }
                break;
            }
        }  /*end of if(is wifi connected ?)*/
    }  /*end of while(1)*/
}
//------------------------------------------------------------------------------
//  指定時間ごとに LCDに「.」を打つ。
//      in: uint32_t msec 指定時間(ms)
void printDotLCD(uint32_t msec)
{
    static uint32_t prev = 0;

    uint32_t now = millis();
    if (now - prev > msec) {
        prev = now;
        M5.Lcd.print(".");
    }
}
//------------------------------------------------------------------------------

wificonnect.h

#include <WebServer.h>
#include <ESPmDNS.h>
#include <Preferences.h>
#include "wpsConnector.h"

#define SSID "ssid"  /*Preferencesに保存する変数名、WiFiアクセスポイントの SSID*/
#define PASS "pass"  /*Preferencesに保存する変数名、WiFiアクセスポイントのパスワード*/
#define SSID_PASS_UPDATED "updated"  /*Preferencesに保存する変数名、WiFiアクセスポイントの SSID、パスワードを更新したか*/

class WiFiConnect
{
public:
    //  WiFiモード
    enum wifi_mode_t {
        WIFI_NONE,  /*未接続*/
        WIFI_STA,   /*ステーションモード*/
        WIFI_AP     /*アクセスポイントモード*/
    };

    //  コンストラクタ
    //      in: const cahr* app_name: アプリ識別子、15文字以下英数字と_、例 "wifi_connect"
    WiFiConnect(const char* app_name);

    //  デストラクタ
    ~WiFiConnect(void);

    //  ローカルドメインを求める。
    //  アクセスポイントモードで仮接続し、SSID、パスワードを
    //  設定するための Webサイトのローカルドメイン。
    //  mDNSで IPアドレスを得られる。
    //  return: const char* ローカルドメイン文字列、".local"を含まない
    const char* localDomain(void);

    //  ローカル IPアドレスを求める。
    //  上記ローカルドメインの IPアドレス。
    //  return: IPAddress ローカル IPアドレス
    IPAddress ipAddr(void);

    //  SSIDをメモリに一時記憶する。
    //      in: const char* ssid SSID
    void setSSID(const char* ssid);

    //  パスワードをメモリに一時記憶する。
    //      in: const char* pass 上記 SSIDのパスワード
    void setPassword(const char* pass);

    //  一時記憶した SSID、パスワードなどのパラメータを
    //  不揮発性メモリに保存する。
    void storeParams(void);

    //  一時記憶した SSID、パスワードなどのパラメータを
    //  忘れる。
    void clearParams(void);

    //  ステーションモードで WiFi接続を試みる。
    void connectSTA(void);

    //  WiFiモードを求める。
    //  return: wifi_mode_t WiFiモード
    wifi_mode_t mode(void);

    //  WiFi接続できたかチェックする。
    //  return: bool true 接続されている、false 未接続
    bool isConnected(void);

    //  最後の WiFi接続の試行から指定時間経ったかチェックする。
    //  return: bool true 指定時間経った、false 経っていない
    //      in: uint32_t sec 指定時間(秒)
    bool timeElapsed(uint32_t sec);

    //  WPSが開始しているかチェックする。
    //  return: true WPS接続を開始している、false 開始してない
    bool isWPSgoing(void);

    //  WPSで WiFi接続を試みる。
    void connectWPS(void);

    //  WPSでの接続を中断する。
    void stopWPS(void);

    //  SSID、パスワードを手動で設定する Webサーバの 
    //  index.html処理関数を登録する。
    //      in: void(*func)(void) 処理関数、null なし
    void webIndex(void(*func)(void) = nullptr);

    //  SSID、パスワードを手動で設定する Webサーバの 
    //  ページが見つからないときの処理関数を登録する。
    //      in: void(*func)(void) 処理関数、null なし
    void webNotFound(void(*func)(void) = nullptr);

    //  SSID、パスワードを手動で設定する Webサーバの 
    //  URIに応じた処理関数を登録する。
    //      in: char* uri ディレクトリ、例 "/about.html"
    //          void(*func)(void) 処理関数、null なし
    void webPage(char* uri, void(*func)(void) = nullptr);

    //  アクセスポイントモードで WiFi接続を試みる。
    void connectAP(void);

    //  アクセスポイントモードの SSIDを求める。
    //  return: const char* SSID
    const char* AP_SSID(void);

    //  アクセスポイントモードのパスワードを求める。
    //  return: const char* パスワード
    const char* AP_Password(void);

    //  SSID、パスワードを手動で設定する Webサーバの URLを求める。
    //  return: const char* URL、例 "http://netchime-050aff.local/"
    const char* webUrl(void);

    //  ステーションモードで接続完了時の処理を実行する。
    void doneConnecting(void);

    //  SSID、パスワードを手動で設定する Webサーバへの
    //  クライアント接続を処理する。
    void handleWebClient(void);

private:
    //  WiFiモード
    wifi_mode_t _wifi_mode;

    //  WiFiアクセスポイント関連
    char _ssid_buf[128];            /*SSIDバッファ*/
    char _pass_buf[128];            /*パスワードバッファ*/
    bool _ssidUpdated;              /*SSID、パスワードが更新された*/

    //  デバイス関連
    IPAddress _ipAddr;              /*IPアドレス*/
    uint8_t _macAddr[6];            /*MACアドレス*/

    //  アクセスポイントモード関連
    char _localDomain[15+1+6+1];    /*アクセスポイントモードでのローカルドメイン*/
    char _webURL[sizeof(_localDomain)+14];  /*http://<_localDomain>.local/*/
    char _ap_ssid_buf[128];         /*アクセスポイントモードでの SSIDバッファ*/
    char _ap_pass_buf[128];         /*アクセスポイントモードでのパスワードバッファ*/

    bool _index_html_set;           /*index.html処理関数をWebサーバにセットしたか?*/
    bool _not_found_set;            /*not found処理関数をWebサーバにセットしたか?*/

    //  接続待ち
    uint64_t _waitStartAt;          /*接続待ちを開始した CPU時刻(ms)*/
    bool _wpsGoing;                 /*WPSで接続試行中*/

    //  Preferences 不揮発性メモリ
    char _appName[15+1];            /*アプリ識別子*/
    Preferences _pref;

    //  デフォルトコンストラクタは無効
    WiFiConnect(void);
};

//  デフォルト index.htmlページ
void default_index_html(void);

//  デフォルト Not_Foundページ
void default_not_found(void);

#endif /*WIFICONNECT_H_INCLUDED*/

wificonnect.cpp

#include "wificonnect.h"

extern WebServer server;
extern WiFiConnect wificonnect;

//public:
//------------------------------------------------------------------------------
//  コンストラクタ
//      in: const cahr* app_name: アプリ識別子、15文字以下英数字と_、例 "wifi_connect"
WiFiConnect::WiFiConnect(const char* app_name) :
    _wifi_mode(WIFI_NONE), _ssidUpdated(false), _ipAddr({0,0,0,0}),
    _index_html_set(false), _not_found_set(false),
    _waitStartAt(0), _wpsGoing(false)
{
    //  アプリ識別子
    strncpy(_appName, app_name, sizeof(_appName));
    _appName[sizeof(_appName)-1] = '\0';

    //  SSIDバッファ、パスワードバッファ
    _ssid_buf[0] = '\0';
    _pass_buf[0] = '\0';

    //  アクセスポイントモードでの SSIDバッファ、パスワードバッファ
    _ap_ssid_buf[0] = '\0';
    _ap_pass_buf[0] = '\0';

    //  MACアドレスを求める。
    esp_read_mac(_macAddr, ESP_MAC_WIFI_STA);

    //  ローカルドメイン、Web URL
    snprintf(_localDomain, sizeof(_localDomain),
             "%s-%02x%02x%02x", _appName, _macAddr[3], _macAddr[4], _macAddr[5]);
    snprintf(_webURL, sizeof(_webURL),
             "http:\/\/%s.local\/", _localDomain);
    snprintf(_ap_ssid_buf, sizeof(_ap_ssid_buf),
             "%s-%02x%02x%02x", _appName, _macAddr[3], _macAddr[4], _macAddr[5]);
    snprintf(_ap_pass_buf, sizeof(_ap_pass_buf),
             "%02x%02x%02x%02x", _macAddr[0], _macAddr[1], _macAddr[2]);
}
//------------------------------------------------------------------------------
//  デストラクタ
WiFiConnect::~WiFiConnect(void) {}
//------------------------------------------------------------------------------
//  ローカルドメインを求める。
//  アクセスポイントモードで仮接続し、SSID、パスワードを
//  設定するための Webサイトのローカルドメイン。
//  mDNSで IPアドレスを得られる。
//  return: const char* ローカルドメイン文字列、".local"を含まない
const char* WiFiConnect::localDomain(void)
{
    return _localDomain; /*FIN*/
}
//------------------------------------------------------------------------------
//  ローカル IPアドレスを求める。
//  上記ローカルドメインの IPアドレス。
//  return: IPAddress ローカル IPアドレス
IPAddress WiFiConnect::ipAddr(void)
{
    return _ipAddr;  /*FIN*/
}
//------------------------------------------------------------------------------
//  SSIDをメモリに一時記憶する。
//      in: const char* ssid SSID
void WiFiConnect::setSSID(const char* ssid)
{
    strncpy(_ssid_buf, ssid, sizeof(_ssid_buf));
    _ssid_buf[sizeof(_ssid_buf)-1] = '\0';
}
//------------------------------------------------------------------------------
//  パスワードをメモリに一時記憶する。
//      in: const char* pass 上記 SSIDのパスワード
void WiFiConnect::setPassword(const char* pass)
{
    strncpy(_pass_buf, pass, sizeof(_pass_buf));
    _pass_buf[sizeof(_pass_buf)-1] = '\0';
}
//------------------------------------------------------------------------------
//  一時記憶した SSID、パスワードなどのパラメータを
//  不揮発性メモリに保存する。
void WiFiConnect::storeParams(void)
{
    //  Preferenceのアプリ専用領域を Read/Write モードで開く。
    _pref.begin(_appName, false);

    //  SSID、パスワードを書き込む。
    _pref.putString(SSID, _ssid_buf);
    _pref.putString(PASS, _pass_buf);
    _pref.putBool(SSID_PASS_UPDATED, true);

    //  Prefencesを閉じる。
    _pref.end();
}
//------------------------------------------------------------------------------
//  一時記憶した SSID、パスワードなどのパラメータを
//  忘れる。
void WiFiConnect::clearParams(void)
{
    //  Preferenceのアプリ専用領域を Read/Write モードで開く。
    _pref.begin(_appName, false);

    //  SSID、パスワードを消去する。
    _pref.putString(SSID, "");
    _pref.putString(PASS, "");
    _pref.putBool(SSID_PASS_UPDATED, false);

    //  Prefencesを閉じる。
    _pref.end();
}
//------------------------------------------------------------------------------
//  ステーションモードで WiFi接続を試みる。
void WiFiConnect::connectSTA(void)
{
    //  WiFiアクセスポイントの SSID、パスワードを更新したか?
    //  PrefencesをReadOnlyモードで開く。
    _pref.begin(_appName, true);
    _ssidUpdated = _pref.getBool(SSID_PASS_UPDATED, false);

    Serial.println("*** WiFiConnect::connectSTA");
    Serial.printf("  SSID_PASS_UPDATED: %s\n", _ssidUpdated ? "yes" : "no");

    if (_ssidUpdated) {
        //  --- SSID、パスワードは更新された。
        _pref.getString(SSID, _ssid_buf, sizeof(_ssid_buf));
        _pref.getString(PASS, _pass_buf, sizeof(_pass_buf));

        Serial.printf("  updated SSID: %s\n", _ssid_buf);
        Serial.printf("  updated PASS: %s\n", _pass_buf);

        WiFi.begin(_ssid_buf, _pass_buf);
    }
    else {
        //  --- SSID、パスワードは更新されていない。
        //  前回の設定で WiFi接続を試みる。
        Serial.println("  try to connect WiFi with previous settings.");

        WiFi.begin();
    }

    //  Prefencesを閉じる。
    _pref.end();

    //  WiFiモード:ステーションモード
    _wifi_mode = WIFI_STA;

    //  接続待ちを開始する。
    _waitStartAt = millis();
}
//------------------------------------------------------------------------------
//  WiFiモードを求める。
//  return: wifi_mode_t WiFiモード
WiFiConnect::wifi_mode_t WiFiConnect::mode(void)
{
    return _wifi_mode;  /*FIN*/
}
//------------------------------------------------------------------------------
//  WiFi接続できたかチェックする。
//  return: bool true 接続されている、false 未接続
bool WiFiConnect::isConnected(void)
{
    /*WiFi.status() は、アクセスポイントモードでは WL_CONNECTEDにならないらしい。*/
    bool connected = (WL_CONNECTED == WiFi.status());
    if (connected) {
        //  --- 接続されている
        _wpsGoing = false;

        //  IPアドレスを取得する。
        _ipAddr = WiFi.localIP();
        /*アクセスポイントモードでは WiFi.localIP() は 0.0.0.0 を返す。*/
    }

    return (connected || _wifi_mode == WIFI_AP);  /*FIN*/
}
//------------------------------------------------------------------------------
//  最後の WiFi接続の試行から指定時間経ったかチェックする。
//  return: bool true 指定時間経った、false 経っていない
//      in: uint32_t sec 指定時間(秒)
bool WiFiConnect::timeElapsed(uint32_t sec)
{
    return (millis() - _waitStartAt >= (uint64_t)sec * 1000);  /*FIN*/
}
//------------------------------------------------------------------------------
//  WPSが開始しているかチェックする。
//  return: true WPS接続を開始している、false 開始してない
bool WiFiConnect::isWPSgoing(void)
{
    return _wpsGoing;  /*FIN*/
}
//------------------------------------------------------------------------------
//  WPSで WiFi接続を試みる。
void WiFiConnect::connectWPS(void)
{
    Serial.println("*** WiFiConnect::connectWPS");

    _wpsGoing = true;

    WiFi.begin();
    WiFi.disconnect(false, false);  /*disconnect()しないとWPSが成功しない*/
    //  bool WiFi.disconnect(bool wifioff, bool ereseap)
    //  return: true 成功、false 失敗
    //      in: bool wifioff false ステーションモードを終了しない(default)
    //          bool eraseap false WiFi設定をクリアする(default)
    wpsConnect();
}
//------------------------------------------------------------------------------
//  WPSでの接続を中断する。
void WiFiConnect::stopWPS(void)
{
    wpsStop();
}
//------------------------------------------------------------------------------
//  SSID、パスワードを手動で設定する Webサーバの 
//  index.html処理関数を登録する。
//      in: void(*func)(void) 処理関数、nullptr なし
void WiFiConnect::webIndex(void(*func)(void))
{
    if (func) {
        server.on("/", func);            /*index.html*/
        server.on("/index.html", func);  /*index.html*/
    }
    else {
        server.on("/", default_index_html);            /*index.html*/
        server.on("/index.html", default_index_html);  /*index.html*/
    }
    _index_html_set = true;
}
//------------------------------------------------------------------------------
//  SSID、パスワードを手動で設定する Webサーバの 
//  ページが見つからないときの処理関数を登録する。
//      in: void(*func)(void) 処理関数、nullptr なし
void WiFiConnect::webNotFound(void(*func)(void))
{
    if (func) {
        server.onNotFound(func);  /*not found*/
    }
    else {
        server.onNotFound(default_not_found);  /*not found*/
    }
    _not_found_set = true;
}
//------------------------------------------------------------------------------
//  SSID、パスワードを手動で設定する Webサーバの 
//  URIに応じた処理関数を登録する。
//      in: char* uri ディレクトリ、例 "/about.html"
//          void(*func)(void) 処理関数、nullptr なし
void WiFiConnect::webPage(char* uri, void(*func)(void))
{
    if (func) {
        server.on(uri, func);
    }
}
//------------------------------------------------------------------------------
//  アクセスポイントモードで WiFi接続を試みる。
void WiFiConnect::connectAP(void)
{
    Serial.println("*** WiFiConnect::connectAP");

    //  index.html処理, not_found処理が登録されていなかったら、
    //  デフォルトを登録する。
    if (!_index_html_set) {
        webIndex();
    }
    if (!_not_found_set) {
        webNotFound();
    }

    //  念のため、WiFiを切断する。
    WiFi.begin();
    WiFi.disconnect(false, false);

    //  アクセスポイントモードで WiFiに接続する。
    WiFi.softAP(_ap_ssid_buf, _ap_pass_buf);
    _ipAddr = WiFi.softAPIP();
    delay(10);

    //  WiFiモード:アクセスポイントモード
    _wifi_mode = WIFI_AP;

    //  microDNSを起動する。
    MDNS.begin(_localDomain);

    Serial.printf("  mDNS: %s\n", _localDomain);
    Serial.printf("  IP Address: %s\n", _ipAddr.toString().c_str());

    //  Webサーバを起動する。
    server.begin();  /*web server start*/
}
//------------------------------------------------------------------------------
//  アクセスポイントモードの SSIDを求める。
//  return: const char* SSID
const char* WiFiConnect::AP_SSID(void)
{
    return _ap_ssid_buf;  /*FIN*/
}
//------------------------------------------------------------------------------
//  アクセスポイントモードのパスワードを求める。
//  return: const char* パスワード
const char* WiFiConnect::AP_Password(void)
{
    return _ap_pass_buf;  /*FIN*/
}
//------------------------------------------------------------------------------
//  SSID、パスワードを手動で設定する Webサーバの URLを求める。
//  return: const char* URL、例 "http://netchime-050aff.local/"
const char* WiFiConnect::webUrl(void)
{
    return _webURL;  /*FIN*/
}
//------------------------------------------------------------------------------
//  ステーションモードで接続完了時の処理を実行する。
void WiFiConnect::doneConnecting(void)
{
    //  SSID、パスワードが更新されていたら、更新なしに戻す。
    if (_ssidUpdated) {
        _ssidUpdated = false;
        clearParams();

        Serial.println("  SSID, PASS cleared.");
    }
}
//------------------------------------------------------------------------------
//  SSID、パスワードを手動で設定する Webサーバへの
//  クライアント接続を処理する。
void WiFiConnect::handleWebClient(void)
{
    server.handleClient();
}
//------------------------------------------------------------------------------
//private:
//------------------------------------------------------------------------------
//  デフォルトコンストラクタは無効
WiFiConnect::WiFiConnect(void) {}
//------------------------------------------------------------------------------
/*end of class WiFiConnect*/
//------------------------------------------------------------------------------
//  デフォルト index.htmlページ
void default_index_html(void)
{
    char buf[1000];
    String ssid = "";
    String pass = "";
    String flag = "";

/*0.1.1*/
    //  POSTパラメータを解釈する。
    if (HTTP_POST == server.method()) {
        //  --- POSTメソッド
        ssid = server.arg("ssid");
        pass = server.arg("pass");
        flag = server.arg("flag");  /*hidden parameter*/

        //  前後の空白を取り除く。
        ssid.trim();
        pass.trim();
        flag.trim();
    }

    //  flagがセットされていないときは、初回アクセス。
    if (0 == ssid.length() || 0 == pass.length() || 0 == flag.length()) {
        //  入力欄を表示する。
        snprintf(buf, sizeof(buf), 
            "<html lang=\"ja\">"
            "<head>"
                "<meta charset=\"utf-8\">"
                "<title>WiFi設定</title>"
            "</head>"
            "<body>"
                "<h1>WiFi設定</h1>"
                "<p>WiFiアクセスポイントのSSIDとパスワードをセットしてください。</p>"
                "<form method=\"POST\" action=\"/index.html\">"
                    "<div>SSID:<input type=\"text\" name=\"ssid\" size=\"40\" maxlength=\"64\">%s</div>"
                    "<div>PASSWORD:<input type=\"text\" name=\"pass\" size=\"40\" maxlength=\"64\">%s</div>"
                    "<div><button type=\"reset\">クリア</button>"
                         "<button type=\"submit\">送信</button></div>"
                    "<input type=\"hidden\" name=\"flag\" value=\"1\">"
                "</form>"
            "</body>"
            "</html>",
            ssid.c_str(), pass.c_str()
        );
        buf[sizeof(buf)-1] = '\0';
    }
    else if (0 == flag.compareTo("1")) {
        //  SSID、パスワードをメモリに一時記憶する。
        wificonnect.setSSID(ssid.c_str());
        wificonnect.setPassword(pass.c_str());

        snprintf(buf, sizeof(buf), 
            "<html lang=\"ja\">"
            "<head>"
                "<meta charset=\"utf-8\">"
                "<title>WiFi設定の確認</title>"
            "</head>"
            "<body>"
                "<h1>WiFi設定の確認</h1>"
                "<p>WiFiアクセスポイントのSSIDとパスワードを確認してください。</p>"
                "<p>正しければ[再起動]ボタンを押してください。<br />"
                   "デバイスを再起動し、WiFiアクセスポイントに接続を開始します。</p>"
                "<p>SSID:%s<br />"
                   "パスワード:%s</p>"
                "<form method=\"POST\" action=\"/index.html\">"
                    "<div><button type=\"submit\">再起動</button></div>"
                    "<input type=\"hidden\" name=\"ssid\" value=\"%s\">"
                    "<input type=\"hidden\" name=\"pass\" value=\"%s\">"
                    "<input type=\"hidden\" name=\"flag\" value=\"999\">"
                "</form>"
            "</body>"
            "</html>",
            ssid.c_str(), pass.c_str(),
            ssid.c_str(), pass.c_str()
        );
        buf[sizeof(buf)-1] = '\0';
    }
    else if (0 == flag.compareTo("999")) {
        wificonnect.storeParams();

        //  リスタートする。
        ESP.restart();
    }

    server.send(200, "text/html", buf);
}
//------------------------------------------------------------------------------
//  デフォルト Not_Foundページ
void default_not_found(void)
{
    server.send(404, "text/plain", "File not found.\n\n");
}
  • wpcConnector.h、wpcConnector.cpp は、@coppercele(Moke Nakamura)さんの「ESP32(M5StickC)でWPSを使うと設定ファイルも必要ないし便利ですよ」に掲載されたコードをそのまま使わせていただきました。ありがとうございます。

結果

(1)WPS で接続

前回の SSID、パスワードで WiFi 接続を試みますが、10秒以内に接続できないときは WPS で接続を待機します。(写真1)

写真1 WPS モードで待機中

この状態で WiFi ルータの WPS ボタンを押して、M5StickC を接続することができます。

WPS がうまくいかないときや WiFi ルータのボタンを押せないときは、M5StickC の上ボタン(押しボタンA)を押します。

(2)アクセスポイント(AP)モードで Webサーバを立てる

WPS モードを終了し、アクセスポイント(AP)モードで WiFi を再起動し、WiFi ルータの SSID、パスワードを入力するための Web サーバを立てます。(写真2)

写真2 AP モードで Web サーバを立てる

スマートフォンの設定で、M5StickC のディスプレイに表示された SSID のアクセスポイントを探し、表示されたパスワードを入力して、スマートフォンを M5StickC に接続します。これらの SSID、パスワード、ローカルドメイン名は M5StickC の MAC アドレスから自動的に生成します。

スマートフォンを上記の SSID に接続できたら、スマートフォンのカメラで URL の QRコードを撮影します。

ブラウザが起動し、M5StickC 上のローカルドメインにアクセスします。本来の目的である WiFi ルータに接続するための SSID、パスワードを入力する画面が表示されるはずです。(図1)

図1 WiFi ルータの設定(スマートフォンのブラウザ)

WiFi ルータの SSID、パスワードを入力し、[送信]を押すと、M5StickC の不揮発性メモリにいったん保存し、M5StickC をリスタートします。

(3)WiFi ルータに接続

不揮発性メモリの SSID、パスワードで WiFi 接続を試み、接続に成功すると写真3 のようになります。

この時点で、スマートフォンの WiFi は切断されますので、必要に応じてスマートフォンの WiFi 設定を元に戻してください。

成功したときの SSID、パスワードは M5StickC に記憶されますので、次回の電源オンでは直ちに WiFi ルータに接続できるはずです。

写真3 の状態で、上ボタン(押しボタンA)を押すと、不揮発性メモリを全消去するので、また最初からやり直しになります。

写真3 WiFi ルータに接続成功

スケッチの動作は、M5StickC、M5StickC Plus、M5Stack Gray で確認しました。

注意

  • WiFi 接続機能だけで、フラッシュメモリの大半(OTA の場合 60%以上)を占有します。

参考

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

  • mDNS、Web サーバは Arduino-IDE M5Stack のライブラリの examples を参照。ライブラリは下記のフォルダにあります。
    C:\Users\<ユーザ>\AppData\Local\Arduino15\packages\m5stack\hardware\esp32\2.0.7\libraries\

ライセンス

このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。


この記事が気に入ったらサポートをしてみませんか?