こんにちわ。ゆきぽんずです。
今日はWebsocketを使ったAndroidとPCのリアルタイム通信を実装していきます。結構レベルの高い内容ですが、わかりやすいように写真もたくさん入れているのでついてきてください。
それではさっそく内容に入っていきます。
今回実装するもの
出力されたCSVファイル結果
以上が結果として得られるものです。
興味のある人は続きもどうぞ!
Android側の実装
bulid.gradle (Module:app)をクリックして、以下の部分を付け足す
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フォルダ内の配置はこんな感じ
ではでは引き続きコードを紹介していきます。
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 (デザイン)
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にしている記事をあげているので、詳しくはそちらをご覧ください
(*以降の内容は、こちらの記事の前提知識があるとして話を進めていきます)
それでは、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が学びたいと思った方にはこちらの本がオススメです。分厚いですが読み応え抜群です!!
pythonはネット上の情報で何とかなると思うので、Google先生に頼りましょう!(笑)
何かの参考になれば幸いです。
今日もブログを読んでくださりありがとうございます。
それではまたいつか!( ´∀`)bグッ!