AndroidWearの加速度センサ値をスマホでリアルタイムにグラフ描画する!(後編)

  • このエントリーをはてなブックマークに追加
  • Pocket

AndroidWearで取得した加速度をスマホに送信して、リアルタイムにグラフ描画するアプリを作ります。

(前編)ではスマートウォッチで取得した加速度をスマートフォンへ送信する処理を書きました。

この(後編)ではスマートウォッチから受け取った加速度をMPAndroidChartというライブラリを使ってリアルタイムにグラフ描画します。

手順

wear側(前編)

1.加速度データ取得

2.加速度データ送信

mobile側(後編)※今回

3.加速度データ受信

4.加速度をグラフにプロット

3.加速度データ受信

ウェアラブルからのデータを受け取るため、GoogleApiClientを使います。

  1. GoogleApiClientのインスタンスを作成し、

  2. GoogleApiClientに接続して、

  3. GoogleApiClientの結果を受け取る。

という流れです。

3.1.GoogleApiClientのインスタンスを作成

onCreate内でGoogleApiClientのインスタンスを作成します。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //GoogleApiClientインスタンス生成
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                @Override
                public void onConnectionFailed(ConnectionResult connectionResult) {
                    Log.d(TAG, "onConnectionFailed:" + connectionResult.toString());
                }
            })
            .addApi(Wearable.API)
            .build();
    mChart = (LineChart) findViewById(R.id.lineChart);
    mChart.setDescription(null); // 表のタイトルを空にする
    mChart.setData(new LineData()); // 空のLineData型インスタンスを追加
}

3.2.GoogleApiClientに接続

onStart内で記述します。

@Override
protected void onStart() {
    super.onStart();
    //GoogleApiClient接続
    mGoogleApiClient.connect();
}

3.3.GoogleApiClientの結果を受け取る。

@Override
public void onConnected(Bundle bundle) {
        //GoogleApiClient 接続成功時に呼ばれます。
    Log.d(TAG, "onConnected");
    Wearable.MessageApi.addListener(mGoogleApiClient, this);
}

@Override
public void onConnectionSuspended(int i) {
    //接続中断時に呼ばれます。
    Log.d(TAG, "onConnectionSuspended");
}

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    //メッセージ(データ)が来たら呼ばれます。
    xTextView.setText(messageEvent.getPath());
    //受け取ったデータmsgはコンマ区切りのcsv形式なので、value[]にそれぞれ格納します。
    String msg = messageEvent.getPath();
    String[] value = msg.split(",", 0);
}

ここまでで、ウェアラブルで取得し送られて来た加速度データを、スマホ側で取得できるようになりました。

4.加速度をグラフにプロット

最後に取得した加速度データをMPAndroidChartを使ってグラフにプロットしていきます。

グラフ描画にはMPAndroidChartライブラリを用います。

導入はとても簡単です。

MPAndroidChart導入

プロジェクト(ルート直下)のbuild.gradle内に下記を設定します。

ハイライトが追記部分です。

allprojects {
    repositories {
        jcenter()
        maven {
            url "http://jitpack.io"
        }
    }
}

mobileのbuild.gradle内に下記を設定します。

dependencies {
    compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
}

レイアウト

レイアウトファイルでグラフを表示します。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="com.example.matu_mio.graphdraw.MainActivity">


    <com.github.mikephil.charting.charts.LineChart
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/lineChart"/>


    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/xValue"
        android:layout_below="@+id/textView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/yValue"
        android:layout_below="@+id/xValue"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zValue"
        android:layout_below="@+id/yValue"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</android.support.constraint.ConstraintLayout>

これでスマートウォッチで取得した加速度がリアルタイムでグラフ描画できました!

最後にソースコード

wear

mobile

ActivityMain.java(wear)

package com.example.matu_mio.graphdraw;

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.wearable.activity.WearableActivity;
import android.support.wearable.view.BoxInsetLayout;
import android.support.wearable.view.WatchViewStub;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class MainActivity extends Activity implements SensorEventListener {
    private final String TAG = MainActivity.class.getName();
    private final float GAIN = 0.9f;

    private TextView mTextView;
    private SensorManager mSensorManager;
    private GoogleApiClient mGoogleApiClient;
    private String mNode;
    private float x,y,z;
    int count = 0;
    //final DateFormat df = new SimpleDateFormat("HH:mm:ssSSS");
    private Date date;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mTextView = (TextView) findViewById(R.id.text);
        mTextView.setTextSize(30.0f);

        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                    @Override
                    public void onConnected(Bundle bundle) {
                        Log.d(TAG, "onConnected");

//                        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
                        Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
                            @Override
                            public void onResult(NodeApi.GetConnectedNodesResult nodes) {
                                //Nodeは1個に限定
                                if (nodes.getNodes().size() > 0) {
                                    mNode = nodes.getNodes().get(0).getId();
                                }
                            }
                        });
                    }

                    @Override
                    public void onConnectionSuspended(int i) {
                        Log.d(TAG, "onConnectionSuspended");

                    }
                })
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) {
                        Log.d(TAG, "onConnectionFailed : " + connectionResult.toString());
                    }
                })
                .build();
    }

    @Override
    protected void onResume() {
        super.onResume();

        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
        mGoogleApiClient.connect();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
        mGoogleApiClient.disconnect();
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if(count>= 2) {
            count = 0;
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                //x = (x * GAIN + event.values[0] * (1 - GAIN));
                //y = (y * GAIN + event.values[1] * (1 - GAIN));
                //z = (z * GAIN + event.values[2] * (1 - GAIN));
                x = event.values[0];
                y = event.values[1];
                z = event.values[2];
                if (mTextView != null)
                    mTextView.setText(String.format("X : %f\nY : %f\nZ : %f" , x, y, z));

                //転送セット
                String SEND_DATA = x + "," + y + "," + z;
                if (mNode != null) {
                    Wearable.MessageApi.sendMessage(mGoogleApiClient, mNode, SEND_DATA, null).setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
                        @Override
                        public void onResult(MessageApi.SendMessageResult result) {
                            if (!result.getStatus().isSuccess()) {
                                Log.d(TAG, "ERROR : failed to send Message" + result.getStatus());
                            }
                        }
                    });
                }
            }
        }else count++;
    }

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

    }
}

