C#とAndroidによるUDP通信で加速度センサの値を取得するアプリケーション

#大まかな内容
ここでは、Androidの加速度センサの値をC#で取得し、その値によってAndroidの端末がどのように動いたかを感知するために作成している。

#Androidのアプリケーション
このアプリケーションでは、三軸（X,Y,Z）の加速度センサの値をUDP通信で送る。端末が、どの軸方向に向いているかを分かりやすくるために、各軸が一定の値以上になった場合に音声が鳴るように作成した。

##アプリケーションのGUI
ASUSTab K013の場合
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376689/1a48099b-b9ae-3897-41c5-8cdd72991f0e.jpeg" width=50%>
##ソースコード
ソケット通信を行うので、Androidmanifest.xmlに下記を追加する。

```xml:Androidmanifest.xml
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
```
次に、画像と音声ファイルを扱うため、app内のresのdrawableとrawを作成し、その中に画像データと音声データを入れる。

```
app
 └res
   ├drawable
   │ ├en.jpg
   │ ├en_gleen.jpg
   │ └en_red.jpg
   └raw
     ├xjikum.mp3
     ├xjikup.mp3
     ├yjikum.mp3
     ├yjikup.mp3
     ├zjikum.mp3
     └zjikup.mp3
```

```xml:activity_main.xml
<EditText
        android:id="@+id/IP_Address"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="52dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="0.0.0.0"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/Port"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="8080"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/IP_Address" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="280dp"
        android:text="X:"
        android:textSize="32sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="24dp"
        android:text="Y:"
        android:textSize="32sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="24dp"
        android:text="Z:"
        android:textSize="32sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <TextView
        android:id="@+id/X_Data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="280dp"
        android:text="TextView"
        android:textSize="32sp"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/Y_Data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="TextView"
        android:textSize="32sp"
        app:layout_constraintStart_toEndOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/X_Data" />

    <TextView
        android:id="@+id/Z_Data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="TextView"
        android:textSize="32sp"
        app:layout_constraintStart_toEndOf="@+id/textView3"
        app:layout_constraintTop_toBottomOf="@+id/Y_Data" />

    <TextView
        android:id="@+id/textView7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="230dp"
        android:text="加速度センサの値"
        android:textSize="32sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/Ran"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="87dp"
        android:layout_marginLeft="87dp"
        android:layout_marginBottom="70dp"
        android:text="通信開始"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="8dp"
        android:text="IPアドレス"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/IP_Address"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="8dp"
        android:text="ポート番号"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/Port"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/End"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="87dp"
        android:layout_marginRight="87dp"
        android:layout_marginBottom="70dp"
        android:text="通信解除"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/LED1"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_marginStart="100dp"
        android:layout_marginLeft="100dp"
        android:layout_marginBottom="150dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@drawable/en" />

    <ImageView
        android:id="@+id/LED2"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_marginEnd="100dp"
        android:layout_marginRight="100dp"
        android:layout_marginBottom="150dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/en" />

    <TextView
        android:id="@+id/textView6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="352dp"
        android:layout_marginEnd="124dp"
        android:layout_marginRight="124dp"
        android:text="Delay"
        android:textSize="32sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/Delay"
        android:layout_width="159dp"
        android:layout_height="49dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="40dp"
        android:layout_marginRight="40dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="100"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView6" />

```

