M5Stick-Cを使ってSORACOMボタンのWiFi版を作ってみた

##はじめに##
　別記事でSORACOM LTE-M Button Plusを福祉機器に使った話を書きましたが、個人の家ではLTE-Mの方が便利な場合が多いんですけど、施設などでWiFiが入っている場合もあるにはあって、この場合はLTE-Mの回線代が安いとはいえ重荷になる場合があります。そこで、SORACOM LTE-M Button Plusと互換のWiFiボタンをM5Stick-Cで作ってみました。
##構成##
 I/F的にSORACOM LTE-M Button Plusと同じにするために、M5StickCのGROVEポートに3.5Φステレオジャックを取り付けます。
SORACOM LTE-M Button PlusのボタンはM5StickCのメインボタンに機能を割り当てます。
　SORACOM LTE-M Button Plus側の回路が不明なので、厳密に同じわけではないのですが、実測で端子間に3Vかかってますので、プルアップの入力でいいと思います。入力ポートはG32です。![IMG_20191218_095114.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33661/183e35df-235e-9ebe-b1e5-a2d9a3962658.jpeg)


##動作##
 動きとしては以下のようにしたいと思います。
初期時（WiFi未設定）
・APモードで起動
　M5StickCのWebサーバー（192.168.4.1)に接続して、WiFiのSSID、パスワード設定
WiFi設定後
・WiFiクライアントモードで起動
・ボタン押し（短押し、2度押し、長押し）の検出
・ボタン押しの検出でサーバーのWebAPIをコール

　コールするAPIはSORACOMボタンと合わせるため、BeamsのWebHookで設定しているURLと同じにします。

