M5StickC Wi-Fiパスワード入力補助＆複数アクセスポイント接続ツール

## M5StickCのWi-Fiを柔軟に設定したい
M5StickCでWi-Fi接続する場合、プログラム中にSSIDとパスワードを記述するのが一般的だと思います。しかしそうすると別のアクセスポイントに接続する度に、プログラムを書き換えなければなりません。自宅や仕事場、宿泊先の東横インでもWi-Fi接続したい！

ということで、プログラムを修正しなくても、M5StickC単体でWi-Fiの設定ができ、且つ複数のアクセスポイントに接続できるようなプログラムを作ってみました。ボタンが少ないM5StickCでは文字の入力が大変なため、IMU(6軸センサー)を利用して、本体を傾けて文字を入力するインターフェースとなってます。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">今度はM5StickCの6軸センサーを使って、本体を傾けてカーソル移動するようにしてみた。使える機能は活用しなくちゃね。 <a href="https://twitter.com/hashtag/M5StickC?src=hash&amp;ref_src=twsrc%5Etfw">#M5StickC</a> <a href="https://t.co/Crdi6t30jU">pic.twitter.com/Crdi6t30jU</a></p>&mdash; Kaz (@Kaz_Macintosh) <a href="https://twitter.com/Kaz_Macintosh/status/1148892170444627968?ref_src=twsrc%5Etfw">2019年7月10日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 設定できるアクセスポイントの数
本ツールで設定できるアクセスポイントは、M5StickC単体で設定できるSSIDが1つ、プログラム中に記述できるSSIDはいくつでも可能です。この設定した中から最も電波強度が強いアクセスポイントに優先的に接続する仕組みになっています。

## パスワード設定モード
M5StickCの起動時にHOMEボタン（M5マーク）を押すと、パスワード設定モードになります。ここで接続したいSSIDを選択し、パスワードを設定すると内部の仮想EEPROMに保存され、次回以降は自動的に接続できるようになります。

(1) ホームボタンを押しながら電源を入れる。
(2) 周辺にあるアクセスポイントが表示されるので、
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129769/9939cf77-0276-1df2-113f-b3373a5b43c5.jpeg)
RSTボタン（ディスプレイ横）　で次のSSIDを選択
HOMEボタン　で決定

(3) 続いてパスワードを入力します。
本体を左右に傾けると、カーソルが左右に移動します。角度を急にすると速く移動します。
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129769/1e15e839-1478-7981-d236-d2a9e2a5ecb7.jpeg)
HOMEボタン　でカーソルの文字を入力、
RSTボタン　で文字種が変わります。

(4) 何度かRSTボタンを押すと Delete / Finish の選択画面が出ます。
Delete　は1文字削除
Finish　で決定です。
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129769/2b476607-b3d0-bc71-b7c4-6ef42b2d91ad.jpeg)

(5) この後は通常の接続モードに移ります。

## 接続モード
周辺にあるアクセスポイントをスキャンし、設定してあるSSIDを順にアクセスしていきます。その際に最も電波強度が強いアクセスポイントへ1番最初に接続を試みます。接続できなければ次のSSIDへと順に進んでいきます。
![4のコピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129769/e4186097-97fe-e85f-d12d-c83d9443d248.jpeg)