activity_main.xml(wear)

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.BoxInsetLayout 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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.matu_mio.graphdraw.MainActivity"
    tools:deviceIds="wear">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_world"
        app:layout_box="all" />

    <TextView
        android:id="@+id/clock"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|start"
        android:textColor="@android:color/white"
        app:layout_box="all" />

</android.support.wearable.view.BoxInsetLayout>

MainActivity.java(mobile)

package com.example.matu_mio.graphdraw;

import android.app.ActionBar;
import android.app.Activity;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Wearable;

import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;

public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, MessageApi.MessageListener{
    private static final String TAG = MainActivity.class.getName();
    private GoogleApiClient mGoogleApiClient;
    TextView xTextView;
    TextView yTextView;
    TextView zTextView;
    LineChart mChart;
    int x,y,z;

    String[] names = new String[]{"x-value", "y-value", "z-value"};
    int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        xTextView = (TextView)findViewById(R.id.xValue);
        yTextView = (TextView)findViewById(R.id.yValue);
        zTextView = (TextView)findViewById(R.id.zValue);
        ActionBar ab = getActionBar();
        //ab.hide();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) {
                        Log.d(TAG, "onConnectionFailed:" + connectionResult.toString());
                    }
                })
                .addApi(Wearable.API)
                .build();
        mChart = (LineChart) findViewById(R.id.lineChart);

        mChart.setDescription(null); // 表のタイトルを空にする
        mChart.setData(new LineData()); // 空のLineData型インスタンスを追加

    }

    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
            Wearable.MessageApi.removeListener(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        //getMenuInflater().inflate(R.menu.my, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        //if (id == R.id.action_settings) {
        //    return true;
        //}
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.d(TAG, "onConnected");
        Wearable.MessageApi.addListener(mGoogleApiClient, this);
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.d(TAG, "onConnectionSuspended");

    }

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        xTextView.setText(messageEvent.getPath());
        String msg = messageEvent.getPath();
        String[] value = msg.split(",", 0);

        xTextView.setText(String.valueOf(value[0]));
        yTextView.setText(String.valueOf(value[1]));
        zTextView.setText(String.valueOf(value[2]));

        //x =  Integer.parseInt(value[0]);
        //y =  Integer.parseInt(value[1]);
        //z =  Integer.parseInt(value[2]);


        LineData data = mChart.getLineData();
        if (data != null) {
            for (int i = 0; i < 3; i++) {
                ILineDataSet set = data.getDataSetByIndex(i);
                if (set == null) {
                    set = createSet(names[i], colors[i]);
                    data.addDataSet(set);
                }

                data.addEntry(new Entry(set.getEntryCount(),Float.parseFloat(value[i])), i);
                data.notifyDataChanged();
            }

            mChart.notifyDataSetChanged();
            mChart.setVisibleXRangeMaximum(50);
            mChart.moveViewToX(data.getEntryCount());
        }
    }
    private LineDataSet createSet(String label, int color) {
        LineDataSet set = new LineDataSet(null, label);
        set.setLineWidth(2.5f);
        set.setColor(color);
        set.setDrawCircles(false);
        set.setDrawValues(false);

        return set;
    }
}

activity_main.xml(mobile)

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="com.example.matu_mio.graphdraw.MainActivity">


    <com.github.mikephil.charting.charts.LineChart
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/lineChart"/>


    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/xValue"
        android:layout_below="@+id/textView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/yValue"
        android:layout_below="@+id/xValue"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zValue"
        android:layout_below="@+id/yValue"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</android.support.constraint.ConstraintLayout>

 

参考

Android:グラフ表示で表現豊かに。MPAndroidChart を使ってみよう!|マネーフォワード エンジニアブログ

MPAndroidChartを使って「リアルタイム更新のセンサーデータ時系列グラフ」のサンプルを作ってみた|LyricalMaestro0-Qiita

 

  • このエントリーをはてなブックマークに追加
  • Pocket

SNSでもご購読できます。

コメント

  1. ゆんきち より:

    こんにちは。このAndroidWear側のアプリは公開されていますか?
    またweb bluetooth apiからその値を取得することはできるものなのでしょうか?

    1. mio より:

      ゆんきちさん
      コメントありがとうございます。
      こちらのアプリについては公開しておらず、本記事に掲載しているソースコードのみとなっております。
      また、Web Bluetooth APIについては本サンプルで使用しておらず、申し訳ございませんが分かりかねます。

コメントを残す

*