...

センサーの読み方 - commucom.jp(コミュコム)

by user

on
Category: Documents
166

views

Report

Comments

Transcript

センサーの読み方 - commucom.jp(コミュコム)
第
16
章
センサーの読み方
著:渡辺知男
16-1 Androidでセンサーを使う
著:渡辺知男
LESSON
KEYWORD
リスナー
センサーはAndroidでゲームやARアプリなどを作るため
に重要な要素です。ここでは、Androidのセンサーの概要を
学び、センサーを使う上でのセンサーのフレームワークの使
い方を理解します。
この節を学ぶとできること
Androidに搭載されているセンサーの概要を
理解
Androidに搭載されているセンサーはiPhoneと異なり、端末の
種類によって違います。Androidとセンサーの関係を理解し整理
します。
Androidに実装されているセンサーの値を読み
取る
Androidには様々なセンサーが搭載されていますが、その利用
方法はフレームワークを利用することによって比較的簡単に扱える
ようになっています。センサーは色々な種類があるのですが、フレー
ムワークを利用することで抽象化されるというのがポイントです。この
手法はプログラムを書く上で応用範囲が広い考え方なので、理解
しておくと役に立ちます。
82
イベントコールバック
センサーマネージャ
16 -1-1
センサーとは
一般にセンサーというと、加速度センサーやGPSセンサーといったものを思い浮か
べるでしょう。こういったセンサーは身近にも使われていて、たとえば車のエアバッグ
が開く仕組みには加速度センサーが利用されており、ある程度のマイナス加速度
第
が発生した瞬間にエアバッグを膨らませるようになっています。カーナビには自車位
16
置を常に捕捉するためにGPSが搭載されています。
動的に点くようになっているのは、人感センサーを使っています。このようにセンサー
セ
ン
サ
ー
の
は意外と身近なところで使用されています。
み
章
また、暗い廊下などで節電のために蛍光灯などが切ってあっても、人が通ると自
最近のセンサーはMEMSで構成され、超小型化されているのが特徴です
(図
23)。
読
MEMS(Micro Electro Mechanic
al Systems)とは、電子回路やセン
サー、アクチュエータなどを複合化し
たデバイスとなっていて、一つのデバ
イス内部に複数の機能を用意するこ
とが可能
図23:半導体プロセスを用いて作成さ
れたギア
(左下)
とダニ
(右上)
の電子顕
微鏡写真(Wikipediaより)
Androidには複数のハードウェアセンサーが搭載されていますが、さらにそれらを
まとめたフュージョンセンサーというものもあります。これは、複数の物理的なセンサー
の値をソフトウェアで計算し、合成することで、より正確な値が得られるようにするもの
です。また、ハードウェアからは直接得られない内容のセンサーとして機能します。
16 -1-2
Android で使用可能なセンサー
AndroidではAPIレベルが上がるたびに使用可能なセンサーの種類が増えて
います。現在の対応はリファレンスを参照してください。
Androidでは多くのセンサー情報が取得できますが、実際のハードウェア
(セン
http://developer.android.com/
reference/android/hardware/
Sensor.html
83
方
サー)
がその数の分だけ搭載されているわけではありません。ハードウェア1つで複
数のセンサー情報に対して情報を提供するように実装されていることが多いです。
また位置情報を取得するためのGPSセンサーについては、他のセンサーとは異な
りGoogle Play Servicesで提供されるライブラリを使用して取得します。まずは
GPS以外のセンサーから説明していきます。
リファレンスによると、現在のAPI Level 21で使用可能なセンサーの一覧は表
2のようにになります。
センサーの種類
説明
TYPE_ACCELEROMETER
加速度センサー
TYPE_AMBIENT_TEMPERATURE
周囲の温度
TYPE_MAGNETIC_FIELD
地磁気センサー
TYPE_GYROSCOPE
ジャイロスコープ
(角速度)
TYPE_HEART_RATE
心拍数
TYPE_LIGHT
照度センサー
TYPE_PROXIMITY
近接センサー
TYPE_PRESSURE
気圧センサー
TYPE_RELATIVE_HUMIDITY
相対湿度センサー
TYPE_GAME_ROTATION_VECTOR
回転ベクトルセンサー(地磁気影
響を除外)
TYPE_GEOMAGNETIC_ROTATION_VECTOR
地磁気回転ベクトルセンサー
TYPE_GRAVITY
重力センサー
TYPE_ROTATION_VECTOR
回転ベクトルセンサー
TYPE_GYROSCOPE_UNCALIBRATED
ジャイロスコープ
(生データ)
TYPE_MAGNETIC_FIELD_UNCALIBRATED
地磁気センサー
(生データ)
TYPE_LINEAR_ACCELERATION
加速度センサー
(重力を除外)
TYPE_SIGNIFICANT_MOTION
動き検知
TYPE_STEP_COUNTER
歩数計
TYPE_STEP_DETECTOR
歩行検知
表2:センサー一覧
使用可能なセンサーの概要
API Level 21で規定されているセンサーをざっと整理してみましょう。
加速度センサー (Acceleration sensor)
です
(図24)
x軸、y軸、z軸のそれぞれの加速度を表します。単位は
(m/s 2)
84
第
16
章
セ
ン
サ
ー
の
読
み
方
図24:加速度センサーの軸
周囲温度センサー (Temperature Sensor)
端末の周囲の温度を表します。単位は
(℃)
です。
地磁気センサー (Geomagnetic field sensor)
x軸、y軸、z軸方向の磁気の強さを表します。単位は
(μT)。磁気の強さから
方位を測定できます。
地磁気センサー
(生データ)
(Uncalibrated Magnetometer)
x軸、y軸、z軸方向の磁気の強さをキャリブレーション無しの生データで表しま
す。単位は
(μT)
です。
ジャイロスコープ (Gyroscope)
x軸、y軸、z軸の回転の速度、角速度を表します。単位は
(rad/s)
で、たとえば
時計の秒針の角速度は、60秒で一回転(360度)
なので、6度/秒です。rad =
度 ×π/180となります
(図25)
図25:角速度
85
心拍センサー
(Heart Rate)
1分間の心拍数を表します。単位は
(回/分)
です。心拍センサーでは精度が重要
なのでAcuuracyがSENSOR_STATUS_UNRELIABLEの場合と、SENS
OR_STATUS_NO_CONTACTの場合はセンサーの値を使用しないようにしま
す
(Android Wear用)。
照度センサー (Light)
周囲の明るさを表します。単位は
(lx)
です。
近接センサー (Proximity Sensor)
端末の前面との距離を表します。単位は
(cm)
です。ただし
「near」
と
「far」の
2値しか返さない端末もあります。その場合は「near」が最小値となり
「far」が最大
値となります。
気圧センサー (Pressure)
周囲の気圧を表します。単位は
(hPa)
です。
相対湿度センサー (Humidity Sensor)
周囲の湿度を表します。単位は
(%)
です。
回転ベクトルセンサー (Rotation Vector Sensor)
回転ベクトルセンサーは、傾きセンサーよりも精度が高く、同様に傾きを取得でき
ます。
回転ベクトルセンサー(地磁気影響を除外) (Game Rotation Vector
Sensor)
回転ベクトルセンサーから地磁気の影響を除外したものを表します。ゲームでの
利用を想定しています。多くのゲームにおいて方位が不要なためです。
地磁気回転ベクトルセンサー (Geomagnetic Rotation Vector Sens
or)
回転ベクトルセンサーとほぼ同じものですが、ジャイロスコープの代わりに地磁気
センサーを使用しています。回転ベクトルセンサーよりも精度は落ちますが低消費電
力です。バックグラウンドでの動作に使われます。
重力センサー (Gravity sensor)
です。
重力加速度を表します。単位は
(m/s 2)
86
ジャイロスコープ(生データ)(Uncalibrated Gyroscope)
x軸、y軸、z軸の回転の速度、角速度を温度ドリフトなどを補正しない、生データ
を表します。単位は
(rad/s)
です。
加速度センサー
(重力を除外)(Linear acceleration sensor)
第
16
x軸、y軸、z軸のそれぞれの加速度を重力加速度を差し引いて表します。単位は
章
です。
(m/s )
2
セ
ン
サ
ー
の
動き検知 (Significant Motion Sensor)
読
み
端末を持ったユーザーが動いたことを検知します。たとえば、歩く、自転車、座る、
車で移動など。このセンサーはワンショットのトリガー起動なので、
「TriggerEvent
ワンショットトリガー起動とは、動きを
検知したとき1度だけ起動する動作
のこと。
Listener」を使用します。
歩数計 (Step Counter Sensor)
端末がリブート起動してからの歩数を表します
(全アプリで共通)。
「歩いた歩
数」が重要な場合に使用します。
歩行検知 (Step Detecter Sensor)
端末を持ったユーザーが歩行中であることを検出します。
「歩いたタイミング」が
重要な場合に使用します。
傾きセンサー (Orientation Sensor)
端末の傾きを検出します。方位角と傾斜角と回転角を表します。
16 -1-3
Android で使用可能なセンサー2
センサーの情報を取得するには、Androidで用意されているフレームワークを使
います。特殊なセンサーを除けば、基本的な実装方法は変わりません。センサー情
報取得のフレームワークと、
センサー情報の取得方法を見ていくことにします。
87
方
16 -1-4
センサー情報取得フレームワーク
センサーの利用のために、便利なフレームワークが用意されています。使用する
クラスは次のとおりです。
・ Sensor
・ SensorManager
・ SensorEventListener
・ SensorEvent
使い方としては、SensorManagerをシステムから取得し、使用するセンサーを指
定します。センサーイベントを受け取るように設定すると、SensorEventListenerで
設定したリスナーでSensorEventを受け取れるので、これを解析するという流れに
なります。では具体的に見ていきます。
まずはSensorManagerをシステムから取得します。
センサーマネージャの取得
private SensorManager mSensorManager;
...
mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
例として、Acceleration( 加速度センサー)
を取得します。
センサーの取得
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
次にセンサーを有効にします。
センサーの有効化
mSensorManager.registerListener(this, acceleration, Sensor.SEN
SOR_DELAY_NORMAL);
これで、該当するActivityにSensorEventListenerが設定され、イベントリス
ナーが呼ばれるようになります。
SensorEventListenerの実装を見ていきましょう。ここではリスナーを2つ用意し
ます。
88
イベントコールバック
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// 処理
}
@Override
第
16
public final void onSensorChanged(SensorEvent event) {
章
// 処理
SensorEventListener#onSensorChangedはセンサー値が変化した時に
セ
ン
サ
ー
の
呼ばれます。SensorEventListener#onAccuracyChangedはセンサーの精
み
}
読
方
度が変化した時に呼ばれます。たいていの場合、SensorEventListener#onSe
nsorChangedを実装すれば問題ありません。実際のところ、センサーの精度変化
はほとんど起きないからです。
基本的な流れは以上になりますが、実際に使用する場合の実装は次のようにな
ります。
1. onCreateもしくはonResumeでSensorManagerを取得する
2. onResumeでセンサーを有効化する
3. onPauseでセンサーを無効化する
センサーの有効化と無効化があるのは、必要な時のみセンサー情報を使用する
ようにするためです。SensorManager#getDefaultSensorでセンサーを取得し、
S e n s o r M a n a g e r # r e g i s t e r L i s t e n e rで 有 効 化します 。 無 効 化 は
SensorManager#unregisterListenerで行えます。
センサー情報を長い間使用すると電池を消費してしまうため、
アプリのライフサイク
ルを意識して利用時を定めます。そのため、たいていのアプリでは、onResumeでセ
ンサーの取得を有効化し、onPauseで無効化するパターンとなります。もちろん、こ
れが絶対ではなく、Serviceを使いバックグラウンドで取得し続けるなどの使用方法
もあるので、
アプリに応じて電池の消費量をできるだけ抑えるように設定することになり
ます。
実装をまとめると次のようになるでしょう。
89
コード例
public class SensorActivity extends ActionBarActivity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mAcceleration;
private TextView[] mSensor = new TextView[3];
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// センサーマネージャの取得
mSensorManager =
(SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor[0] = (TextView) findViewById(R.id.sensor_0_text);
mSensor[1] = (TextView) findViewById(R.id.sensor_1_text);
mSensor[2] = (TextView) findViewById(R.id.sensor_2_text);
}
@Override
protected void onResume() {
super.onResume();
// 使用するセンサーの設定
Sensor acceleration =
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// センサーを有効にする
mSensorManager.registerListener(this,
acceleration, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
// センサーを無効にする
if (mSensorManager != null) {
mSensorManager.unregisterListener(this);
}
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// センサーの精度が変化した場合に呼ばれる
}
@Override
public final void onSensorChanged(SensorEvent event) {
// センサーの値が変化した場合に呼ばれる
}
}
ここまでが基本的なセンサーのフレームワークの実装になります。onSensorCha
ngedは、センサーの値を取得した時、あるいは変化した時に呼ばれますが、呼ば
れ方は遅延設定のパラメータによってタイミングが異なります。次からはパラメータ
設定と実際のセンサー値を取得していきます。
90
センサー取得の遅延設定
前述のソースコード「センサーの有効化」の「SensorManager.SENSOR_
DELAY_NORMAL」はセンサーの取得の遅延設定値です。この設定値は使
用する目的によって設定します
(表3)。
第
16
遅延設定の種類
章
説明
SENSOR_DELAY_FASTEST
変 化 が あり次 第 、た だち に 取 得する( 遅 延 時 間
0ms)
SENSOR_DELAY_GAME
ゲーム利用に適している(遅延時間20ms程度)
SENSOR_DELAY_NORMAL
ス クリーン の 向き 変 更 に 適して い る( 遅 延 時 間
60ms程度)
SENSOR_DELAY_UI
ユーザーインタフェースへの利用に適している(遅
延時間200ms程度)
セ
ン
サ
ー
の
読
み
方
表3:センサーの遅延設定
16 -1-5 センサー値の取得
それでは実際のセンサーの値を取得してみます。センサーのデータはコールバッ
ク内で取得します。センサーデータはSensorEventに入っています。SensorEve
ntには次のデータが入っています
(表4)。
データ
説明
accuracy
精度
sensor
センサーオブジェクト
timestamp
イベントの発生した時間
values
センサーデータ配列
表4: SensorEventのセンサーデータ
センサーデータはvaluesに入っていますが、配列の数と意味は使用するセン
サーによって異なります。使用するセンサーによるデータの意味はリファレンスで確認
しましょう。
http://developer.android.com/
guide/topics/sensors/sensors_
motion.html
今回の加速度センサーの場合は次のようになります。valuesの配列サイズは
「3」です。また型はfloatなのでint等と間違えないように注意して下さい。
・ SensorEvent.values[0] : x軸加速度
・ SensorEvent.values[1] : y軸加速度
・ SensorEvent.values[2] : z軸加速度
加速度なので、そのままx、y、zそれぞれの加速度が取得できます。これをコード
91
で取得するには、onSensorChangedを次のとおりに変更します。
センサー値変化イベントコールバック
@Override
public final void onSensorChanged(SensorEvent event) {
// センサーの値が変化した場合に呼ばれる
float[] accell = new float[3];
accell[0] = event.values[0];
accell[1] = event.values[1];
accell[2] = event.values[2];
Log.i(TAG, "accell[X] " + accell[0]);
Log.i(TAG, "accell[Y] " + accell[1]);
Log.i(TAG, "accell[Z] " + accell[2]);
mSensor[0].setText(String.valueOf(accell[0]));
mSensor[1].setText(String.valueOf(accell[1]));
mSensor[2].setText(String.valueOf(accell[2]));
}
ここではログにセンサー値を表示するようにしています。
実際のデータを取得した例としては次のようなデータが取得できます
(図26)。
図26:加速度センサーの取得
端末をほぼ自分に向けた状態では、y軸の加速度が重力加速度の約9.8に近
い値になっています。実際に動作させると目まぐるしく値が変化します。これは遅延
設定の値によって変化の速さが変わります。端末を振ったりすれば当然値も変化
するので、遅延設定を変えてみたりして、数値の変化の度合いを確認してみてくだ
さい。
92
16 -1-6 複数センサーを取得
複数のセンサーを利用したい場合はどうなるでしょうか。その場合はセンサーを複
数設定することで可能です。次の例は「TYPE_ALL」で一旦すべてのセンサーを
リストとして取得し、
リストから使用するものだけを選択して登録しています。
第
16
複数のセンサーを使用 onResumeでのセンサー登録
章
// センサーの取得
セ
ン
サ
ー
の
List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
み
@Override
protected void onResume() {
super.onResume();
読
方
// センサーマネージャへリスナーを登録
for (Sensor sensor : sensors) {
if (sensor.getType() == Sensor.TYPE_PROXIMITY) {
mSensorManager.registerListener(this,
sensor, SensorManager.SENSOR_DELAY_UI);
}
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mSensorManager.registerListener(this,
sensor, SensorManager.SENSOR_DELAY_UI);
}
}
}
ただし、取得したデータを処理する場合は、次のように場合分けする必要があり
ます。
複数センサー利用時のイベントコールバック
@Override
public void onSensorChanged(SensorEvent event) {
// 値が変化したセンサーが加速度センサーだった場合
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// 処理
}
// 値が変化したセンサーが近接センサーだった場合
else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
// 処理
}
}
複数のセンサーを使用する例として、後述する傾きを取得する場合があります。
なぜなら傾きは方位と加速度を使用して計算する必要があるからです。
端末の傾きを利用することで、端末の中のオブジェクトをさまざまな角度から見られ
るように変化させたり、さらに位置情報とカメラを組み合わせて、特定の場所にオブ
ジェクトを出現させるようなARアプリの作成ができるようになります。
93
16 -1-7 傾きセンサーの取得
API Level 8以降では傾きセンサー(TYPE_ORIENTATION)は非推奨と
なっており、代わりにSensorManager#getOrientationメソッドの仕様が推奨さ
れています。
傾きは、方位角と傾斜角、回転角で表されます。傾きは「地磁気センサー」
と
「加速度センサー」から計算して求めます。前述の複数センサーの取得方法を使
います。
傾きは端末の縦横が変わると変わってしまうので、縦に固定しておきます。Andr
oidManifest.xmlにandroid:screenOrientation="portrait"を追記します。
縦固定にする
<activity android:name=".orientationSensorActivity"
android:label="@string/app_name"
android:screenOrientation="portrait">
あとは定石どおり、
センサーマネージャの登録を行いますが、
センサーの登録として
「TYPE_ACCELEROMETER」
と
「TYPE_MAGNETIC_FIELD」を使用
します。onPauseでの登録破棄も忘れないようにしてください。
センサーの登録
@Override
protected void onResume() {
super.onResume();
// センサーの取得
List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
// センサーマネージャへリスナーを登録
for (Sensor sensor : sensors) {
if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mSensorManager.registerListener(this,
sensor, SensorManager.SENSOR_DELAY_UI);
}
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mSensorManager.registerListener(this,
sensor, SensorManager.SENSOR_DELAY_UI);
}
}
}
http://developer.android.com/
reference/android/hardware/
SensorManager.html
センサーの取得はonSensorChangedで行います。この計算の詳細は省きます
がリファレンスのSensorManager#getRotationMatrixを参照してください。
ソースコードは次のとおりです。
94
傾きを求めるイベントコールバック
@Override
public void onSensorChanged(SensorEvent event) {
// 精度の低いデータは捨てる
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
return;
第
16
// 地磁気センサー、加速度センサーの値を取得
章
switch (event.sensor.getType()) {
セ
ン
サ
ー
の
case Sensor.TYPE_MAGNETIC_FIELD:
mGeomagnetic = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
読
み
mAcceleration = event.values.clone();
方
break;
}
// 両方のデータが揃ったら計算を行う
if (mGeomagnetic != null && mAcceleration != null) {
SensorManager.getRotationMatrix(inR, I, mAcceleration, mGeomagnetic);
// Activityの表示が縦固定で、端末表面が自分を向いている場合
SensorManager.remapCoordinateSystem(inR,
SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);
SensorManager.getOrientation(outR, mOrientation);
//
radianToDegree(mOrientation[0])
Z軸方向, Azimuth
//
radianToDegree(mOrientation[1])
X軸方向, Pitch
//
radianToDegree(mOrientation[2])
Y軸方向, Roll
mSensor[0].setText(String.valueOf(radianToDegree(mOrientation[0])));
mSensor[1].setText(String.valueOf(radianToDegree(mOrientation[1])));
mSensor[2].setText(String.valueOf(radianToDegree(mOrientation[2])));
}
}
これで求められる値は次のとおりです。
・ Z軸方向, 方位角 : 北を0度とする方位
・ X軸方向, 傾斜角 : 端末の縦方向の仰角
・ Y軸方向, 回転角 : 端末の横方向の傾き
これらは方位、傾斜、角度をそれぞれ表しています。図27の概念を言葉のみで
理解することは困難ですので、ぜひ動かしてみてください。
センサーの大半は動かして学習することで理解できます。
95
図27:傾きセンサー
16 -1-8 センサーのハードウェア情報の取得
端末に搭載されているセンサーをすべて列挙して取得し、センサーのハードウェ
ア情報を取得してみます。センサーから取得できるハードウェア情報は次のとおりで
す
(表5)。
型
メソッド
int
getFifoMaxEventCount()
センサーイベントに保管でき
るFIFOの数
int
getFifoReservedEventCount()
バッチモードで使用したとき
のFIFO内のデータ数
float
getMaximumRange()
センサー値の最大値
int
getMaxDelay()
センサーの最大遅延時間
(μs)
API21
int
getMinDelay()
センサーの最小遅延時間
(μs)
String
getName()
float
getPower()
int
getReportingMode()
セン サ ー の 取 得 頻 度 の 取 得
API21
float
getResolution()
センサー解像度
int
getType()
センサーのタイプ
String
getVendor()
センサーのベンダ名
int
getVersion()
センサーのバージョン
boolean
isWakeUpSensor()
センサーがスリープ時にも動
作可能か API21
表5:センサーのハードウェア情報
96
意味
センサーの名前
センサー使用時の電力消費量
(mA)
全センサーの取得には「Sensor.TYPE_ALL」を指定すると、センサーのリスト
として取得できます。ログとして表示するようにしてみたのが次のコードになります。
センサーリストの取得
第
16
// センサーのオブジェクトリストを取得する
章
mSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
Log.v("SENSOR", sensor.getFifoReservedEventCount());
セ
ン
サ
ー
の
Log.v("SENSOR", sensor.getMaximumRange());
み
// センサーリストから個別のセンサーを取得
for (Sensor sensor : mSensors) {
Log.v("SENSOR", sensor.getFifoMaxEventCount());
読
方
Log.v("SENSOR", sensor.getMinDelay());
Log.v("SENSOR", sensor.getName());
Log.v("SENSOR", sensor.getPower());
Log.v("SENSOR", sensor.getResolution());
Log.v("SENSOR", sensor.getType());
Log.v("SENSOR", sensor.toString());
Log.v("SENSOR", sensor.getVendor());
Log.v("SENSOR", sensor.getVersion());
}
次のスクリーンショットは見やすくするため、アプリ画面で表示させています
(図
28、図29)。
図28:加速度センサー
図29:近接センサー
これはNexus 5でのセンサーのハードウェアの例です。Nexus 5では18種類も
のセンサーが搭載されていることも、
センサーリストの数を取得するとわかります。
97
16 -1-9 センサー利用上の注意
ここまででセンサー情報の取得は、非常に簡単にできることがわかりました。またセ
ンサーのハードウェア情報を取得することもできました。しかしセンサーを使う上での
注意事項として、次の点に留意する必要があります。
・ 機種によって搭載されるセンサーの種類が異なる
・ 機種によって搭載されるセンサーのハードウェアが異なる
・ センサーの数値の上限下限が機種によって異なる
それぞれに対して、対応を考える必要があります。
搭載センサーの違い
たとえば、
「Sensor.TYPE_STEP_COUNTER」を使ったアプリを作りたい場
合、
このセンサーを搭載していない機種には対応ができません。
そういった場合には対応する機種を制限することができます。AndroidManife
st.xmlファイルに使用するハードウェアを設定することで、未搭載の端末に対しては
Google Playからのダウンロードを抑止できます。
たとえば、歩数計のセンサーを必須とする場合はAndroidManifest.xmlに次
のように追加します。
AndroidManifest.xml
<uses-feature
android:name="android.hardware.sensor.stepdetector"
android:required="true" />
uses-feature要素を追記し、stepdetectorを持っている機種のみにインストー
ルを許可します。
センサーのハードウェアの違い
センサーのハードウェア差異は、実は実装の段階であまり気にすることはありませ
ん。ただし、端末がすべて1社で製造されているiPhoneとは異なり、実際に搭載さ
れているハードウェアは機種によって違うので、センサーの感度や精度が大きく違う
場合があります。
98
センサーの数値の上限下限の違い
センサーのハードウェアが違えば、精度や上限・下限も異なる場合があります。セ
ンサーの値の範囲を気にする必要がある場合もあります。たとえば、さきほどのNex
us5と安価な端末を近接センサーで比較してみると、次のようになっています
(図
第
16
30)。
章
セ
ン
サ
ー
の
読
み
方
図30:安価な端末の場合
図29と比較すると、MaxRangeが異なります。このように端末によって細かい部
分が異なる場合があるので、複数の端末で正常に動作するかどうかきちんと確認す
る必要があります
(一部の安価な端末ではAndroid OSのバージョンが低く、Max
Fifo、ReservedFifoは存在しないこともあります)。
このような場合は、機種ごとに対応したり、値を正規化して相対値として使用でき
るか個別に検討が必要です。
16 -1-10 センサーの種類と系統
センサーの種類は多く、APIレベルが上がるにつれて増えてきています。ここでは
センサーの種類と系統をまとめておきます。
センサーの種類をハードウェアでカテゴリで分けると、
99
・ 基本
・ 状態
・ 生データ
・ 動き
基本は実際に搭載されているセンサーを意味していて、
それぞれのセンサーデー
タを取得します。
それ以外のセンサー値については、基本を組み合わせて生成したものと、一部
生データを提供するものに分類できます
(もちろんハードウェアとして実装されていて
も構わないはずです)。
https://source.android.com/de
vices/sensors/index.html
センサーのカテゴリを相関図として整理してみましょう
(図31)。
図31:センサーのカテゴリ分け
こうして整理してみることで、それぞれのセンサー値がどのように合成されているか
わかります。ただし、ここにある基本のセンサーでも搭載されていない機種があるの
で、基本だからといってセンサー値が取得できるとは限りません。
100
これを表にまとめると次のとおりです
(表6)
カテゴリ
基本
状態
生データ
動き
Type
説明
トリガーモード
Accelerometer
加速度センサー
連続
Ambient temperature
周囲の温度
Gyroscope
ジャイロセンサー
(角速度)
Light
照度センサー
Geomagnetic field
地磁気センサー
連続
Pressure
気圧センサー
連続
Proximity
近接センサー
変化時
セ
ン
サ
ー
の
Relative humidity
相対湿度
変化時
み
Game rotation vector
Accelerometer+Gyroscope
連続
Geomagnetic rotation vector
Accelerometer+Magnetometer
連続
Gravity
Accelerometer+Gyroscope
連続
Orientation
Accelerometer+Magnetometer(+Gyroscope)
連続
Rotation vector
Accelerometer+Gyroscope+Magnetometer
連続
Gyroscope uncalibrated
Gyroscope
連続
Magnetic field uncalibrated
Magnetometer
連続
linear acceleration
Accelerometer+Gyroscope+Magnetometer
連続
Significant motion
Accelerometer
1回のみ
Step counter
Accelerometer
変化時
Step detector
Accelerometer
特別
変化時
連続
第
16
章
変化時
読
方
表6:センサーのまとめ
トリガーモードとは、センサーの変化をコールバック関数でどのように伝えるかを示
します。
「連続」はセンサーの変化にかかわらず一定の間隔でコールバック関数が呼ば
れます。
「変化時」は変化した時のみ、コールバック関数が呼ばれます。
「1回の
み」は1回だけしかコールされません。
「特別」
となる
「Step detector」は歩行検
知時に呼ばれるので、実際には「変化時」
と言ってもよさそうです。
また、
ワクで囲ったセンサーは低消費電力動作可能です。
16 -1- 11 センサーのバッチ処理
センサーは非常に便利ですが、使うと消費電力が大きいのがデメリットです。そこ
でAndroidでは、センサーのデータを複数回分貯めこんでおき、バッチ処理で通
知する仕組みがあります。
バッチ処理が実行されるまでは、センサーは低消費電力な状態を維持できるの
で電池の消費を抑えることができます。このバッチ処理の間隔は設定ができます。
バッチモードは、スクリーンがオフの状態やシステムがスリープ中でも動作するた
101
め、フィットネスや位置情報の追跡や監視などに有効な手段となります。Android
4.4からは歩行検出センサーと、歩数計センサーが使用可能になっており、特にこ
の辺りのセンサーを使用する場合に有効な手段となります。
バッチモードを使用するには、LocationManager#registerListenerメソッド
で指定します。通常のセンサーのメソッドにバッチ処理時間が引数で増えています。
次の例はバッチ処理として1000 * 1000 * 10 μs = 10sに設定した場合です。
バッチモード設定
private Sensor mStepDetector;
...
boolean status = mSensorManager.registerListener(this,
mStepDetector, SensorManager.SENSOR_DELAY_NORMAL, 10000);
ただし、バッチ処理が可能かどうかはセンサーの対応によるので、対応している
場合のみ戻り値statusにtrueが返ります。
注意点としては、バッチモードで使用されるデータバッファのFIFO(First in Fi
rst Out)はすべてのアプリで共通になるため、
「他のアプリで通常のセンサーを使
用されてしまうと自分のアプリで思ったようにバッチモードが動かない」
という場合が
あります。
演習問題
Andorid端末のセンサーの一覧を取得してリストビューで表示してみましょう。
102
16-2 AndroidでGPSを使う
著:渡辺知男
第
16
LESSON
章
KEYWORD
セ
ン
サ
ー
の
リスナー
GPSセンサーはAndroidで位置情報アプリやマップと合
わせて使うことで威力を発揮します。 GPSもセンサーと同様
イベントコールバック
読
パーミッション
み
方
フレームワークが用意されています。 GPSとはどういう仕組
みなのかも同時に学びます。
この節を学ぶとできること
GPSを使って位置情報を取得する方法を理解
GPSについてもセンサーと同じようにフレームワークが用意さ
れています。
Google Play Servicesの導入方法を理解
GPSを簡単に利用するためにGoogle Play Servicesの
導入を行います。
103
16 -2 -1 GPS センサーの概要
重要なデバイスとしてGPSセンサーがあります。GPSは「Global Positioning
Systemの略で、アメリカが打ち上げた高度約2万キロに位置する複数の軍事用
衛星からの電波を受信することで、現在位置を正確に知ることができます。
正確にはセンサーというよりはGPSからの電波を受信する受信機になります。
ちょう
ど電波時計のように電波を受信して時計を合わせる。というのと同じです。日本で
受信可能なGPS衛星の数は6∼10個程度です。位置測位で緯度と経度を求め
るには3つの衛星を捕捉する必要があり、高度を含んだ3次元測位を行うには4つ
以上の衛星の捕捉が必要になります。
測位は三角測量の概念で説明できるのですが、GPSでは距離の計算に時間を
利用しています。電波は光速であることから、GPS衛星の距離を送信時刻と受信
時刻との差に速度を掛けて求めるわけです。しかしGPS衛星には原子時計が積ま
れていますが、受信機にはそのような正確な時計がありませんので、代わりにもう1基
のGPS衛星の時計を利用します。すなわち未知数として、位置(x, y, z)と時刻tの
4つを得るためには4つ以上の衛星の情報が必要ということになります
(図32)。
図32:GPS衛星による位置測位のイメージ
地球表面は2次元としても構わないように思えますが、3次元としての高度が必要
な理由は高い山などに登ると、登った距離と実際の水平方向の距離にズレが生じ
てしまうので、カーナビなどではこれを考慮するために3次元測位を行っています
(図
33)。
104
第
16
章
セ
ン
サ
ー
の
読
み
方
図33:高度が必要な理由
GPSは受信精度が高ければ、正確な位置を10m程度の誤差で測位できます
が、
いくつかの弱点もありますので留意して下さい。
マルチパスとは、電波を受信する際
にビルなどの反射により同じ電波を
時間差で複数受信してしまう現象。
かつてのアナログテレビの時のゴース
トと呼ばれる現象と同じです。
・ 高層ビルの間など空がひらけていない場合、十分な衛星を捕捉できない、
あるいはマルチパスの影響を受けるため、都心部ではむしろ位置精度が
落ちることがある
・ 衛星の捕捉と位置検出には、60秒以上の時間がかかる場合がある
check!
日本独自のGPS衛星「みちびき」
日本では独自GPS衛星として
「みちびき」
の打ち上げに成功し、今後4機での本格運
用が計画されています。これは
「準天頂衛星システム」
といい、従来のGPSを補完する
ため、常に1機は日本上空にあって、マルチパスの影響を受けず正確な位置を測位す
ることが可能になります。みちびきを利用するには、端末のGPSのファームウェアの
アップデートが必要になります。アプリを作成するには特に意識する必要はありませ
ん。
JAXAの「みちびき」サイト
http://www.jaxa.jp/projects/sat/qzss/index_j.html
16 -2 -2 GPS を利用した位置情報の取得
GPSを利用して位置情報を取得しますが、これは通常のセンサーと異なりGoog
le Play Servicesライブラリを導入して、
この中にあるLocationClientを使って取
得します。
105
16 -2 -3 Google Play Services Library の導入
Android Studioの左のペインの"Gardle Scripts"から"build.gradle(Mo
dule.app)"を開きます。
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
defaultConfig {
applicationId "com.techbooster.location"
minSdkVersion 18
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.google.android.gms:play-services:6.1.71'
<-- この1文を追加
}
Gradleのファイルを手動で編集した場合、Gradleの同期更新を行う必要があ
ります
(図34)。
編集して保存すると、"Sync Now"というメッセージが出ますので、
それをクリック
するか、GradleのSyncボタンをクリックしてGradleを同期更新します。
図34:gardlesync.png
これでGoogle Play Servicesがアプリのビルド時に組み込まれるようになります。
106
16 -2 -4 位置情報精度の設定
GPSを使用して精度を上げたい場合はGPS機能を有効にする必要があります。An
droidアプリの「設定」
を起動して、次の手順で設定を行っておきましょう
(図35)
。
第
16
章
セ
ン
サ
ー
の
読
み
方
図35:GPS設定
「位置情報サービス」の「GPS機能を使用」のチェックをオンにすることで、GPSが
有効になります。通常GPSを使う場合には高精度にしておきましょう。
16 -2 -5 位置情報の取得
GPSを利用した位置情報取得にはフレームワークが用意されているのでそれを
使います。使用するクラスは次のとおりです。
・LocationClient
・Location
使い方としては、LocationClientを生成しておきます。Google Play Services
への接続が成功すると、位置情報はバックグラウンドで取得され、LocationClien
t#getLastLocationに現在の位置情報が非同期に更新されるので、
この値を使
用します。
107
Google Play Servicesを使わずにGPSやWiFiを使って位置情報の取得も
可能ですが、GPSやWiFiのON/OFFを検出したり、GPSとWiFiのステータスを
管理しなければならず、かなり面倒なのが実際です。
Google Play ServicesのLocationClientを使うと、
「一番良い位置情報を
頼む」
ということができ、実際にはGPS/WiFiの区別を意識することはありません。
LocationClientの取得は次のようになります。
LocationClientの取得
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Google Play Servicesが有効かどうかチェックを行う
final int result = GooglePlayServicesUtil.
isGooglePlayServicesAvailable(this);
if (result != ConnectionResult.SUCCESS) {
Toast.makeText(this,
"Google Play Services is not available (status=" + result + ")",
Toast.LENGTH_LONG).show();
finish();
}
// LocationClientの取得
mLocationClient = new LocationClient(this, this, this);
}
LocationClientを使用する場合には、2つのコールバックを引数に取るので、次
のようにリスナーをActivityに設定しておきます。
リスナーの設定
public class MainActivity extends ActionBarActivity
implements ConnectionCallbacks, OnConnectionFailedListener
設定されるコールバック関数は3つで、OnConnectionFailedListenerは接
続に失敗した時に呼ばれ、次のようになります。
接続失敗時のイベントコールバック
@Override
public void onConnectionFailed(ConnectionResult result) {
// Google Play Servicesの接続に失敗
}
ConnectionCallbacksは接続時と切断時に呼ばれ、次のようになります。
108
接続時・切断時のイベントコールバック
@Override
public void onConnected(Bundle connectionHint) {
// Google Play Servicesに接続した
}
@Override
第
16
public void onDisconnected() {
章
// Google Play Servicesと切断した
セ
ン
サ
ー
の
}
実装ではGoogle Play Servicesへの接続と切断を明示的に行う必要がありま
読
み
す。この辺りは通常のセンサーと同じように実装できます。
方
・onCreateまたはonResume:LocationClientを取得する
・onResume:LocationClient#connectで接続する
・onPause:LocationClient#disconnectで切断する
・位置情報の取得:LocationClient#getLastLocationを利用する
また、気をつける点としてLocationClient#connectは非同期関数で、onCon
nectedコールバックが呼ばれてからでないとLocationClient#getLastLocati
onを呼ぶことができません。
以上をまとめると、次のような実装になります。
GPSを使うコード
public class MainActivity extends ActionBarActivity
implements ConnectionCallbacks, OnConnectionFailedListener {
private LocationClient mLocationClient;
private Location mLoc;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Google Play Servicesが実装されているか確認
final int result =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (result != ConnectionResult.SUCCESS) {
Toast.makeText(this,
"Google Play Services is not available (status=" + result + ")",
Toast.LENGTH_LONG).show();
finish();
}
109
mLocationClient = new LocationClient(this, this, this);
}
@Override
protected void onResume() {
super.onResume();
// Google Play Servicesへの接続
mLocationClient.connect();
}
@Override
protected void onPause() {
super.onPause();
// Google Play Servicesとの切断
if (mLocationClient != null) {
mLocationClient.disconnect();
}
}
@Override
public void onConnectionFailed(ConnectionResult result) {
// Google Play Servicesへの接続に失敗した
Toast.makeText(this, "onConnectionFailed", Toast.LENGTH_LONG).show();
Log.d("ERROR", result.toString());
}
@Override
public void onConnected(Bundle connectionHint) {
Toast.makeText(this, "Connected", Toast.LENGTH_LONG).show();
// 位置情報の取得
mLoc = mLocationClient.getLastLocation();
Log.d("LOCATION", "LAT: " + mLoc.getLatitude());
Log.d("LOCATION", "LON: " + mLoc.getLongitude());
}
@Override
public void onDisconnected() {
Toast.makeText(this, "Disconnected", Toast.LENGTH_LONG).show();
}
}
これでソースコードは完成ですが、
しかしまだこれだけでは動作しません。
GPSを使うにはパーミッションが必要であることと、Google Play Servicesライブ
ラリを使うことを宣言する必要があります。
AndroidManifest.xmlにGoogle Play Servicesを使うための宣言を追加し
ます。Google Play Servicesの利用宣言はAndroidManifest.xml内の<app
lication>エレメント内に次のように追加してください。
Google Play Servicesの使用宣言
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
110
続いてAndroidManifest.xmlにGPSのパーミッションを追加します。
GPSのパーミッションを追加する
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
第
16
このソースコードでは、取得した位置情報をログに出力します。アプリ画面に表示
章
させるようにすると図36のようになります。
セ
ン
サ
ー
の
読
み
方
図36:位置情報取得
さて、これだけだといわゆる緯度経度の数値しか見えないので、イマイチ取得した
位置が正しいかどうかわかりません。ちょっと工夫して、位置をMapで表示するように
してみます。
ボタンを配置して、リスナーをセットします。ボタンがクリックされたらIntentでGoo
gle Mapsを起動してみましょう。その時に"geo:"のスキーマに次のコードを設定し
ます。これにより現在位置の地図が表示されます
(図37)。
Google Mapで位置を確認
mMapBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// IntentでGoogle Mapsを呼び出す
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("geo:" + mLoc.getLatitude() + "," + mLoc.getLongitude()));
startActivity(intent);
}
})
111
図37:IntentでMap表示
16 -2 -6 位置情報の更新
ここまでの方法では、起動時の位置情報しか取得できませんでした。これでは移
動しながら位置情報を更新する用途には使いづらいので、都度更新するような方
法で実装してみます。
・ LocationRequest#create で更新リクエストを生成
・ LocationRequest#setInterval で更新間隔の目安をセット(ms)
・ LocationRequest#setSmallestDisplacement で更新距離間隔を
セット(m)
・ LocationRequest#setPriority 更新のプライオリティをセット
・ LocationClient#requestLocationUpdates LocationClientに更
新リクエストをセット
onConnectedまでは同じですが、その後に更新間隔をリクエストするようにしま
す。
112
更新設定
@Override
public void onConnected(Bundle connectionHint) {
// 位置情報の更新リクエスト
LocationRequest req = LocationRequest.create();
req.setInterval(5000);
req.setSmallestDisplacement(1);
第
16
req.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
章
mLocationClient.requestLocationUpdates(req, this);
}
Google I/O 2013講演資料より
プライオリティの設 定は、端 末の電 池 消 費に影 響します。例としてG a l a x y
(http://y-anz-m.blogspot.
jp/2013/05/google-io-2013-
Nexusの場合は次の表7のようになるという実験結果があります。
プライオリティ
更 新 の目 安
android-beyond-blue-dot.html)
電 池 消 費 量(% / h)
精度
PRIORITY_HIGH_ACCURACY
5秒
7.25%
~10n
PRIORITY_BALANCED_POWER_ACCURACY
20秒
0.6%
~40m
PRIORITY_LOW_POWER
―
―
~10km
PRIORITY_NO_POWER
N/A
少ない
~1マイル?
表7:プライオリティと電池消費量
プライオリティ
特徴
PRIORITY_HIGH_ACCURACY
屋外ではGPS、屋内ではWiFiやセル
(基地局)
を使用する。電池消費量が多
くマップアプリ向け
PRIORITY_BALANCED_POWER_ACCURACY
GPSを使わない。WiFiやセルを使用する。ブロックレベル
(約100m)
の精
度
PRIORITY_LOW_POWER
GPSを使わない。WiFiやセルを使用する。市レベル
(約10km)
の精度
PRIORITY_NO_POWER
正確性はその時の状態に依存
表8:プライオリティの特徴
厳密にはリファレンスと異なりますが、
この辺りは機種依存性もあるので一概にはい
えません。公称と実験結果では差がありそうです。
Google I/O 2013講演資料より
(http://y-anz-m.blogspot.
jp/2013/05/google-io-2013android-beyond-blue-dot.html)
さて、このLocationClient#requestLocationListenerを使用する場合は、
com.google.android.gms.location.LocationListenerをリスナーにセットし、
コールバックを実装します。
これで、更新のタイミングでこのコールバックが呼ばれるよ
うになります。また、
リクエストした更新は次の「ActivityへのLocationListenerの
設定」
と
「位置情報更新のコールバック」のコードのようにonPauseで解除しておき
ます。
ActivityへのLocationListenerの設定
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks,
OnConnectionFailedListener, LocationListener {
113
セ
ン
サ
ー
の
読
み
方
位置情報更新のコールバック
@Override
public void onLocationChanged(Location loc) {
Toast.makeText(this, "Get Location", Toast.LENGTH_SHORT).show();
Date date = new Date(loc.getTime());
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd: HH:mm:ss");
mLocation[0].setText(String.valueOf(loc.getLatitude()));
mLocation[1].setText(String.valueOf(loc.getLongitude()));
mLocation[2].setText(String.valueOf(loc.getAltitude()));
mLocation[3].setText(String.valueOf(loc.getSpeed()));
mLocation[4].setText(sdf.format(date));
mLocation[5].setText(String.valueOf(loc.getAccuracy()));
mLocation[6].setText(String.valueOf(loc.getBearing()));
}
@Override
protected void onPause() {
super.onPause();
if (mLocationClient != null) {
// LocationListenerを解除
mLocationClient.removeLocationUpdates(this);
// Google Play Servicesとの切断
mLocationClient.disconnect();
}
}
今回は取得したLocationオブジェクトからいくつかのデータを取り出しています。
・ Latitude : 緯度
(度)
・ Longitude : 経度
(度)
・ Altitude : 高度
(m)
・ Speed : 移動速度
(m/s)
・ Time : 取得時間
(UTC)
・ Accuracy : 精度
(m)
・ Bearing : 北からの時計回りの角度
(度)
無い場合は0
このように、ざっくりと位置を確認したい場合はLocationClient#getLastLoca
tionを使い、時間や位置の変化で更新を伴う場合はLocationRequest#requ
estLocationUpdatesを使用することが定石です
(図38)。
114
第
16
章
セ
ン
サ
ー
の
読
み
方
図38:位置情報データ
ナビゲーションアプリを作る場合はこの位置情報を元にMapビューなどと組み合
わせて表示するようにすればよいということになります。ある一定の間隔で位置情報
を収集するにはServiceで定期的に位置情報を取得してデータベースに登録して
おき、後でMapビューで表示できるようにする等の応用例が考えられます。
まとめ
センサーの応用例として次のようなものが挙げられます。
・ フィジカルコンピューティング
・ AR(Augmented Reality)とよばれる拡張現実空間
・ 3Dボーカロイドなどの俯瞰処理
・ 体感型ゲーム
・ フィットネスや健康管理
・ ナビゲーションシステム
ここまで見てきたように、センサー自体の使い方はさほど難しくありませんが、これら
を組み合わせて応用しようとすると、行列演算やリアルタイム処理など、かなり複雑な
処理を行う必要があり、数学的な視点などプログラミング技術だけではない点が出
てきます。また、AndroidはiPhoneのようにすべて同じ部品が使われているわけで
はないため、ハードウェアの違いが少なくなく、機種によって微妙に動作が異なった
115
り、あるいは全く動かない、ということもあります。実際のプログラミングや検証作業で
は、
どの端末で対応するのか? APIレベルはいくつ以上なのか?など考慮する必要
があります。
そしてもう一つ重要なのは電池消費量です。GPSも含めてセンサーは電池消費
が大きいため、常時に動作させることは避けるべきです。いかに電池消費を抑えつ
つ効果のある動作を実現できるかも、
このセンサーを扱う上でポイントとなります。
116
第
16
章
セ
ン
サ
ー
の
読
み
方
117
Fly UP