はじめに

M5StickCを任意のWiFiに接続し,計測された加速度から求めた合成加速度と,合成加速度を高速フーリエ変換してもとめた周波数(5Hzでカットオフ)を,クラウドに送信し,その結果をリアルタイムで可視化します。

準備

M5StickCを充電してください。
フル充電で連続45分程度の測定が出来ます。
長時間の測定には,別途充電池からの給電が必要です。

M5StickCをWiFiに接続する

コンピュータかスマートフォンをM5StickCに接続する

M5StickCの電源を入れます(この図で見て左上にあるボタンを長押しします)。
電源を入れると,このような画面が表示されます。
パソコンかスマートフォンを,画面に表示されているSSIDのアクセスポイント(M5StickCがアクセスポイントとして機能しています)を選択します。
画面に表示されているSSIDのパスワードを入力し接続します。

M5StickCを任意のインターネットに接続されたWiFiに接続する

M5StickCのOPEN THE URL BELOWの下に表示されているURLをブラウザで開きます。
接続したい任意のインターネットに接続されたWiFiのアクセスポイントのSSID,パスワード,利用者の名前を入力し,「接続する」を押します。
2.4GHz帯のアクセスポイントに接続してください。5GHz帯では接続できません。
接続試行中にはこのような表示となります。
このような画面が出ると接続は成功です。
これで,測定結果がクラウドに送信されます。

なお,以下のようなリストを用意しておき,URLをそのままブラウザに読ませるようにすると,複数の対象者に対応しやすいと思います。

SSID パスワード お名前 URL
M5S-CHXX 12345678 THORNDIKE_EDWARD_L http://192.168.4.1/connect?ssid=F660A-X2ac-G&password=qYLTNwxP&subject=THORNDIKE_EDWARD_L

また,名前の入力は必要ではありません。M5StickCを対象者に配布しやすくするためだけのものです。名前はクラウドには送信されませんし,M5StickCの電源を落とすと,SSID,パスワードとともに消去されます。

測定の実際

名札ケースに格納し,首から提げます。
位置は胸のあたりがよいでしょう。

結果を見る

結果はAmbientで見ることができます。
例えば,この例にあるCH08と番号の振られた端末のデータは,以下のURLでリアルタイムで見ることができます。
https://ambidata.io/bd/board.html?id=23194

これをたくさん用意すると,こんな感じで,複数の対象者のデータをリアルタイムでモニタリングできるようになります。

参考

このM5StickCに書き込んだコード

#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiUDP.h>
#include "time.h"
#include "Ambient.h"

//wifiinput
#include <ESPmDNS.h>
#include <WiFiClient.h>

String ssid = "M5S_CH08";
String password = "12345678";

String wifiSsid = "";
String wifiPassword = "";
String wifiName = "";
int inputstatus = 0;

int wifiStatus = WiFi.status();
WiFiServer server(80);

String getSsid(String path)
{
  int start = path.indexOf("?ssid=");
  int end = path.indexOf("&password=");
  return path.substring(start + 6, end);
}

String getPassword(String path)
{
  int start = path.indexOf("&password=");
  int end = path.indexOf("&subject=");
  return path.substring(start + 10, end);
}

String getwifiName(String path)
{
  int start = path.indexOf("&subject=");
  int end = path.length();
  return path.substring(start + 9, end);
}
//wifiinput


//FFT
#include "arduinoFFT.h"
#define SAMPLING_FREQUENCY 50
const uint16_t FFTsamples = 256;  // サンプル数は2のべき乗
double vReal[FFTsamples];  // vReal[]にサンプリングしたデーターを入れる
double vImag[FFTsamples];
double vLog[FFTsamples];
arduinoFFT FFT = arduinoFFT(vReal, vImag, FFTsamples, SAMPLING_FREQUENCY);  // FFTオブジェクトを作る
unsigned int sampling_period_us;
//unsigned long microseconds;
float indextoHz;
int fftroop;

double vbat = 0.0;
int8_t bat_charge_p = 0;

#define MODE_A 0 // blank
#define MODE_B 1 // display
uint8_t disp_mode = MODE_B;

#define BTN_A_PIN  37
#define BTN_ON  LOW
#define BTN_OFF HIGH
uint8_t prev_btn_a = BTN_OFF;
uint8_t btn_a      = BTN_OFF;

const char* ntpServer =  "ntp.jst.mfeed.ad.jp";
const int sport = 55998;
const int kport = 5556;

RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;

float accX = 0.0F;
float accY = 0.0F;
float accZ = 0.0F;
float acc;