　通常時はスリープ状態にして、ボタンでスリープ起動させたかったのですが、今回は時間の都合で入れていません（バッテリー動作の場合は必要）。
##プログラム##
　WiFiの設定を行う部分はArduino IDEのM5Stackのサンプルスケッチ Advanced - WiFiSettingをそのまま使いました。ESP32では[WiFiManager](https://github.com/zhouhan0126/WIFIMANAGER-ESP32)というライブラリを使うとコードが少なくて済みますが、M5SttickCでコンパイルするとエラーになったので、こちらを使いました。
　ただ、そのままだとWebページで表示される文字が英語なので日本後の表示に少し変更しています。
(サンプルを別名でコピーする場合、サブディレクトリの /detailもコピーしないとエラーになります)

**変更点**
M5StickCの場合はincludeを書き換えます。あと、HTTPClient.hのincludeを加えます

```
#include <M5StickC.h>　　　//変更
#include <HTTPClient.h>　　//追加
```
makePage()

```
String makePage(String title, String contents) {
  String s = "<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>";
  s += "<meta name=\"viewport\" content=\"width=device-width,user-scalable=0\">";
  s += "<title>";
  s += title;
  s += "</title></head><body>";
  s += contents;
  s += "</body></html>";
  return s;
}
```
日本語を表示できるようにHTMLのheaderを変更します。

```
  String s = "<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>";
```
　HTMLの表示部分の英語を日本語に変更します。
　今回はWiFi設定の他に、サーバーログイン情報も登録する部分も追加します。

```
void startWebServer() {
  if (settingMode) {
    //WebAP mode IPアドレス
    M5.Lcd.println("Starting Web Server at");
    M5.Lcd.println(WiFi.softAPIP());
    webServer.on("/setap", []() {
      String ssid = urlDecode(webServer.arg("ssid"));
      M5.Lcd.println("SSID: ");
      M5.Lcd.println(ssid);
      String pass = urlDecode(webServer.arg("pass"));
      M5.Lcd.println("Writing SSID to EEPROM...");

      //WiFi設定の保存
      M5.Lcd.println("Writing Password to nvr...");
      preferences.putString("WIFI_SSID", ssid);
      preferences.putString("WIFI_PASSWD", pass);

      //WiFiボタン設定の保存
      userid = urlDecode(webServer.arg("userid"));
      button_pass = urlDecode(webServer.arg("password"));
      preferences.putString("USERID", userid);
      preferences.putString("PASSWD", button_pass);
      
      M5.Lcd.println("Write nvr done!");
      String s = "<h1>WiFi設定終了</h1><p>デバイスリセット後\"";
      s += ssid;
      s += "\" に接続します";
      webServer.send(200, "text/html", makePage("WiFi設定", s));
      delay(3000);
      //M5Stick-C再起動
      ESP.restart();
    });
    webServer.onNotFound([]() {
      String s = "<h1>WiFiボタン　WiFi設定</h1><p>WiFiのSSID,パスワード入力</p>";
      s += "<form method=\"get\" action=\"setap\"><label>SSID: </label><select name=\"ssid\">";
      s += ssidList;
      s += "</select><br>Password: <input name=\"pass\" length=64 type=\"password\">";
      s += "<br><br>userid: <input name=\"userid\" length=64 type=\"text\">";
      s += "<br>pass: <input name=\"password\" length=64 type=\"password\"><br><input type=\"submit\"></form>";
      webServer.send(200, "text/html", makePage("Wi-Fi設定", s));
    });
  }
  webServer.begin();
}
```

WiFiのセットアップはスマホでWiFi　SSID「WiWiButton_SETUP」に接続し、Webブラウザから192.168.4.1にアクセスします。
![device-2019-12-18-204504.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33661/f0d88e6e-76af-de15-fdd9-68449425208e.png)
 サンプルはWiFi設定後の再起動でもAPモードで起動するようになってますが、今回はクライアントで動作させるのでsetupの部分を変更します。

```
String userid = "";
String button_pass = "";
int type = 1;
int state = 0;
HTTPClient http;
int tp;

void setup() {
  m5.begin();
 
  pinMode(SW_PORT, INPUT_PULLUP);
  cktime = 0;
  btnst = 0;
  preferences.begin("wifi-config");

  delay(10);
  if (restoreConfig()) {
    //if (checkConnection()) {
      //WiFi設定済みの場合
      settingMode = false;      //WiFi設定モードOFF
      //startWebServer();
      return;
    //}
  }
  settingMode = true;           //WiFi設定モードON
  setupMode();
}

void loop() {
  if (settingMode) {
    webServer.handleClient();
  }else{
    switch_check();             //外部スイッチ検出処理
  }
}
```
**ボタン検出**
WiFi設定後のモードでSORACOMボタンと同じスイッチ動作を追加します。

```
//ボタン検出
int btnst;
unsigned long cktime;
unsigned long now;
unsigned long ck;
int cnt = 0;
int swBack = 0;
const int SW_PORT = 32;
//--------------------------------------------------------
//SORACOM ボタン等価の動作
//--------------------------------------------------------

void switch_check(){
  now = millis();
  if(cktime > 0){
    ck = now - cktime;
  }
  //外部スイッチ
  int sw = digitalRead(SW_PORT); 
  if(digitalRead(M5_BUTTON_HOME) == LOW || sw == LOW){
    if(btnst == 0){
      btnst = 1;
      cktime = millis();
      //Serial.print("ON ");
      ck = 0;
    }
    else if(btnst == 2){
      //Serial.print("ON DOUBLE");
      btnst = 5;
    }
  }else{
    if(btnst == 1){
      //SHORT
      //Serial.println("OFF");
      btnst = 2;
    }else if(btnst == 4){
      //Serial.println("OFF LONG");
      //LONG
      btnst = 0;
    }else if(btnst == 5){
      //Serial.println("OFF DOUBLE");
      btnst = 6;
      //DOUBLE
    }
  }
  
  if(btnst != 0 && ck > 1200){
    //Serial.println(btnst);
    if(btnst == 1){
      //LONG
      btnst = 4;
      state = 3;
      delay(200);
    }else if(btnst == 2){
      //SHORT
      state = 1;
      btnst = 0;
    }else if(btnst == 6){
      //DOUBLE
      btnst = 0;
      state = 2;
    }
  }
  if(digitalRead(M5_BUTTON_HOME) == LOW && digitalRead(M5_BUTTON_RST) == LOW){
    if(btnst != 5){
      cktime = millis();
      btnst = 5;
    }else if(btnst ==5 && ck > 2000){  //2秒長押し
      //WiFi設定消去   
      preferences.remove("WIFI_SSID");
      preferences.remove("WIFI_PASSWD");
      preferences.remove("USERID");
      preferences.remove("PASSWD");
      delay(3000);
      ESP.restart();
    }
  }else{
    if(btnst == 5){
      btnst = 0;
    }
  }
  
  //送信
  if(state != 0){
    tp = state;
    state = 0;
    cktime = 0;
    ck = 0;

    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("Connecting");

    //WiFi接続
    M5.Lcd.println(wifi_ssid);
    M5.Lcd.println(wifi_password);
    
    WiFi.begin(wifi_ssid.c_str(),wifi_password.c_str());
    delay(200);
    while(WiFi.status() != WL_CONNECTED){
      delay(1000);
      M5.Lcd.print(".");
    }
    M5.Lcd.println("connected!");

    String type = String(tp);
    //WebAPIのURL
    String url = "https:/XXXXXXXX?id=" + userid + "&button_pass=" + type + "&type=" + type;

    http.begin(url);
    int st = http.GET();
    if(st > 0){
      M5.Lcd.println("");
      if(st == 200){
        String body = http.getString();
        M5.Lcd.println(body);
      }else{
        M5.Lcd.println(http.errorToString(st));
      }
    }
    http.end();
  }
}
```
##まとめ##
これで、サーバー側を同じにしてSORACOMボタンとWiFiボタンを混在して使えるようになりました。
セキュリティ的にはWiFiルータにSORACOM AirのSIMを挿してSORACOM Beamsを経由させたほうが、正解だと思います。
複数のスイッチが必要な時には、WiFiボタンをまとめてSORACOMに接続するような使い方もできますね。
SIM対応WiFiルーター　Aterm HT100LN
https://www.aterm.jp/product/atermstation/product/mobile/ht100ln/