```java:MainActivity.java
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity implements SensorEventListener{

    private SensorManager sensorManager;
    private TextView X_Data_TextView;   //加速度センサXの値
    private TextView Y_Data_TextView;   //加速度センサYの値
    private TextView Z_Data_TextView;   //加速度センサZの値
    private float data_X,data_Y,data_Z;
    private int xp,yp,zp,xm,ym,zm;
    private int data_x,data_y,data_z;
    private String Data;
    private Timer timer1,timer2;
    private mTimerTask1 timerTask1;
    private mTimerTask2 timerTask2;
    private Handler handler = new Handler();
    private Handler handler1 = new Handler();
    private long Delay;
    ImageView LED1,LED2;

    SoundPool soundPool;
    int mp3_xp,mp3_xm,mp3_yp,mp3_ym,mp3_zp,mp3_zm;


    @Override
    protected void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        X_Data_TextView = findViewById(R.id.X_Data);
        Y_Data_TextView = findViewById(R.id.Y_Data);
        Z_Data_TextView = findViewById(R.id.Z_Data);

        LED1 = findViewById(R.id.LED1);
        LED2 = findViewById(R.id.LED2);
        LED1.setImageResource(R.drawable.en);
        LED2.setImageResource(R.drawable.en);

        //効果音付けるのに必要なやつ
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
        } else {
            AudioAttributes attr = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build();
            soundPool = new SoundPool.Builder()
                    .setAudioAttributes(attr)
                    .setMaxStreams(5)
                    .build();
        }

        mp3_xp = soundPool.load(this, R.raw.xjikup, 1);
        mp3_xm = soundPool.load(this,R.raw.xjikum,1 );
        mp3_yp = soundPool.load(this, R.raw.yjikup, 1);
        mp3_ym = soundPool.load(this,R.raw.yjikum,1 );
        mp3_zp = soundPool.load(this, R.raw.zjikup, 1);
        mp3_zm = soundPool.load(this,R.raw.zjikum,1 );

        Button ran = findViewById(R.id.Ran);
        Button end = findViewById(R.id.End);

        ran.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(null != timer1){
                    timer1.cancel();
                    timer1 = null;
                }

                xp=yp=zp=xm=ym=zm=0;

                String delay = ((EditText)findViewById(R.id.Delay)).getText().toString();
                Delay = Long.parseLong(delay);

                timer1 = new Timer();
                timerTask1 = new mTimerTask1();
                timer1.schedule(timerTask1,0, Delay);

                timer2 = new Timer();
                timerTask2 = new mTimerTask2();
                timer2.schedule(timerTask2,0,Delay);
            }
        });

        end.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(null != timer1){
                    timer1.cancel();
                    timer1 = null;
                }
                final String address = ((EditText) findViewById(R.id.IP_Address)).getText().toString();
                String port = ((EditText) findViewById(R.id.Port)).getText().toString();
                int Port = Integer.parseInt(port);
                String exit = "exit";
                byte buf[] = new byte[exit.length()];

                try {
                    buf = exit.getBytes("SHIFT_JIS");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

                InetSocketAddress inetSocketAddress = new InetSocketAddress(address, Port);
                final DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, inetSocketAddress);

                AsyncTask<DatagramPacket, Void, Void> task = new AsyncTask<DatagramPacket, Void, Void>() {
                    @Override
                    protected Void doInBackground(DatagramPacket... datagramPackets) {
                        DatagramSocket datagramSocket = null;
                        try {
                            datagramSocket = new DatagramSocket();
                            datagramSocket.send(datagramPackets[0]);
                            datagramSocket.close();
                        } catch (SocketException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }

                };
                task.execute(datagramPacket);

            }
        });

    }



    @Override
    protected void onResume(){
        super.onResume();
        //Event Listener登録
        Sensor accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        sensorManager.registerListener((SensorEventListener) this,accel,SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause(){
        super.onPause();
        //Event Listener登録解除
        sensorManager.unregisterListener((SensorEventListener) this);
    }

    @Override
    public void onSensorChanged(SensorEvent event){
        if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
            data_X= (500+event.values[0]*25);
            data_Y= (500+event.values[1]*25);
            data_Z= (500+event.values[2]*25);

            data_x = (int)data_X;
            data_y = (int)data_Y;
            data_z = (int)data_Z;
            Data = data_x + " " +
                    data_y + " " +
                    data_z;
            X_Data_TextView.setText(String.valueOf(data_x));
            Y_Data_TextView.setText(String.valueOf(data_y));
            Z_Data_TextView.setText(String.valueOf(data_z));
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy){

    }

    private class mTimerTask2 extends TimerTask{
        @Override
        public void run() {
            handler.post(new Runnable() {
                @Override
                public void run() {

                    if (data_X >= 700) {
                        LED1.setImageResource(R.drawable.en_gleen);
                        xp += 1;
                        if (xp == 1) {
                            yp=zp=xm=ym=zm=0;
                            soundPool.play(mp3_xp, 2, 2, 0, 0, 1f);
                        }
                    }else if (data_X <= 300) {
                        LED1.setImageResource(R.drawable.en);
                        xm += 1;
                        if (xm == 1) {
                            xp=yp=zp=ym=zm=0;
                            soundPool.play(mp3_xm, 2, 2, 0, 0, 1f);
                        }
                    }else if (data_Y >= 700) {
                        LED2.setImageResource(R.drawable.en_red);
                        yp += 1;
                        if (yp == 1) {
                            xp=zp=xm=ym=zm=0;
                            soundPool.play(mp3_yp, 2, 2, 0, 0, 1f);
                        }
                    } else if (data_Y <= 300) {
                        LED2.setImageResource(R.drawable.en);
                        ym += 1;
                        if (ym == 1) {
                            xp=yp=zp=xm=zm=0;
                            soundPool.play(mp3_ym, 2, 2, 0, 0, 1f);
                        }
                    }else if (data_Z >= 700) {
                        LED1.setImageResource(R.drawable.en_gleen);
                        LED2.setImageResource(R.drawable.en_red);
                        zp += 1;
                        if (zp == 1) {
                            xp=yp=xm=ym=zm=0;
                            soundPool.play(mp3_zp, 2, 2, 0, 0, 1f);
                        }
                    }else if (data_Z <= 300) {
                        LED1.setImageResource(R.drawable.en);
                        LED2.setImageResource(R.drawable.en);
                        zm += 1;
                        if (zm == 1) {
                            xp=yp=zp=xm=ym=0;
                            soundPool.play(mp3_zm, 2, 2, 0, 0, 1f);
                        }
                    }
                }
            });
        }

    }
    private class mTimerTask1 extends TimerTask{
        @Override
        public void run(){

            handler.post(new Runnable() {
                @Override
                public void run() {
                    final String address = ((EditText) findViewById(R.id.IP_Address)).getText().toString();
                    String port = ((EditText) findViewById(R.id.Port)).getText().toString();
                    int Port = Integer.parseInt(port);
                    byte buf[] = new byte[Data.length()];

                    try {
                        buf = Data.getBytes("SHIFT_JIS");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }

                    InetSocketAddress inetSocketAddress = new InetSocketAddress(address, Port);
                    final DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, inetSocketAddress);


                    AsyncTask<DatagramPacket, Void, Void> task = new AsyncTask<DatagramPacket, Void, Void>() {
                        @Override
                        protected Void doInBackground(DatagramPacket... datagramPackets) {
                            DatagramSocket datagramSocket = null;

                            try {
                                datagramSocket = new DatagramSocket();
                                datagramSocket.send(datagramPackets[0]);
                                datagramSocket.close();
                            } catch (SocketException e) {
                                e.printStackTrace();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                            return null;
                        }

                    };
                    task.execute(datagramPacket);
                }
            });

        }
    }
}

```