int peak;
int mil;
int milf;
int mila;

uint8_t mac3[6];

WiFiUDP Udp;
char packetBuffer[800];

//Ambient
WiFiClient client;
Ambient ambient;
unsigned int AchannelId = 32595;
const char* AwriteKey = "fbddc1b79d31878d";
//Ambient

void sample(int nsamples) {
  int i;
    for (int i = 0; i < nsamples; i++) {
  btn_a = digitalRead(BTN_A_PIN);

  if(prev_btn_a == BTN_OFF && btn_a == BTN_ON){
    if(disp_mode == MODE_A){
    M5.Lcd.setCursor(0, 1, 2);
    M5.Lcd.print("NAME: ");
    M5.Lcd.println(wifiName);
    M5.Lcd.setCursor(0, 16);
    M5.Lcd.print("WIFI: ");
    M5.Lcd.println(WiFi.localIP());
    M5.Lcd.setCursor(0, 34);
    M5.Lcd.printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac3[0], mac3[1], mac3[2], mac3[3], mac3[4], mac3[5]);
    disp_mode = MODE_B;
    }else{
    M5.Lcd.fillScreen(BLACK);
      disp_mode = MODE_A;
    }
  }

  prev_btn_a = btn_a;

  // put your main code here, to run repeatedly:

        while(millis() < (mil + sampling_period_us)){
        }
        mil = millis();
        mila = millis();
        M5.IMU.getAccelData(&accX, &accY, &accZ);
        acc = sqrt(sq(accX)+sq(accY)+sq(accZ));

        Serial.print(mila);
        Serial.print(", ");
        Serial.print(acc);
        Serial.print(", ");
        Serial.print(peak);
        Serial.print(", ");
        float hz;
        hz = (float)peak * indextoHz;
        Serial.println(float(hz));

        vReal[i] = acc;
        vLog[i] = acc;
        vImag[i] = 0;

  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);

  if(disp_mode == MODE_B){
  vbat = M5.Axp.GetVbatData() * 1.1 / 1000;
  bat_charge_p = int8_t((vbat - 3.0) / 1.2 * 100);
  if(bat_charge_p > 100){
    bat_charge_p = 100;
  }else if(bat_charge_p < 0){
    bat_charge_p = 0;
  }
  M5.Lcd.setCursor(0, 50);

  M5.Lcd.printf("C:%3d%% ", bat_charge_p);
  M5.Lcd.printf("%02d-%02d ", RTC_DateStruct.Month, RTC_DateStruct.Date);
  M5.Lcd.printf("%02d:%02d:%02d\n", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);
  M5.Lcd.setCursor(0, 66);
  M5.Lcd.printf("ACC:%5.2f  FFT:%.2fHz   ", acc, hz);
  }


   }
}

void setup() {
  // put your setup code here, to run once:
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Axp.ScreenBreath(8); // MIN7-MAX12

  M5.IMU.Init();

  pinMode(BTN_A_PIN,  INPUT_PULLUP);

  esp_read_mac(mac3, ESP_MAC_WIFI_STA);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(1, 0, 2);

//wifiinput
  Serial.println();
  Serial.print("Configuring access point...");
  WiFi.softAP(ssid.c_str(), password.c_str());

  M5.Lcd.println("CONNECT TO");
  M5.Lcd.println("SSID: " + ssid);
  M5.Lcd.println("PASS: " + password);
  M5.Lcd.println("OPEN THE URL BELOW");
  M5.Lcd.println("http://192.168.4.1/");

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);

  if (!MDNS.begin("esp32"))
  {
    Serial.println("Error setting up MDNS responder!");
    while (1)
    {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");

  server.begin();
  Serial.println("Web server started");

  MDNS.addService("http", "tcp", 80);

  while(inputstatus == 0){
  wifiinput();
  }

  delay(1000);
//wifiinput


  // Set ntp time to local
  configTime(9 * 3600, 0, ntpServer);
  // Get local time
  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    // Set RTC time
    RTC_TimeTypeDef TimeStruct;
    TimeStruct.Hours   = timeInfo.tm_hour;
    TimeStruct.Minutes = timeInfo.tm_min;
    TimeStruct.Seconds = timeInfo.tm_sec;
    M5.Rtc.SetTime(&TimeStruct);

    RTC_DateTypeDef DateStruct;
    DateStruct.WeekDay = timeInfo.tm_wday;
    DateStruct.Month = timeInfo.tm_mon + 1;
    DateStruct.Date = timeInfo.tm_mday;
    DateStruct.Year = timeInfo.tm_year + 1900;
    M5.Rtc.SetData(&DateStruct);
  }
  M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 1, 2);
    M5.Lcd.print("CH08: ");
    M5.Lcd.println(wifiName);
    M5.Lcd.setCursor(0, 16);
    M5.Lcd.print("WIFI: ");
    M5.Lcd.println(WiFi.localIP());
   uint8_t mac3[6];
   esp_read_mac(mac3, ESP_MAC_WIFI_STA);
   M5.Lcd.setCursor(0, 34);
  M5.Lcd.printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac3[0], mac3[1], mac3[2], mac3[3], mac3[4], mac3[5]);
  Udp.begin(kport);

  //FFT
