Myo Gesture Control ArmbandのデータをPCを経由してUDPでAndroidスマートフォン/スマートウォッチに送信する

# やること
Myo Gesture Control ArmbandのデータをPCで受け取り、処理を行なってAndroidスマートフォン/スマートウォッチへと送る。
Myo armbandとPCの間はBLEでの通信、PCとAndroid側の間はUDPで通信することにしました。
Myo armbandとAndroid側を直接BLEで通信する方法にするとシステムが一番シンプルになりますが、BLE接続に関しての資料が非常にわかりにくいことと、プロトタイプであればUDPを使った方法が簡単なので今回はUDPを使った方法を紹介します。

<img width="1039" alt="スクリーンショット 2018-10-26 16.56.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/186901/5ebc8864-37b5-d3c5-c615-d64a203396f9.png">


※2018.10.25現在 Myo armbandの販売が終了しています。

PC側はProcessing、Android側のアプリはAndroid StudioにてJavaで記述しました。

## Processing側
「スケッチ」→「ライブラリをインポート...」→「ライブラリを追加...」から以下のライブラリを追加します。

・Myo for Processing 0.9.0.3
・ControlP5 2.2.6
・UDP 0.1

また、PCはMyoと接続しておく必要があります。

以下にコードを示します。
Processing側のコードでは、BLE接続されたMyoから信号を受け取り、Android側へUDPを用いて送信します。

```java:MyoWebsocket.pde

import de.voidplus.myo.*;
import controlP5.*;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.ArrayList;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import hypermedia.net.*;

Myo myo;
ArrayList<ArrayList<Integer>> sensors;
ControlP5 cp5;
UDP udp;
int portNo = 9002;
String ipAddress = "192.168.63.169"; //Android側のIPアドレス(192.168.63.169は例です)
int SEND_BUFFER_SIZE = 4;
byte[] sendBuffer = new byte[SEND_BUFFER_SIZE];

boolean useGUI = false;
boolean useSpectram = false;
boolean useWebsocket = false;
boolean sendStart = false;
boolean paintMode = true;

String receivedText = "";
String sendingText  = "";

float start = 0;
float ptime = 0;
float stime = 0;

int red   = 0;
int green = 0;
int blue  = 0;

 class NetworkData{
  int code; // 4byte integer
  int id;   // 4byte integer
 }

 boolean flagReceived = false;
 NetworkData receiveBuffer;

void setup(){
  // Screen setup
  frameRate(60);
  size(800, 600);
  background(200);
  noFill();
  stroke(0);

  // Myo setup
  println("attempting connect to Myo...");
  myo = new Myo(this, true); // true, with EMG data
  sensors = new ArrayList<ArrayList<Integer>>();
  for(int i=0; i<8; i++){
    sensors.add(new ArrayList<Integer>());
  }

  // UDP setup
  udp = new UDP(this, portNo);
  udp.setBuffer(SEND_BUFFER_SIZE);
  udp.setReceiveHandler("received");

  receiveBuffer = new NetworkData();
  receiveBuffer.code = receiveBuffer.id = 0;

  // GUI setup
  cp5 = new ControlP5(this);
  cp5.addButton("RawDataButton")
  .setPosition(695, 40)
  .setSize(100, 40);
  cp5.addButton("SpectramButton")
  .setPosition(695, 100)
  .setSize(100, 40);
  cp5.addButton("SendButton")
  .setPosition(695, 160)
  .setSize(100, 40);

  background(255);

  println("setup was done!");
}

int calcSumValue(){
 int sum = 0;
 //String s = "";
 for(int ch=0; ch<8; ch++){
   if(sensors.get(ch).size() > 0){
    sum = sum + Math.abs(sensors.get(ch).get(sensors.get(ch).size()-2)) * 5;
   }
 }
 return sum;
}

void draw(){

  stroke(red, green, blue);

  if(paintMode){
    if(mousePressed == true){
      line(mouseX, mouseY, pmouseX, pmouseY);
    }
  }

  if(useGUI){
    background(255);
    synchronized(this){
      for(int i=0; i<8; i++){
        if(!sensors.get(i).isEmpty()){
          beginShape();
          for(int j=0; j<sensors.get(i).size(); j++){
           vertex(j, sensors.get(i).get(j)+(i*50)+175); 
          }
          endShape();
        }
      }
    }
  }

  else if(useSpectram){
    background(255);
   synchronized(this){
    for(int ch=0; ch<8; ch++){
     pushStyle();
     fill(171, 1, 88, 100);
     rect(50+(ch*75), 400, 75, -calcRMS(sensors.get(ch), sensors.get(ch).size()-1*2));
     popStyle();
    }
   }
  }

}

// Calcuration of Route Mean Square(RMS) value.
float calcRMS(ArrayList<Integer> emgValue, int headOfData){
  float rmsValue=0;

  try {
    for(int i=0; i<300; i++){
      rmsValue += pow(emgValue.get(headOfData-i), 2);
    }
  } catch(ArrayIndexOutOfBoundsException e){
    // println("ERROR:" + e + ", return value 0.");
    return 0.0;
  }
  return sqrt(rmsValue/5.0);
}


void RawDataButton(){
  useGUI = !useGUI;
  useSpectram = false;
  background(255);
}

void SpectramButton(){
  useGUI = false;
  useSpectram = !useSpectram;
  background(255);
}

void SendButton(){
  sendStart = !sendStart;
  //wss.sendMessage("");
  if(sendStart){
    println("send start!"); 
    (new Thread(new UDPThread())).start();
  }
  else{
    println("send suspended"); 
  }
}

void myoOnEmgData(Device myo, long timestamp, int[] data) {

  // Data:
  synchronized (this) {
    for (int i = 0; i<data.length; i++) {
      sensors.get(i).add((int) map(data[i], -128, 127, -25, 25)); // [-128 - 127]
    }
    while (sensors.get(0).size() > width) {
      for(ArrayList<Integer> sensor : sensors) {
        sensor.remove(0);
      }
    }
  }
}

int byteArrayToInt(byte[] b) {
  return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24;
}

byte[] intToByteArray(int a){
  byte[] bytes = ByteBuffer.allocate(4).putInt(a).array();
  return bytes;
}

class UDPThread implements Runnable{
  @Override
  public void run(){
    while(true){

      int emgData = calcSumValue();

      byte[] tmpArray = intToByteArray(emgData);
      sendBuffer[0] = tmpArray[0];
      sendBuffer[1] = tmpArray[1];
      sendBuffer[2] = tmpArray[2];
      sendBuffer[3] = tmpArray[3];
      println(sendBuffer[0]+" "+sendBuffer[1]+" "+sendBuffer[2]+" "+sendBuffer[3]);

      udp.send(sendBuffer, ipAddress, portNo);

      try{
        Thread.sleep(20);
      }
      catch(Exception e){

      }
    }
  }
}
```
RawDataButtonを押すと、Myoからの源信号を表示します。力を入れてみたりしてうまく信号が上下していたらMyoからPCへは正しく信号が送られています。
SpectrumButtonを押すと、Myoの信号をRMS(Root Mean Square)値を表示します。
SendButtonを押すと、Android側とのUDP通信を開始し、Myoの筋電値を送信します。本コードでは筋電値8チャンネル分の合計値を送信しています。