#C#のアプリケーション
このアプリケーションでは、UDP通信で受け取ったAndroidの加速度センサの値をrichTextBoxに記録し、各軸の値をそれぞれTextBox表示するように作成した。

##アプリケーションのGUI
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376689/9a05b718-816e-ddc7-aacb-f2c26f2ee4b3.png" width=50%>


##ソースコード

```cs:Form1.cs
using System;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;

namespace UDP_Server
{
    public partial class Form1 : Form
    {
        int i;
        int[] d;
        string[] D;
        private UdpClient udpClient = null; //受信用クライアント
        public string rcvMsg = null;//受信メッセージ格納用
        
        public Form1()
        {
            InitializeComponent();

        }

        //ボタンをクリックしたときの処理
        private void button1_Click(object sender, EventArgs e)
        {
            //UDPの接続があるときにボタンを押した場合
            //その処理をなかったことにする（少し違う）
            if (udpClient != null)
            {
                return;
            }
            ((Button)sender).Enabled = false;

            string IPString = "127.0.0.1";
            IPAddress IPAdd = IPAddress.Parse(IPString); //IPアドレスを指定
            int Port = 8080;    //ポート番号を指定

            //UdpClientを作成し、指定したポート番号にバインドする
            IPEndPoint EP = new IPEndPoint(IPAdd, Port);
            UdpClient udp = new UdpClient(EP);

            richTextBox1.BeginInvoke(
                    new Action<string>(ShowReceivedString1), "受信を開始します");

            //非同期的な受信を開始する
            udp.BeginReceive(UdpServer, udp);
            ((Button)button1).Enabled = true;
        }

        //データを受信した時の処理
        private void UdpServer(IAsyncResult ar)
        {
            UdpClient udp = (UdpClient)ar.AsyncState;
            for (;;)
            {
                //一度非同期受信を終了する
                IPEndPoint remoteEP = null;
                byte[] rcvBytes = udp.Receive(ref remoteEP);


                //受信したデータを文字列に変換
                string rcvMsg = System.Text.Encoding.UTF8.GetString(rcvBytes);

                //"exit"を受信したら終了
                if (rcvMsg == "exit")
                {
                    break;
                }

                D = rcvMsg.Split(' ');
                for(i=0;D[i]== null; i++)
                {
                    d[i] = int.Parse(D[i]);
                }


                //受信したデータをTextBoxに表示する
                string displayMsg = string.Format("{0}", rcvMsg);
                richTextBox1.BeginInvoke(
                    new Action<string>(ShowReceivedString1), displayMsg);
                X_Data.BeginInvoke(
                    new Action<string>(ShowReceivedString_x), D[0]);
                Y_Data.BeginInvoke(
                    new Action<string>(ShowReceivedString_y), D[1]);
                Z_Data.BeginInvoke(
                    new Action<string>(ShowReceivedString_z), D[2]);
            }

            rcvMsg = "終了しました";
            //UdpClientを閉じる
            udp.Close();

            richTextBox1.BeginInvoke(
                   new Action<string>(ShowReceivedString1), rcvMsg);
        }

        private void ShowReceivedString1(string str)
        {
            if (richTextBox1.Text == "")
            {
                richTextBox1.Text = str;
                richTextBox1.SelectionStart = richTextBox1.Text.Length;
                richTextBox1.Focus();
                richTextBox1.ScrollToCaret();
            }
            else {
                richTextBox1.Text = richTextBox1.Text + "\r\n" + str;
                richTextBox1.SelectionStart = richTextBox1.Text.Length;
                richTextBox1.Focus();
                richTextBox1.ScrollToCaret();
            }

        }

        private void ShowReceivedString_x(string str)
        {
            X_Data.Text = str;

        }
        private void ShowReceivedString_y(string str)
        {
            Y_Data.Text = str;

        }
        private void ShowReceivedString_z(string str)
        {
            Z_Data.Text = str;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            //UdpClientを閉じる
            if (udpClient != null)
            {
                udpClient.Close();
            }
        }
    }
}

```

#今後の目標
C#に加速度センサの値で制御できるLEDを模した画像を追加しようと思っているが、そのためにはマルチスレッドでpictureBoxを操作できるようにならないといけないのでそこに力を入れていく。