//  sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
  sampling_period_us = round(1000*(1.0/SAMPLING_FREQUENCY));
    indextoHz = (float)SAMPLING_FREQUENCY / FFTsamples;
    fftroop = 5 / indextoHz + 1;
  //FFT
  mil = millis();

  //ambient
  ambient.begin(AchannelId, AwriteKey, &client);
  //ambient

}

void DCRemoval(double *vData, uint16_t samples) {
    double mean = 0;
    for (uint16_t i = 1; i < samples; i++) {
        mean += vData[i];
    }
    mean /= samples;
    for (uint16_t i = 1; i < samples; i++) {
        vData[i] -= mean;
    }
}

void loop() {
    sample(FFTsamples);

    DCRemoval(vReal, FFTsamples);
    FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);  // 窓関数
    FFT.Compute(FFT_FORWARD); // FFT処理(複素数で計算)
    FFT.ComplexToMagnitude(); // 複素数を実数に変換
    int i;
    float peakm = 0;
      for(i = 0; i < fftroop; i++){ //5HZ相当までしか判定しない
      if(peakm<=vReal[i]){
      peakm=vReal[i];
      peak = i;
      }
    }
  //UDP
  milf = millis();

  //ambient
  ambient.set(1, acc);
  ambient.set(2, peak * indextoHz);

  ambient.send();
  //ambient

}

