Engineerの研鑽

メインはプログラミング系ブログ(本の要約とかもします)

質問はCONTACTやコメントでお願い致します。

【WebSocket】リアルタイムでAndroidの加速度値を測定してみよう

f:id:yukiyukiponsu:20190412193139j:plain

こんにちわ。ゆきぽんずです。

 

今日はWebsocketを使ったAndroidとPCのリアルタイム通信を実装していきます。結構レベルの高い内容ですが、わかりやすいように写真もたくさん入れているのでついてきてください。

 

それではさっそく内容に入っていきます。

 

記事の目的 AndroidとPCをリアルタイムで通信(WebSocket)し、Androidから加速度値をPCに送信

 

記事の対象読者 AndroidStudioを使ったことがある人、Javaに関する知識が多少なりともある人

 

今回実装するもの


AndroidとPCをリアルタイム通信して加速度値を取得

 

出力されたCSVファイル結果

f:id:yukiyukiponsu:20200124001057p:plain

 

以上が結果として得られるものです。

 

興味のある人は続きもどうぞ!

 Android側の実装

bulid.gradle (Module:app)をクリックして、以下の部分を付け足す

f:id:yukiyukiponsu:20200123222504p:plain

bulid.gradle (Module: app)

apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion "29.0.1" defaultConfig { applicationId "com.e.myapplication" minSdkVersion 25 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
/* 追加部分 : javaでwebsocketを使えるようにする*/ implementation 'org.java-websocket:Java-WebSocket:1.3.8' //use for Json implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.7' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.7' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.7'
/**/ testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }

appフォルダ内の配置はこんな感じ

f:id:yukiyukiponsu:20200123230042p:plain

ではでは引き続きコードを紹介していきます。

AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.e.myapplication">

<!-- 追加部分 --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" > </uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" > </uses-permission> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml (Text)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/StateTxtView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="155dp"
        android:text="State"
        tools:ignore="MissingConstraints"
        tools:layout_editor_absoluteX="175dp"
        android:gravity="center"/>

    <TextView
        android:id="@+id/RecvTxtView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="77dp"
        android:text="Message"
        app:layout_constraintTop_toBottomOf="@+id/StateTxtView"
        tools:ignore="MissingConstraints"
        tools:layout_editor_absoluteX="147dp"
        android:gravity="center"/>

    <TextView
        android:id="@+id/text_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="247dp"
        android:text="text_info"
        app:layout_constraintTop_toBottomOf="@+id/StateTxtView"
        tools:ignore="MissingConstraints"
        tools:layout_editor_absoluteX="147dp"
        android:gravity="center" />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="367dp"
        android:gravity="center"
        android:text="Androidの加速度を測定"
        app:layout_constraintTop_toBottomOf="@+id/StateTxtView"
        tools:ignore="MissingConstraints"
        tools:layout_editor_absoluteX="147dp" />

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="463dp"
        android:gravity="bottom"
        android:orientation="horizontal">

        <Button
            android:id="@+id/ConnectBtn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"

            android:text="Connect" />

        <Button
            android:id="@+id/CorrectBtn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"

            android:text="Correct Data" />

        <Button
            android:id="@+id/SendMsgBtn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="SendText" />

    </LinearLayout>
</RelativeLayout>
activity_main.xml (デザイン)

f:id:yukiyukiponsu:20200123230632p:plain

 

Main.java
package com.e.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; //java instance change JSON

import java.net.URI;
import java.net.URISyntaxException;

//送信用加速度の構造体配列
class AccelerationData{
    String date;
    float X;
    float Y;
    float Z;
}

public class MainActivity extends AppCompatActivity implements SensorEventListener {
    //socket
    private Button wsConncetButton, wsSendMessageButton, wsCorrectDataButton;
    private TextView wsStateTextView, wsRecvMessageTextView;
    //自分のPCのIPアドレスを記入
    private static String ServerIP = "192.168.3.3";
    private static String ServerPORT = "5001";
    private myWsClientListener ws;

    private boolean clickFlag = false; //送信ボタンクリック判定
    private long startTime;
    private long stopTime;

    AccelerationData[] array_acceleration = new AccelerationData[12000];