## プログラム
```c:autowifi_sample.ino
// M5StickC Wi-Fiパスワード入力補助ツール & 複数AP自動選択  by Kaz
// https://qiita.com/Kaz_Macintosh/items/78bf142c37845d3ab3e6
#include <M5StickC.h>
#include "WiFi.h"

void setup(){
  // 初期化
  M5.begin();
  Serial.begin(115200);
  M5.IMU.Init();

  // I/O設定
  pinMode(M5_BUTTON_HOME, INPUT);
  pinMode(M5_BUTTON_RST, INPUT);

  // Wi-Fiセレクトモードの判定（起動時ホームボタンONで有効）
  bool wifi_select_mode = (digitalRead(M5_BUTTON_HOME) == LOW) ? true : false;

  // Wi-Fi接続（Wi-Fiパスワード入力補助ツール & 複数AP自動選択）
  autowifi(wifi_select_mode);

}

void loop() {
  M5.update();
  Serial.println("Hello, World!");
  delay(1000);
}
```
```c:autowifi.ino
// M5StickC Wi-Fiパスワード入力補助ツール & 複数AP自動選択  by Kaz
// https://qiita.com/Kaz_Macintosh/items/78bf142c37845d3ab3e6

// Wi-Fi関係 --------
#define    WIFI_APS  3  //接続対象のAPの数
const char WIFI_SSID[WIFI_APS][32] = { "aa", "bb", "cc" }; //SSID
const char WIFI_PASS[WIFI_APS][32] = { "11", "!!", "!!" }; //パスワード

// EEPROM関係 --------
#include <EEPROM.h>
#define EEPROM_ADDR_SSID 0    // EEPROMの先頭アドレス SSID 0 (〜31)
#define EEPROM_ADDR_PASS 32   // EEPROMの先頭アドレス PASS 32 (〜64）

// IMU(6軸センサー)関係 --------
#define GYRO_accY_CENTER  0.14  // 水平時のaccYの値
#define GYRO_accY_LEFT   -0.84  // 左に90°傾けた時のaccYの値
#define GYRO_accY_RIGHT   1.16  // 右に90°傾けた時のaccYの値

// その他 --------
#define _sp(x) Serial.println(x)
#define _spn(x) Serial.print(x)

// M5StickC Wi-Fi 複数AP接続＆任意AP接続ツール
void autowifi (bool wifi_select_mode) {
  int i;

  //ディスプレイ＆フォント設定
  M5.Lcd.setRotation(1);  // 横向き・ホームボタンが右
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextFont(1);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(2, 2);
  M5.Lcd.println( (wifi_select_mode) ? "Wi-Fi Setting Mode" : "Wi-Fi Scanning..." );

  // Wi-Fi初期設定
  WiFi.mode(WIFI_STA);  // ステーションモード
  WiFi.disconnect();
  delay(100);
  WiFi.printDiag(Serial);
  int apnum = WiFi.scanNetworks();  // 周辺のSSIDをスキャン

  // Wi-Fi通常接続
  //WiFi.begin("ssid", "pw");  //  Wi-Fi APに接続
  //while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
  //  _spn(".");
  //  delay(100);
  //}

  // Wi-Fiセレクトモード（起動時ホームボタンONのときに実行）
  if (wifi_select_mode) {
    int btn, dir;

    // [1] SSIDの選択画面
    int seled_ssid = -1;
    while (seled_ssid == -1) {
      for (i = 0; i < apnum; i++) {
        M5.Lcd.fillRect(0,20, 160,20, TFT_BLACK);
        M5.Lcd.drawString("SSID: "+WiFi.SSID(i), 2,22);
        M5.Lcd.drawString("[RST] next / [HOME] YES", 2,42);
        btn = _autowifi_get_button(true); //ボタンが押されるまで待機
        if (btn == M5_BUTTON_HOME) {  //決定
          seled_ssid = i;
          break;
        } else if (btn == M5_BUTTON_RST) {  //次へ
          continue;
        }
        delay(50);
      }
    }  

    // [2] パスワードの入力画面
    int bank = 0; //選択中の文字列バンク
    int cur1 = 0; //1文字目表示位置のオフセット
    int cur2 = 0; //カーソルの位置bank=0-3
    int cur3 = 0; //カーソルの位置bank=4
    int inpwpos = 0;
    int pwcur = 0;
    char inpasswd[32] = ""; //入力したパスワード
    const int vnum = 13;    //画面に表示する文字数
    const int banknum = 4;  //バンク数
    const int mojinum = 33; //1バンクの文字数
    const char moji[4][(mojinum+1)] = {
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ      *",
      "abcdefghijklmnopqrstuvwxyz      *",
      "012345678901234567890123456789  *",
      "-_/.!\"!#$%&'()*+,;:<=>?@[\\]^`{|}~"
    };
    const int ly1 = 29;
    const int ly2 = 59;
    int lx;
    char lc;
    int pwok = false;
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.drawString(WiFi.SSID(seled_ssid)+" password", 2,2);
    M5.Lcd.setTextFont(2);
    while (! pwok) {
      //入力済みパスワードの表示
      M5.Lcd.fillRect(0,ly1, 160,20, TFT_BLACK);
      M5.Lcd.setTextColor(TFT_YELLOW);
      M5.Lcd.drawString(inpasswd, 4, ly1);
      M5.Lcd.setTextColor(TFT_WHITE);
      pwcur = strlen(inpasswd);
      //文字選択バーの表示
      M5.Lcd.fillRect(0,ly2, 160,20, TFT_BLACK);
      if(bank < banknum) { //bank=0-3
        //カーソル
        lx = cur2 * 12;
        M5.Lcd.fillRect(lx,ly2, 10,18, TFT_BLUE);
        //文字列
        for (i=0; i<vnum; i++) {
          lx = i * 12 + 2;
          lc = moji[bank][(cur1+i)];
          M5.Lcd.drawString(String(lc), lx+1, ly2+1);
        }
      } else if(bank == banknum) { //bank=4
        //カーソル
        lx = cur3 * 50;
        M5.Lcd.fillRect(lx,ly2, 42,18, TFT_BLUE);
        //文字列
        M5.Lcd.drawString("Delete  Finish", 2, ly2+1);
      }
      //入力判定（ボタン、本体傾き）
      int degree, addi = 1;
      float accel = 0;
      do {
        btn = -1; //押されたボタン -1=なし
        dir = 0;  //本体傾き 0=水平
        degree = _get_imu_y_degree(); //本体の角度Yを求める
        if (degree < -35) accel -= 0.3;
        else if (degree < -15) accel -= 0.1;
        else if (degree > 35) accel += 0.3;
        else if (degree > 15) accel += 0.1;
        if (accel <= -1) {  //左傾き
          dir = -1;
          accel = 0;
        } else if (accel >= 1) {  //右傾き
          dir = 1;
          accel = 0;
        }
        btn = _autowifi_get_button(false);  //ボタンの状態を取得
        //_sp("degree="+String(degree)+" accel="+String(accel)+" btn="+String(btn)+" bank="+String(bank)+" cur1="+String(cur1)+" cur2="+String(cur2)+" cur3="+String(cur3));
        if(dir == 1) {  //右傾き　＝　右移動
          if(bank < banknum) {
            if (cur2 < (vnum-1)) {
              cur2 += addi;
              if(cur2 > (vnum-1)) cur2 = vnum - 1;
              break;
            } else {
              if (cur1 < (mojinum-vnum)) {
                cur1 += addi;
                if(cur1 > (mojinum-vnum)) cur1 = mojinum - vnum;
                break;
              }
            }
          }
        } else if(dir == -1) {  //左傾き　＝　左移動
          if(bank < banknum) {
            if (cur2 > 0) {
              cur2 -= addi;
              if (cur2 < 0) cur2 = 0;
              break;
            } else {
              if (cur1 > 0) {
                cur1 -= addi;
                if (cur1 < 0) cur1 = 0;
                break;
              }
            }
          }
        } else if(btn == M5_BUTTON_RST) {  //RSTボタン　＝　バンク切り替え
          if (bank < banknum) {
            bank ++;
            cur3 = 0;
          } else if (bank == banknum) {
            if (cur3 < 1) {
              cur3 ++;
            } else {
              bank = 0;
              cur3 = 0;
            }
          }
        } else if(btn == M5_BUTTON_HOME) {  //HOMEボタン　＝　決定
          if(bank < banknum) { //bank=0-3
            inpasswd[pwcur] = moji[bank][(cur1+cur2)];
            inpasswd[pwcur+1] = '\0';
            pwcur ++;
          } else {  //bank=4
            if(cur3 == 0) { //Delete
              if(pwcur > 0) {
                pwcur --;
                inpasswd[pwcur] = '\0';
              }
            } else if(cur3 == 1) { //Finish
              pwok = true;
            }
          }
        }
        delay(50);
      } while (btn == -1 && dir == 0);
    }

    // [3] EEPROMにパスワードを保存する
    char ssidtmp[32];
    strcpy(ssidtmp, WiFi.SSID(seled_ssid).c_str());
    EEPROM.begin(64);
    EEPROM.put(EEPROM_ADDR_SSID, ssidtmp);
    EEPROM.put(EEPROM_ADDR_PASS, inpasswd);
    EEPROM.commit();
    EEPROM.end();
    _sp("Save to EEPROM. ssid="+WiFi.SSID(seled_ssid)+" pass="+String(inpasswd));

    // [4] 終了
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(TFT_WHITE);
    M5.Lcd.setTextFont(1);
    M5.Lcd.setTextSize(1);
  }
  // Wi-Fiセレクトモードここまで

  // EEPROMに保存したSSID/PASSの取得
  char eeprom_ssid[32];
  char eeprom_pass[32];
  EEPROM.begin(64);
  EEPROM.get(EEPROM_ADDR_SSID, eeprom_ssid);
  EEPROM.get(EEPROM_ADDR_PASS, eeprom_pass);
  EEPROM.end();
  _sp("EEPROM LOAD: eeprom_ssid="+String(eeprom_ssid)+" eeprom_pass="+String(eeprom_pass));

  // EEPROM 初期書き込みTEST
  //if((int)eeprom_ssid[0] == 0) {
  //  EEPROM.begin(64);
  //  EEPROM.put(EEPROM_ADDR_SSID, "!!");
  //  EEPROM.put(EEPROM_ADDR_PASS, "!!");
  //  EEPROM.commit();
  //  EEPROM.end();
  //}

  // WiFiスキャン（一番電波が強力なAPを抽出する）
  int ap;
  int rssi_max = -999;
  int ap_hit = -1;
  for (i = 0; i < apnum; i++) {
    _sp("WiFi(" + String(i) + ") " + WiFi.SSID(i) + " : " + String(WiFi.RSSI(i)));
    for (ap = 0; ap <= WIFI_APS; ap++) { // ap=0 EEPROMのSSID、ap1〜n WIFI_SSID[0〜m]
      if ((ap==0 && WiFi.SSID(i) == eeprom_ssid) || (ap>0 && WiFi.SSID(i) == WIFI_SSID[ap-1])) {
        _sp("hit " + WiFi.SSID(i));
        if (rssi_max < WiFi.RSSI(i)) {
          rssi_max = WiFi.RSSI(i);
          ap_hit = ap;
        }
        break;
      }
    }
  }
  _sp("ap_hit=" + String(ap_hit));
  _sp("rssi_max=" + String(rssi_max));
  if (ap_hit >= 0) {
    M5.Lcd.println("Nearby AP is AP" + String(ap_hit + 1));
  }

  // Wi-Fi接続スタート（APに接続するまで順に繰り返す）
  int stat, ap_conn;
  while (true) {
    for (ap = -1; ap <= WIFI_APS; ap++) { //まず初めに一番電波が強いAPを試し、以降順番通りに接続
      if (ap == -1) {
        if (ap_hit < 0) continue;
        ap_conn = ap_hit;
      } else {
        if (ap == ap_hit) continue;
        ap_conn = ap;
      }
      if(ap_conn == 0) {
        WiFi.begin(eeprom_ssid, eeprom_pass);
      } else {
        WiFi.begin(WIFI_SSID[ap_conn-1], WIFI_PASS[ap_conn-1]);
      }
      M5.Lcd.print("AP" + String(ap_conn + 1) + " connecting...");
      _sp("\n\nNow WiFi connecting AP" + String(ap_conn + 1) + " " + String( (ap_conn==0) ? eeprom_ssid : WIFI_SSID[ap_conn-1] ));

      for (i = 0; i < 150; i++) {
        stat = WiFi.status();
        _spn(stat);
        if (stat == WL_DISCONNECTED) { //6
          _spn(".");
          if (i == 149) {
            _sp("abort");
            M5.Lcd.println("");
          }
        } else if (stat == WL_CONNECT_FAILED) { //2  (stat == WL_NO_SSID_AVAIL)//1
          _sp(" err=" + String(stat));
          _sp("Failed. connect to another AP");
          M5.Lcd.println("<<FAIL>>");
          break;  // break to for(i)
        } else if (stat == WL_CONNECTED) { //3
          _sp("\nWiFi connected!");
          _spn("IP address: ");
          _sp(_ipaddress_to_string(WiFi.localIP()));
          M5.Lcd.println("<<OK>> connected!");
          M5.Lcd.println("IP= " + _ipaddress_to_string(WiFi.localIP()));
          break;
        }
        delay(200);
      }
      if (stat == WL_CONNECTED) {  //接続成功 //3
        break;
      } else {  //接続失敗
        WiFi.disconnect();
        delay(1000);
      }
    }
    if (stat == WL_CONNECTED) break;
    _sp("\nCannot connect");
    M5.Lcd.println("Cannot connect. retry");
    delay(5000);
  }
  delay(3000);

  //ディスプレイ＆フォント設定
  M5.Lcd.setRotation(0);  // 縦向き・ホームボタンが下
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextFont(1);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0,0);
}

