◆PROCESSING 逆引きリファレンス
カテゴリー:スマホ(AndroidMode)
加速度センサーと地磁気センサーを使うには(AndroidMode編)
【概要】
PROCESSINGにAndroidMode を導入する事で、PROCESSINGで開発したプログラムをAndroid端末上で動かす事ができるようになります。
AndroidModeの導入については「PROCESSINGをAndroid端末で動かすには(4.0版)」記事を参照してください。
スマホの魅力は様々なアプリケーションを動かすことができるだけでなく、いろいろなセンサーを手軽に利用できるところにもあるのではないでしょうか?。
Androidでセンサーを扱うには、まずはセンサー管理オブジェクトを取得します。
その後センサー管理オブジェクトから、読み取りたいセンサーを制御するオブジェクトを取得し、目標となるセンサーの値が変化したらイベントを通知するリスナーを登録します。
センサーの値が変化すると、指定したリスナーに専用のイベントが飛んできますので、イベント関数の中でセンサーから値を取得するわけです。
センサー管理オブジェクトとセンサー制御オブジェクトの取得については「利用可能なセンサーを知るには(AndroidMode編)」記事を参照してください。
以下では、スマホの代表的なセンサーである加速度センサーと地磁気センサーから値を取得する方法を紹介します。
【詳細】
全般的な注意
センサー管理オブジェクト、センサー制御オブジェクトの取得と、読み取りたいセンサーへのリスナー登録は、Androidの画面が準備完了となり画面が表示される直前に行うべきです。
なぜなら、Androidでは画面が回転したり電話がかかってきたりすると、表示中の画面が破棄され、再作成される事があるためです。
またセンサーを適時読み取る処理はバッテリーを消費するため、必要がなくなった場合(例えば画面が隠れるような状況になった場合)は、これを即座に停止すべきでしょう。
とくにPROCESSINGでセンサーを使うなら、リスナーの登録処理はsetup関数ではなく onResumeメソッドをオーバライドして行うべきです。
またセンサーの開放処理は、onPauseメソッドをオーバライドして行うのが良いでしょう。
このあたりの話題は「PROCESSINGとライフサイクル(AndroidMode編)」記事も参考としてください。
Androidでのセンサー座標軸は、X軸が水平で右側が+、Y軸が垂直で上側が+、Z軸は画面正面の外側が+を指しています。座標軸は画面が回転しても入れ替わることはありません。
(画像URL:illust-AC 様:kaeru-yaさん)
加速度は m//s² で、1秒(s)あたりの移動距離(m)が検知されます。
ただし加速度には重力が加味されているため、例えばスマホを上向き(画面を上)にして水平な机に置いた場合、Z軸に +9.8m/s² の(重力とは反対方向の)加速度が検知されます。
同じようにスマホを右に傾けるとX軸にマイナスの、頭側を下に(奥に)傾けるとY軸にマイナスの加速度が検知されます。
加速度については以下のサイト様が参考となりました。ありがとうございます。
地磁気は磁束密度(マイクロテスラ:μT)で、それぞれX、Y、Z方向の磁束密度が検知されます。
地磁気については以下のサイト様が参考となりました。ありがとうございます。
センサー制御オブジェクト取得
mng : センサー管理オブジェクト
Type : 取り出すセンサーの種類
Typeに取り出したいセンサーの種類を与えることで、センサー制御用のオブジェクトを取得できます。
加速度センサー用の制御オブジェクトを取り出したいなら、TypeにはSensorクラスがもつTYPE_ACCELEROMETER定数(Sensor.TYPE_ACCELEROMETER)を指定します。
また地磁気センサーを取得するには、TYPE_MAGNETIC_FIELD定数(Sensor.TYPE_MAGNETIC_FIELD)を指定します。
リスナー登録・解除
リスナー登録boolean ret = mng . registerListener (SensorEventListener listener,Sensor sensor,int samplingPeriodUs) ;
mng : センサー管理オブジェクト
sensor : センサー制御オブジェクト
listener : リスナーオブジェクト
samplingPeriodUs : 読み取り頻度
ret : 戻り値(true:成功)
リスナーオブジェクトには、SensorEventListenerインタフェースを実装している必要があります。
PROCESSINGで使うなら、Sketchクラスに上記インタフェースを実装するか、専用のクラスを定義するのが良いでしょう。
samplingPeriodUsには、SensorManagerクラスが持つ以下の何れかの定数を与えます。
定数 | 意味 |
---|---|
SENSOR_DELAY_NORMAL | 通常のレート。デフォルト値 |
SENSOR_DELAY_UI | 画面表示やUIを伴うレート |
SENSOR_DELAY_GAME | GAMEに適したレート |
SENSOR_DELAY_FASTEST | できるだけ早くデータを取得するレート |
データをすばやく引き取ろうとすればするほどバッテリーを食いますので、そこは注意してください。
バッテリー消費を最小限に抑え、できるだけゆっくりとイベントを通知すれば良いのであれば、samplingPeriodUsの後ろにmaxReportLatencyUs引数を追加する事が可能です。
maxReportLatencyUsには、イベントをアプリケーションに報告する前に遅延させる最大時間(マイクロ秒)を指定します。
maxReportLatencyUs引数がない場合(上記で紹介した例)は、maxReportLatencyUsに0を指定した場合と等価になります。
リスナー解除void mng . unregisterListener (SensorEventListener listener) ;
listener : リスナーオブジェクト
センサーの値を読み取る必要がなくなったら、速やかにリスナーを解除し、データの通知を停止します。
データの取得
データの取得void onSensorChanged( SensorEvent event) ;
event : センサーイベント
センサから新しい値を取得した場合に呼び出されるメソッドです。SendorEventについては 公式サイト を参照してください。
どのセンサーからのデータであるかは、SensorEventのsensorで知ることが可能です。
またSendorEventからデータ(values)を取得する際には、できるだけ速やかに取得する必要があります。
加速度はm//s² で、event.values[0]からはX軸、event.values[1]からはY軸、event.values[2]からはZ軸の加速度が取得できます。
地磁気はマイクロテスラ(μT)で、おなじくevent.values[0]からはX軸、event.values[1]からはY軸、event.values[2]からはZ軸の周囲磁場が測定できます。
この他に、SensorEventListener にはセンサーの精度が変化した際にイベントの通知が行われるonAccuracyChangedメソッドがありますが、こちらの使い所はよくわかりませんでした(汗)。
センサーの精度を細かく変更しながら処理を行う、特殊なアプリケーションを作成する際には活躍するのかもしれません。
【関連記事】
サンプルプログラム
加速度と地磁気を取得する例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
import android.app.Activity; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; /** * PROCESSING 加速度・地磁気センサーSample * @auther MSLABO * @version 2018/06 1.0 */ //センサーから読み取った値 float[] accelerometerValues = new float[3]; float[] magneticValues = new float[3]; Activity act; SensorManager mSensor = null; SensorCatch sensorCatch = null; float fontSize; //センサー読み取りクラス //別途用意するならSensorEventListenerを実装している事 class SensorCatch implements SensorEventListener{ //センサーの値が変化した時呼ばれるイベントメソッド public void onSensorChanged(SensorEvent sensorEvent) { //それぞれのセンサーから値を取得する switch(sensorEvent.sensor.getType()) { //加速度センサー case Sensor.TYPE_ACCELEROMETER: accelerometerValues = sensorEvent.values.clone(); break; //地磁気センサー case Sensor.TYPE_MAGNETIC_FIELD: magneticValues = sensorEvent.values.clone(); break; } } //精度が変化した時呼ばれるイベントメソッド public void onAccuracyChanged(Sensor sensor, int i) { //処理なし } } //画面表示直前イベント public void onResume() { super.onResume(); //Activityの取得 act = getActivity(); //センサー管理オブジェクト取得 mSensor = (SensorManager)act.getSystemService(Context.SENSOR_SERVICE); //センサーリスナーオブジェクト生成 sensorCatch = new SensorCatch(); //センサーリスナーオブジェクトをリスナーとして登録する //加速度センサーのリスナー登録 mSensor.registerListener(sensorCatch, mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI); //地磁気センサーのリスナー登録 mSensor.registerListener(sensorCatch, mSensor.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI); } //画面非表示時イベント public void onPause() { super.onPause(); //リスナーへのイベント通知を解除する if(mSensor != null){ mSensor.unregisterListener(sensorCatch); } } void setup(){ //縦置き固定 orientation(PORTRAIT); //文字サイズ、文字位置指定 fontSize = 24 * displayDensity; textSize(fontSize); textAlign(LEFT, TOP); stroke(255); } void draw(){ background(0); //センサーの値を表示する //地磁気はXYZから大きさを計算 float ut = sqrt(sq(magneticValues[0]) + sq(magneticValues[1]) + sq(magneticValues[2])); String dispText = String.format( "加速度\n X:%s Y:%s Z:%s\n\n" +"地磁気\n X:%s Y:%s Z:%s\n" +"地磁気の大きさ:%s", nfs(accelerometerValues[0], 2, 2), nfs(accelerometerValues[1], 2, 2), nfs(accelerometerValues[2], 2, 2), nfs(magneticValues[0], 2, 2), nfs(magneticValues[1], 2, 2), nfs(magneticValues[2], 2, 2), nfs(ut,2,2)); text( dispText,0, 0, width, height); } |
加速度と地磁気のセンサー値を画面に表示します。
地磁気に関しては以下の計算式で、大きさを求めて表示しています。この式、たぶん・・・あっていると思います。間違えていたらゴメンなさい(汗)。
電子コンパスの例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
import android.app.Activity; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; /** * PROCESSING 方位角取得 Sample * @auther MSLABO * @version 2018/06 1.0 */ final int AXIS_NUM = 3; final int MATRIX_SIZE = 16; //センサーから読み取った値 float[] accelerometerValues = new float[AXIS_NUM]; float[] magneticValues = new float[AXIS_NUM]; //ローパスフィルタ用の直前の値 float[] saveAcceleVal = new float[AXIS_NUM]; int orientDegrees; //方位角 String orientString; //方位文字 Activity act; SensorManager mSensor = null; SensorCatch sensorCatch = null; float fontSize; PImage compassBase; PImage needle; //センサー読み取りクラス class SensorCatch implements SensorEventListener{ //センサーの値が変化した時呼ばれるイベントメソッド public void onSensorChanged(SensorEvent sensorEvent) { //それぞれのセンサーから値を取得する switch(sensorEvent.sensor.getType()) { //加速度センサー case Sensor.TYPE_ACCELEROMETER: accelerometerValues = sensorEvent.values.clone(); //ローパスフィルタをかける LowPassFilter(accelerometerValues); break; //地磁気センサー case Sensor.TYPE_MAGNETIC_FIELD: magneticValues = sensorEvent.values.clone(); break; } //加速度センサーと地磁気センサーの値から方位角を求める if (accelerometerValues != null && magneticValues != null ) { //方位角を求める orientDegrees = GetAzimuthAngle(accelerometerValues, magneticValues ); //方位文字を得る orientString = toOrientationString(orientDegrees); } } //精度が変化した時呼ばれるイベントメソッド public void onAccuracyChanged(Sensor sensor, int i) { //処理なし } //方位角を計算する処理 private int GetAzimuthAngle(float[] accValues, float[] magValues ){ float[] rotationMatrix = new float[MATRIX_SIZE]; float[] inclinationMatrix = new float[MATRIX_SIZE]; float[] remappedMatrix = new float[MATRIX_SIZE]; float[] orientationValues = new float[AXIS_NUM]; // 回転行列を計算 SensorManager.getRotationMatrix(rotationMatrix, inclinationMatrix, accValues, magValues ); // 端末の画面設定に合わせる SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, remappedMatrix); // 方位角/傾きを取得 SensorManager.getOrientation(remappedMatrix, orientationValues); // 方位角を取得する int degree = toOrientationDegrees(orientationValues[0]) ; return degree; } //方位角(ラジアン)を方位角(角度)に変換する処理 private int toOrientationDegrees(float angle) { return (int)Math.floor(angle >= 0 ? Math.toDegrees(angle) : 360 + Math.toDegrees(angle)); } //ローパスフィルタ処理 final float filterVal = 0.8f; public void LowPassFilter(float[] target ){ float outVal[] = new float[3]; outVal[0] = (float)(saveAcceleVal[0] * filterVal + target[0] * (1-filterVal)); outVal[1] = (float)(saveAcceleVal[1] * filterVal + target[1] * (1-filterVal)); outVal[2] = (float)(saveAcceleVal[2] * filterVal + target[2] * (1-filterVal)); //現在の測定値を次の計算に使うため保存する saveAcceleVal = target.clone(); //加速度センサーから得た値を書き換える accelerometerValues = outVal.clone(); return ; } //方位角(角度)をもとに方位文字列を求める処理 private String toOrientationString(float angle) { String OrientationName = "北"; if( angle >= 337 ){ OrientationName = "北"; } else if( angle >= 293 ){ OrientationName = "北西"; } else if( angle >= 248 ){ OrientationName = "西"; } else if( angle >= 203 ){ OrientationName = "南西"; } else if( angle >= 158 ){ OrientationName = "南"; } else if( angle >= 113 ){ OrientationName = "南東"; } else if( angle >= 68 ){ OrientationName = "東"; } else if( angle >= 23 ){ OrientationName = "北東"; } else if( angle >= 0){ OrientationName = "北"; } return OrientationName; } } //表示直前イベント public void onResume() { super.onResume(); //Activityの取得 act = getActivity(); //センサー管理オブジェクト取得 mSensor = (SensorManager)act.getSystemService(act.SENSOR_SERVICE); //センサーリスナーオブジェクト生成 sensorCatch = new SensorCatch(); //センサーリスナーオブジェクトをリスナーとして登録する //加速度センサーのリスナー登録 mSensor.registerListener(sensorCatch, mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI); //地磁気センサーのリスナー登録 mSensor.registerListener(sensorCatch, mSensor.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI); } //表示停止イベント public void onPause() { super.onPause(); //リスナーへのイベント通知を解除する if(mSensor != null){ mSensor.unregisterListener(sensorCatch); } } void setup(){ //縦置き固定 orientation(PORTRAIT); //コンパス画像読み込み compassBase = loadImage("compassBase.png"); needle = loadImage("needle.png"); //端末の解像度に応じてサイズ変更 compassBase.resize( (int)(compassBase.width * displayDensity), (int)(compassBase.height * displayDensity)); needle.resize( (int)(needle.width * displayDensity), (int)(needle.height * displayDensity)); //文字サイズ、文字位置指定 fontSize = 24 * displayDensity; textSize(fontSize); textAlign(LEFT, TOP); stroke(255); } void draw(){ //コンパスのベース画像を描く background(0); int sx = (width - compassBase.width)/2; int sy = (height - compassBase.height)/2; image(compassBase, sx, sy ); //コンパスの針を回転させる pushMatrix(); translate(width/2, height/2); imageMode(CENTER); rotate(radians(orientDegrees)); image( needle, 0, 0); imageMode(CORNERS); popMatrix(); //方位と方位角を文字でも表示する String dispDegree = String.format("%s : 方位角 %d 度", orientString, orientDegrees ); float fx = (width - textWidth(dispDegree))/2; float fy = (height - compassBase.width)/2 + compassBase.height + fontSize; text( dispDegree,fx,fy ); } |
加速度センサーと地磁気センサーを用いると、こんな事もできるよという例です。
難しい計算の理屈は良くわかりませんが(汗)、両方のセンサーから得た値をもとに方位角の計算を行い、電子コンパスを描きます。
なお検出される方位は磁北になります。真北ではありません。磁北と真北が違うのだということは、このサンプルを作成している中で初めて知りました(お恥ずかしい・・・)。
上記サンプルの作成に、以下のページを参考とさせていただきました。ありがとうございます。
- てるてる坊主 様
- なんでも独り言 様
- Taiyo Project 様
<出力サンプル>
(画像URL:illust-AC 様:ニッキー さん)
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。