    //accelerationSensor
    private SensorManager sensorManager;
    private TextView textView, textInfo;

    private float sensorX, sensorY, sensorZ = 0;
    private final Handler handler = new Handler();
    private Runnable runnable;
    //引数を指定
    private int index = 0;

    @Override
    protected void onResume() {
        super.onResume();
        // Registor Listener
        Sensor accel = sensorManager.getDefaultSensor(
                Sensor.TYPE_LINEAR_ACCELERATION);

        //sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_FASTEST);
        //sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_GAME);
        //sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_UI);
    }

    // 解除するコードも入れる!
    @Override
    protected void onPause() {
        super.onPause();
        // Listenerを解除
        sensorManager.unregisterListener(this);
        StopCyclicHandler();       // 周期ハンドラ停止
    }

    //センサの値が変わったときの処理
    @Override
    public void onSensorChanged(SensorEvent event) {

        if(event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION){

            sensorX = event.values[0];
            sensorY = event.values[1];
            sensorZ = event.values[2];

            if(clickFlag){
                startTime = System.currentTimeMillis(); //process time
                //nowTime = new Date(System.currentTimeMillis());

                //set nowTime and AccelerationData
                array_acceleration[index].date =  format(startTime);
                //array_acceleration[index].date = startTime;
                array_acceleration[index].X = sensorX;
                array_acceleration[index].Y = sensorY;
                array_acceleration[index].Z = sensorZ;

                //check data
                Log.d("AccelerationDate :", array_acceleration[index].date);
                Log.d("AccelerationX :", String.valueOf(array_acceleration[index].X));
                Log.d("AccelerationY :", String.valueOf(array_acceleration[index].Y));
                Log.d("AccelerationZ :", String.valueOf(array_acceleration[index].Z));

                index++;

                //waypoint = System.currentTimeMillis();
                // measure send time
                stopTime = System.currentTimeMillis();
                Log.d("delaytime :", String.valueOf(stopTime - startTime));
            }
        }
        showInfo(event);
    }

    public String format(long millis){
        long hour = (millis / (1000 * 60 * 60)) % 24;
        long minute = (millis / (1000 * 60))  % 60;
        long second = (millis / 1000)  % 60;
        long millisSec = millis % 1000;

        String time = String.format("%02d:%02d:%02d:%03d", hour, minute, second, millisSec);
        return time;
    }
    protected void StopCyclicHandler() {
        handler.removeCallbacks(runnable);
    }


    //WS Lister
    private class myWsClientListener extends WebSocketClient {
        public myWsClientListener(URI serverUri) {
            super(serverUri);
        }