// IPアドレスをドット区切りにして返す
String _ipaddress_to_string(IPAddress address) {
 return String(address[0]) + "." + 
        String(address[1]) + "." + 
        String(address[2]) + "." + 
        String(address[3]);
}

// 押したボタンを返す（true=押されるまで待機、false=今の状態を返すのみ）
int _autowifi_get_button (bool waitpush) {
  int btn = -1;
  int io[2] = { M5_BUTTON_HOME, M5_BUTTON_RST };

  while (btn == -1) {
    for (int i=0; i<2; i++) {
      if (digitalRead(io[i]) == LOW) {
        while(digitalRead(io[i]) == LOW);
        btn = io[i];
      }
    }
    if(! waitpush) break;
    delay(10);
  }
  return btn;
}

// IMUのY軸の角度を求める（これが正しいやり方かよくわかってない）
float _get_imu_y_degree () {
  int16_t accX, accY, accZ;
  float degree = 0;
  float y, max_l, max_r;

  M5.IMU.getAccelAdc(&accX, &accY, &accZ);
  y = ((float) accY) * M5.IMU.aRes - GYRO_accY_CENTER;
  max_l = GYRO_accY_LEFT  - GYRO_accY_CENTER;
  max_r = GYRO_accY_RIGHT - GYRO_accY_CENTER;
  degree = (y < GYRO_accY_CENTER) ? (y / max_l) * -90 : (y / max_r) * 90;
  //_sp("y="+String(y)+" degree="+String(degree));
  return degree;
}
```

## 設定内容
autowifi_sample.ino にWi-FiのSSID/パスワードを設定します。複数のアクセスポイントを指定できます。以下は3つ設定した場合の例です。SSID/パスワードともに、31文字以内を想定しています。

```c:
#define    WIFI_APS  3  //接続対象のAPの数
const char WIFI_SSID[WIFI_APS][32] = { "aa", "bb", "cc" }; //SSID
const char WIFI_PASS[WIFI_APS][32] = { "11", "!!", "!!" }; //パスワード
```
その他EEPROMのアドレス設定などもありますので、必要に応じて変更してください。


追記
これを書いた翌日に WiFiMulti なるものを知ってしまった。。。