やること
Myo Gesture Control ArmbandのデータをPCで受け取り、処理を行なってAndroidスマートフォン/スマートウォッチへと送る。
Myo armbandとPCの間はBLEでの通信、PCとAndroid側の間はUDPで通信することにしました。
Myo armbandとAndroid側を直接BLEで通信する方法にするとシステムが一番シンプルになりますが、BLE接続に関しての資料が非常にわかりにくいことと、プロトタイプであればUDPを使った方法が簡単なので今回はUDPを使った方法を紹介します。
※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を用いて送信します。
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側から送られたデータを受け取ります。
以下にコードを示します(一部抜粋)
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することで、アプリ内でのアクションに利用することができます。