        @Override
        //connect socket
        public void onOpen(ServerHandshake handshakedata) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    wsStateTextView.setText("Conneted!!");
                }
            });
        }

        @Override
        //Message Recieve For Server
        public void onMessage(final String message) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    wsRecvMessageTextView.setText(message);
                    if (message.equals("correctData")) {
                        Log.d("MessageReceive", message);
                        clickFlag = true;
                        //StartCyclicHandler();
                    }
                }
            });
        }

        @Override
        //Disconnect Server
        public void onClose(int code, String reason, boolean remote) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    wsStateTextView.setText("DisConneted..");
                }
            });
        }

        @Override
        //Error
        public void onError(Exception ex) {
            Log.e("ERROR", ex.getMessage());
        }

        //構造体配列をjson形式で送信
        public void send(AccelerationData[] array_acceleration) throws JsonProcessingException {
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(array_acceleration);
            Log.d("json :", json);
            ws.send(json);
        }
    }

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

        // Get an instance of the SensorManager
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        textInfo = findViewById(R.id.text_info);
        // Get an instance of the TextView
        textView = findViewById(R.id.text_view);

        //構造体配列のAccelerationのインスタンス化
        for(int i=0; i<array_acceleration.length; i++){
            array_acceleration[i] = new AccelerationData();
        }

        //サーバーの接続準備
        WebSocketImpl.DEBUG = true;
        try {
            ws = new myWsClientListener(new URI("ws://" + ServerIP + ":" + ServerPORT +"/"));
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        wsStateTextView = findViewById(R.id.StateTxtView);
        wsRecvMessageTextView = findViewById(R.id.RecvTxtView);


        wsConncetButton = findViewById(R.id.ConnectBtn);
        wsSendMessageButton = findViewById(R.id.SendMsgBtn);
        wsCorrectDataButton = findViewById(R.id.CorrectBtn);

        //Server接続Buttonイベント
        wsConncetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!ws.isOpen()) {
                    ws.connect();
                    Log.d("Connect", "connect");
                }

            }
        });

        //加速度を配列に格納するButtonイベント
        wsCorrectDataButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                wsStateTextView.setText("Correct Acceleration Data");
                ws.send("correctData");
            }
        });

        //送信Buttonイベント
        wsSendMessageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    wsStateTextView.setText("Send Acceleration Data");
                    ws.send(array_acceleration); //構造体配列をsend
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    // (お好みで)加速度センサーの各種情報を表示
    private void showInfo(SensorEvent event) {
        // センサー名
        StringBuffer info = new StringBuffer("Name: ");
        info.append(event.sensor.getName());
        info.append("\n");

        // 最小遅れ
        int data = event.sensor.getMinDelay();
        info.append("Mindelay: ");
        info.append(String.valueOf(data));
        info.append(" usec\n");

        // 最大遅れ
        data = event.sensor.getMaxDelay();
        info.append("Maxdelay: ");
        info.append(String.valueOf(data));
        info.append(" usec\n");

        // 最大レンジ
        info.append("MaxRange: ");
        float fData = event.sensor.getMaximumRange();
        info.append(String.valueOf(fData));
        info.append("\n");

        // 分解能
        info.append("Resolution: ");
        fData = event.sensor.getResolution();
        info.append(String.valueOf(fData));
        info.append(" m/s^2\n");

        textInfo.setText(info);
    }

    //センサの精度を上げたいときの処理
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

 

以上でAndroid側の実装は終わりです!

 

しかし、AndroidがWebSocketのClientになるのでメッセージを受け取るServerが必要になります。そこで今回、pythonをつかってPCをWebSocketのServerにしています。

 

以前、pythonを使ってPCをWebSocketのServerにしている記事をあげているので、詳しくはそちらをご覧ください

 

www.yukiyukiponsu.work

 (*以降の内容は、こちらの記事の前提知識があるとして話を進めていきます)

 

それでは、Androidの出力値を受け取るためのpythonのコードを紹介します。試行錯誤の後が残っていますが、気にしないでください(しっかり動作します)。

PC側の実装(python

import os
import sys
#websocket
from websocket_server import WebsocketServer
#pandas
import pandas as pd
#numpy
import numpy as np
#json
import json
import csv
#csv list
csvlist = []

def is_json(myjson): #json型の判定
    try:
        json_object = json.loads(myjson)
    except ValueError as e:
        return False
    return True

def new_client(client, server):
    print("Connect")
    server.send_message_to_all("Connect")

def send_msg_allclient(client, server, message):
    #print(message)
    if is_json(message) == True:
        global csvlist
        csvlist = pd.read_json(message) #Json形式の文字列の読み込み
        print("Get Acceleration Data")
        server.send_message_to_all("get")

    else:
        if message == "correctData":
            server.send_message_to_all(message)

server = WebsocketServer(port=5001, host='192.168.3.3')
server.set_fn_new_client(new_client)
server.set_fn_message_received(send_msg_allclient)
server.run_forever()

#データフレームを作成
df_Acceleration = pd.DataFrame(csvlist)

#CSVファイルとして出力
filename_Acceleration = 'AndroidData.csv'
df_Acceleration.to_csv(filename_Acceleration, index=False)

 

以上です!!

 

これでリアルタイムの加速度値が取れるようになったと思います。このコードをいじって楽しんでください。

 

もっとJavaが学びたいと思った方にはこちらの本がオススメです。分厚いですが読み応え抜群です!!

 

スッキリわかるJava入門 第3版 (スッキリシリーズ)

スッキリわかるJava入門 第3版 (スッキリシリーズ)

 

 

pythonはネット上の情報で何とかなると思うので、Google先生に頼りましょう!(笑)

 

何かの参考になれば幸いです。

 

今日もブログを読んでくださりありがとうございます。

 

それではまたいつか!( ´∀`)bグッ!