## Android側
Android側では、PC側から送られたデータを受け取ります。
以下にコードを示します(一部抜粋)



```java:MainActivity.java
package com.example.midwinter.experience1;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.io.IOException;

import java.nio.ByteBuffer;

public class MainActivity extends Activity{

    // UDP関連
    public static final int SERVER_PORT = 9002; //サーバポート(9002は例です)
    public static final int PACKET_SIZE = 1024;
    DatagramSocket socket = null;
    byte[] buf = new byte[PACKET_SIZE];
    DatagramPacket packet = new DatagramPacket(buf, buf.length);
    
    // *************************** onCreate *************************** //
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 縦画面固定
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }
    // **************************************************************** //

    // *************************** onStart **************************** //
    @Override
    public void onStart(){
        super.onStart();

        Thread ut = new Thread(new UDPThread());
        ut.setPriority(10);
        ut.start();
    }
    // **************************************************************** //

    class UDPThread implements Runnable{
        @Override
        public void run(){
            try{
                socket = new DatagramSocket(SERVER_PORT);
                Log.d("UDPconnection", "データグラムレシーバが起動しました port="+socket.getLocalPort());

                while(true) {
                    socket.receive(packet);

                    int num = ByteBuffer.wrap(packet.getData()).getInt();
                    String s = String.valueOf(num);
                    Log.d("received", s);
                    try{
                        Thread.sleep(5);
                    }
                    catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }
            catch(IOException e){
                e.printStackTrace();
            }
            finally {
                if(socket != null){
                    socket.close();
                }
            }
        }
    }
}


```
UDPThreadクラス内のwhile文の中でUDP通信のデータを受信し続けます。
Stringとして受け取っていますがこれを事前に用意しておいたフィールドにsetすることで、アプリ内でのアクションに利用することができます。