void wifiinput()
{
  if (wifiSsid.length() > 0 && wifiPassword.length() > 0)
  {
    if (wifiStatus != WL_CONNECTED)
    {
      M5.Lcd.setCursor(0, 0, 1);
      M5.Lcd.fillScreen(BLACK);

      Serial.println("SSID: " + wifiSsid);
      Serial.println("Pass: " + wifiPassword);
      Serial.println("Subject: " + wifiName);

      WiFi.begin(wifiSsid.c_str(), wifiPassword.c_str());
      while (wifiStatus != WL_CONNECTED)
      {
        delay(500);
        wifiStatus = WiFi.status();
        Serial.print(".");
        M5.Lcd.setCursor(0, 0, 1);
        M5.Lcd.setTextFont(2);
        M5.Lcd.println("WiFi connecting");
      }

        M5.Lcd.setCursor(0, 0, 1);
        M5.Lcd.setTextFont(2);
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.println("Welcome");
        M5.Lcd.println(wifiName);
        M5.Lcd.println("Your cooperation is");
        M5.Lcd.println("appreciated.");
//        M5.Lcd.println("Connected!");
        inputstatus = 1;
        delay(1000);

      Serial.print("WiFi connected\r\nIP address: ");
      Serial.println(WiFi.localIP());
    }
    delay(1000);
  }
  else
  {
    WiFiClient client = server.available();
    if (!client)
    {
      return;
    }
    Serial.println("");
    Serial.println("New client");
    if (client)
    {
      Serial.println("new client");

      while (client.connected())
      {
        if (client.available())
        {
          String req = client.readStringUntil('\r');
          Serial.print("Request: ");
          Serial.println(req);

          int addr_start = req.indexOf(' ');
          int addr_end = req.indexOf(' ', addr_start + 1);
          if (addr_start == -1 || addr_end == -1)
          {
            Serial.print("Invalid request: ");
            Serial.println(req);
            return;
          }
          String path = req.substring(addr_start + 1, addr_end);
          Serial.print("Path: ");
          Serial.println(path);

          String s = "";
          if (path == "/")
          {
            IPAddress ip = client.remoteIP(); // クライアント側のIPアドレス
            String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
            s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
            s += "<!DOCTYPE html>";
            s += "<html lang=\"ja\">";
            s += "  <head>";
            s += "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>";
            s += "    <style>";
            s += "      body {";
            s += "        font-size: 48px;";
            s += "        margin: 0;";
            s += "        background-color: #f2f2f2;";
            s += "        font-family: 'Arial',YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', Meiryo,'MS ゴシック',sans-serif;";
            s += "      }";
            s += "      header {";
            s += "        width: 100%;";
            s += "        height: 120px;";
            s += "        line-height: 120px;";
            s += "        background-color: #F1B514;";
            s += "        text-align: center;";
            s += "        color: #f2f2f2;";
            s += "        font-weight: bold;";
            s += "      }";
            s += "      main {";
            s += "        padding-left: 24px;";
            s += "        padding-right: 24px;";
            s += "      }";
            s += "      input {";
            s += "        width: 100%;";
            s += "        height: 80px;";
            s += "        border: none;";
            s += "        font-size: 48px;";
            s += "        padding: 16px;";
            s += "        margin-top: 16px;";
            s += "      }";
            s += "      button {";
            s += "        width: 100%;";
            s += "        height: 120px;";
            s += "        background-color: #254DEA;";
            s += "        font-size: 48px;";
            s += "        color: #f2f2f2;";
            s += "        border: none;";
            s += "        border-radius: 60px;";
            s += "        margin-top: 40px;";
            s += "        font-weight: bold;";
            s += "      }";
            s += "    </style>";
            s += "  </head>";
            s += "  <body>";
            s += "    <header>授業研究用加速度計 WiFi接続 CH08</header>";
            s += "    <main>";
            s += "      <form method=\"GET\" action=\"/connect\">";
            s += "        <input type=\"text\" name=\"ssid\" placeholder=\"SSID\" />";
            s += "        <input type=\"password\" name=\"password\" placeholder=\"Password\" />";
            s += "        <input type=\"text\" name=\"subject\" placeholder=\"Subject's name\" />";
            s += "        <button type=\"submit\">接続する</button>";
            s += "      </form>";
            s += "    </main>";
            s += "  </body>";
            s += "</html>";
            Serial.println("Response 200");
          }
          else if (path.startsWith("/connect"))
          {
            Serial.println("send to /connect");
            s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
            s += "<!DOCTYPE html>";
            s += "<html lang=\"ja\">";
            s += "  <head>";
            s += "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>";
            s += "    <style>";
            s += "      body {";
            s += "        font-size: 48px;";
            s += "        margin: 0;";
            s += "        background-color: #f2f2f2;";
            s += "        font-family: 'Arial',YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', Meiryo,'MS ゴシック',sans-serif;";
            s += "      }";
            s += "      header {";
            s += "        width: 100%;";
            s += "        height: 120px;";
            s += "        line-height: 120px;";
            s += "        background-color: #F1B514;";
            s += "        text-align: center;";
            s += "        color: #f2f2f2;";
            s += "        font-weight: bold;";
            s += "      }";
            s += "      main {";
            s += "        padding-left: 24px;";
            s += "        padding-right: 24px;";
            s += "        text-align: center;";
            s += "      }";
            s += "      input {";
            s += "        width: 100%;";
            s += "        height: 80px;";
            s += "        border: none;";
            s += "        font-size: 48px;";
            s += "        padding: 16px;";
            s += "        margin-top: 16px;";
            s += "      }";
            s += "      button {";
            s += "        width: 100%;";
            s += "        height: 120px;";
            s += "        background-color: #254DEA;";
            s += "        font-size: 48px;";
            s += "        color: #f2f2f2;";
            s += "        border: none;";
            s += "        border-radius: 60px;";
            s += "        margin-top: 40px;";
            s += "        font-weight: bold;";
            s += "      }";
            s += "    </style>";
            s += "  </head>";
            s += "  <body>";
            s += "    <header>授業研究用加速度計 WiFi接続 CH08</header>";
            s += "    <main>";
            s += "      <h4>WiFiに接続しています...</h4>";
            s += "      <h6>モニターにConnectedと表示されるまでお待ちください</h6>";
            s += "    </main>";
            s += "  </body>";
            s += "</html>";

            wifiSsid = getSsid(path);
            wifiPassword = getPassword(path);
            wifiName = getwifiName(path);

            Serial.println("SSID: " + wifiSsid);
            Serial.println("Pass: " + wifiPassword);
            Serial.println("Subject: " + wifiName);

            Serial.println("Response 200");
          }
          else
          {
            s = "HTTP/1.1 404 Not Found\r\n\r\n";
            Serial.println("Sending 404");
          }
          client.print(s);
          client.flush();
          client.stop();
        }
      }
    }
    Serial.println("Done with client");
  }
  delay(1000);

}