M5StickCによるジェスチャ認識(3)～Wi-Fi接続と学習データの生成

# M5StickCによるジェスチャー認識
- 今回は、学習データを作成することを目標とします。

# M5StickC系のプログラム関連記事
1. [M5StickCによるジェスチャ認識(1)～インストールから加速度取得まで](https://qiita.com/chimamedia/items/7aa8dac10dc178009943)
2. [M5StickCによるジェスチャ認識(2)～サンプルコードによるジェスチャ認識](https://qiita.com/chimamedia/items/9d8a4703dcd9940d40be)
3. [M5StickCによるジェスチャ認識(3)～Wi-Fi接続と学習データの生成](https://qiita.com/chimamedia/items/fd367f75c3979541c49d)（この記事）
4. M5StickCによるジェスチャ認識(4)～オリジナルデータによるジェスチャ認識
5. M5StickCによる音声認識
※現状の予定になります

# 学習データの作成
- 前回の記事では「〇」「W」「∠」の3種類のサンプルを認識することができた。
- 今回は、オリジナルのジェスチャを認識するために、まずは学習データを作成することを考えます。
- 学習データは、ある一定の長さのジェスチャとなる加速度データをたくさん用意する必要があります。
- 一般的には学習データが多ければ多いほど精度が良くなります。
- 前回のプログラムを応用して、シリアル通信で取得した加速度情報をある一定のフレームで取得し、ファイルを生成することもできますが、シリアル通信のためにコードをパソコンに挿したまま行う必要があり、煩わしくなります。
- そこで、今回はWiFiでデータを飛ばし、学習データを作成することを考えます。
- 今回の手順は次の通りです。

1. Wi-Fi通信のプログラムを作成
2. 受信側で学習データの生成

## Wi-Fiデータで加速度データの送信
- M5StcikCは、通信機能として、Wi-Fiモジュール、Bluetoothモジュールがありますが、今回はWi-Fiを使います。
- 新しくプロジェクトを作成します。プロジェクトは[GitHub](https://github.com/chimamedia/SendAccelforM5StickC.git)にも置いて負います
- main.cppは次のような形になります

```cpp:main.cpp
#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiUDP.h>
#include "time.h"

const char* ssid       = "XXXXXXXXXXXX"; // SSID
const char* password   = "XXXXXXXXXXXXXX"; // Password
const char* ntpServer =  "aaa.bbb.jp";

const char* saddr = "XXX.XXX.XXX.XXX"; //taeget PC address

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;

uint8_t mac3[6];

WiFiUDP Udp;
char packetBuffer[255];


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

  M5.IMU.Init();
  
  esp_read_mac(mac3, ESP_MAC_WIFI_STA);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(1, 0, 2);
  M5.Lcd.println("Welcome \n If the connection fails, turn off and on the power again.");
 
  // connect to WiFi
  M5.Lcd.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.print(".");

  }
  M5.Lcd.println("CONNECTED");
  delay(1000);

  // 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("Connected \nto ");
    M5.Lcd.println(WiFi.localIP());
   uint8_t mac3[6];
   esp_read_mac(mac3, ESP_MAC_WIFI_STA);
  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); 
  
}

void loop() {
  int mil = millis(); 
  // put your main code here, to run repeatedly:
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);
  M5.Lcd.setCursor(0, 50);
  M5.Lcd.printf("%04d-%02d-%02d ", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date);
  M5.Lcd.printf("%02d:%02d:%02d\n", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);


  // 加速度データ取得
  M5.IMU.getAccelData(&accX, &accY, &accZ);

  //データを学習用に変換する
  accX *= 1000;
  accY *= 1000;
  accZ *= 1000;

  M5.Lcd.printf("%6.0f, %6.0f, %6.0f\n", accX, accY, accZ);

  //UDP
  Udp.beginPacket(saddr, sport);
  Udp.printf("%5.2f, %5.2f, %5.2f", accX, accY, accZ);
  Udp.endPacket();  
  delay(100);
}
```

- platformio.iniは次のように設定しました。

```ini:platformio.ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
monitor_speed = 115200
build_flags = -DARDUINOSTL_M_H -Ilib/tfmicro/third_party/gemmlowp -Ilib/tfmicro/third_party/flatbuffers/include 
upload_speed = 1500000
```

- プログラムの初めのほうの「SSID」「PASSWORD」「PCのIPアドレス」を適宜セットしてください。SSIDとPASSWORDは各自宅のWi-Fiの設定どおりに、「PCのIPアドレス」はコマンドプロンプトなどで「ipconfig」で確認できます。
- プログラムを書き終えたら「Build」を押して、ビルドする。エラーがなければ、そのまま「Upload」すると、加速度がディスプレイに表示されます。

## 受信プログラムの作成
- 今回は、Pythonを使って受信プログラムを作成します。

### 開発環境
- python 3.6
- pyqt

### 受信プログラム
- ジェスチャを学習するためのファイルの指定が「output_[LABELNAME]_[PERSON].txt」である。また、ジェスチャーの長さはジェスチャーによって変わるため、それをGUIから操作できるようにした。
- また、各自の環境によってはIPアドレスなども変わるため、そこもGUIから設定できるようにする。
- プログラムは、[GitHub](https://github.com/chimamedia/UDP_GUI.git)に公開している。

```python:UDP_GUI.py
from socket import *
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QCoreApplication
import datetime
import time
import csv
import pprint

class Example(QMainWindow):
    SrcIP = "XXX.XXX.XXX.XXX" #M5StickC Address
    outputName = "sample"
    labelName = "wave"
    loop = 100
    n = 0
    
    #初期化
    def __init__(self):
        super().__init__()
        self.initUI()
        self.dt_now = datetime.datetime.now()

    #UI create
    def setIP(self, msg):
        SrcIP = msg                             # 受信元IP
        SrcPort = 55998                                 # 受信元ポート番号
        self.SrcAddr = (SrcIP, SrcPort)                 # アドレスをtupleに格納

        self.BUFSIZE = 1024                             # バッファサイズ指定
        self.udpServSock = socket(AF_INET, SOCK_DGRAM)  # ソケット作成
        self.udpServSock.bind(self.SrcAddr)             # 受信元アドレスでバインド
        
    #UI create
    def initUI(self):      

        btn1 = QPushButton("Record", self)
        btn1.move(30, 30)

        btn2 = QPushButton("Quit", self)
        btn2.move(150, 30)
        
        btn3 = QPushButton('Set IP', self)
        btn3.move(30, 80)        
        self.inputURL = QLineEdit(self)
        self.inputURL.move(150, 80)
        self.inputURL.setText("192.168.1.1")
        
        btn4 = QPushButton('Set name', self)
        btn4.move(30, 130)
        self.inputFileName = QLineEdit(self)
        self.inputFileName.move(150, 130)
        self.inputFileName.setText("sample")
        
        btn5 = QPushButton('Set Label', self)
        btn5.move(30, 180)
        self.inputLabelName = QLineEdit(self)
        self.inputLabelName.move(150, 180)
        self.inputLabelName.setText("wave")
        
        btn6 = QPushButton('Set Length', self)
        btn6.move(30, 230)
        self.inputLength = QLineEdit(self)
        self.inputLength.move(150, 230)
        self.inputLength.setText("100")

        # クリックされたらbuttonClickedの呼び出し
        btn1.clicked.connect(self.recv)            
        btn2.clicked.connect(QCoreApplication.instance().quit)
        btn3.clicked.connect(self.Output)
        btn4.clicked.connect(self.Setfilename)
        btn5.clicked.connect(self.Setlabel)
        btn6.clicked.connect(self.Setlength)
        
        self.statusBar()

        self.setGeometry(300, 300, 290, 300)
        self.setWindowTitle('Getting accel data')
        self.show()

    #UDP receive
    def recv(self):
        print('\n----------\nIP Adress: ', self.SrcIP)
        print('loop count: ', self.loop)
        print('Output filename: ', self.outputName)
        print('label name:', self.labelName)
        i = 0
                
        #with open(self.outputName + '_' + self.dt_now.isoformat().replace(':','') + '.txt', 'a', newline='') as f:
        with open('output_' + self.labelName + '_' + self.outputName + '.txt', 'a', newline='') as f:
            
            f.write('\n\n\n -,-,-\n')
            time.sleep(2)
            
            self.setIP(self.SrcIP)
            while i < self.loop: # 受信待ち
                i = i + 1
                data, addr = self.udpServSock.recvfrom(self.BUFSIZE) # 受信
                l = data.decode().split(',')
                # 受信データと送信アドレス表示
                print(i, "\t", float(l[self.n]), float(l[self.n+1]), float(l[self.n+2]), addr) 
                s = 'x, y, z = ' + l[self.n] + ', ' + l[self.n+1] + ', ' + l[self.n+2]
                self.statusBar().showMessage(s)
                
                f.write(str(float(l[self.n])) + ',' + str(float(l[self.n+1])) + ',' + str(float(l[self.n+2])) + '\r\n')
            i = 0
            self.udpServSock.close()

    #ULR set
    def Output(self):
        self.SrcIP = self.inputURL.text()
        print('Change IP Adress:', self.SrcIP)
        
    #ULR set
    def Setlength(self):
        self.loop = int(self.inputLength.text())
        print('Change loop count:', self.loop)
        
    #Label set
    def Setlabel(self):
        self.labelName = self.inputLabelName.text()
        print('Change label name:', self.labelName)
    
    #filename set
    def Setfilename(self):
        self.outputName = self.inputFileName.text()
        print('Change output filename:', self.outputName)
        
        #時間の取得
        self.dt_now = datetime.datetime.now()

    def buttonClicked(self):

        # ステータスバーへメッセージの表示
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())
```

- プログラムは、以下のように起動する

```
> python UDP_GUI.py
```

- 起動すると以下のような画面が生成される。

![2020-07-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214668/507d13f0-ac2f-8e80-1e61-6b74493822c4.png)

- それぞれ、数値や文字を入力後にSetボタンを押す
- Set IP: PCのIPアドレスを設定する
- Set Name: 取得した人物の名前を登録（特に何でもよい）
- Set Label: ジェスチャの名前を登録
- Set Length: ジェスチャーを行っている長さを設定
- [Record]をクリックすると、M5StickCが起動していれば、受信し、ファイルを生成する。
- 同じ名前の人で何度も同じジェスチャーのデータを生成する場合は、続けて[Record]すると上書き保存する。
- すでに同じ名前のファイルがある場合は、上書き保存する設定になっているため、新しく作成しなおす場合は、事前に作成されているテキストファイルを削除する。

![2020-07-18 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214668/bcb37ddb-fe96-2006-4a15-643b39f5a824.png)