View
238
Download
0
Category
Preview:
Citation preview
Chapter 14 感測器
作者: 林致孙
手機和感測器的結合,讓手機產生更多的應用,除了應用於遊戲軟體,感測器也
讓手機上實作擴增實境變得更容易。本章將介紹應用程式如何讀取手機上的感測
器,同時也會提供範例,讓讀者瞭解方位感測器(Orientation Sensor)與加速度感
測器(Accelerometer Sensor)的應用。
14.1 讀取感測資料
首先我們先學習感測器相關類別的使用方法,請讀者引進光碟中『\範例程式
\Chapter14\SensorList』這個專案,這個專案有兩個 Activity:
SensorList:繼承了 ListActivity,會列出手機上所的感測器,點選個別的感
測器後,會跳至 SensorReader讀取所選取的感測器的讀值。
SensorReader:讀取特定感測器的數值。
讓我們先來看 SensorList這個 Activity,其內容如下所示:
1 public class SensorList extends ListActivity {
2
3 private SensorManager smgr;
4 List<Sensor> slist;
5
6 @Override
7 public void onCreate(Bundle savedInstanceState) {
8 super.onCreate(savedInstanceState);
9 setContentView(R.layout.main);
10
11 smgr = (SensorManager)getSystemService(
12 Context.SENSOR_SERVICE);
13 slist = smgr.getSensorList(Sensor.TYPE_ALL);
14 ArrayList<String> snlist = new ArrayList<String>();
15
16 for (int i = 0; i < slist.size(); i++)
17 snlist.add(slist.get(i).getName());
18
19 ListAdapter adapter = new ArrayAdapter<String>(this,
20 R.layout.list_item, snlist);
21 setListAdapter(adapter);
22 }
23
24 @Override
25 protected void onListItemClick(ListView l, View v,
26 int position, long id) {
27 super.onListItemClick(l, v, position, id);
28
29 Intent intent = new Intent(this, SensorReader.class);
30 intent.putExtra("KEY_TYPE", slist.get(position).getType());
31 startActivity(intent);
32 }
33 }
首先為了存取手機上的感測器資訊,程式於第 11行先呼叫 getSystemService取
得一個 SensorManager物件[1],第 13行 呼叫 SensorManager物件的 getSensorList
方法取得 Sensor物件[2],參數 Sensor. TYPE_ALL代表所有種類的感測器都要取
得,如果是 Sensor. TYPE_ACCELEROMETER代表只取得加速度感測器的 Sensor
物件(一個手機有可能擁有兩個以上的同種類測器)。第 17行呼叫 Sensor物件的
getName方法取得感測器的名稱,這名稱即要呈現於列表介面元件(ListView)的
『資料』。而當使用者點選某個感測器後,會將感測器的型別(可想成種類)夾帶
在 Intent裡傳送給 SensorReader。下圖是筆者截取實機所得到的執行畫面:
接著討論 SensorReader這個 Activity,其內容如下所示:
1 public class SensorReader extends Activity {
2
3 private SensorManager smgr;
4 private TextView tv;
5 private Sensor sensor;
6
7 @Override
8 public void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.reader);
11
12 tv = (TextView)findViewById(R.id.tv_sresult);
13
14 smgr = (SensorManager)getSystemService(
15 Context.SENSOR_SERVICE);
16 Intent intent = getIntent();
17 int stype = intent.getIntExtra("KEY_TYPE", -1);
18
19 sensor = smgr.getDefaultSensor(stype);
20 }
21
22 @Override
23 protected void onResume() {
24 smgr.registerListener(sListener, sensor,
25 SensorManager.SENSOR_DELAY_UI);
26 super.onResume();
27 }
28
29 @Override
30 protected void onPause() {
31 smgr.unregisterListener(sListener, sensor);
32 super.onPause();
33 }
34
35 private final SensorEventListener sListener =
36 new SensorEventListener() {
37 public void onSensorChanged (SensorEvent event) {
38 if (event.sensor != sensor) return;
39
40 String str = "";
41
42 switch (sensor.getType()) {
43 case Sensor.TYPE_ACCELEROMETER:
44 str = "Accelerometer Sensor\n";
45 break;
46 case Sensor.TYPE_GYROSCOPE:
47 str = "Gyroscope Sensor\n";
48 break;
49 case Sensor.TYPE_LIGHT:
50 str = "Light Sensor\n";
51 break;
52 case Sensor.TYPE_MAGNETIC_FIELD:
53 str = "Magnetic Field Sensor\n";
54 break;
55 case Sensor.TYPE_ORIENTATION:
56 str = "Orientation Sensor\n";
57 break;
58 case Sensor.TYPE_PRESSURE:
59 str = "Pressure Sensor\n";
60 break;
61 case Sensor.TYPE_PROXIMITY:
62 str = "Proximity Sensor\n";
63 break;
64 case Sensor.TYPE_TEMPERATURE:
65 str = "Temperature Sensor\n";
66 break;
67 }
68
69 for (int i = 0; i < event.values.length; i++)
70 str = str + "values[" + i + "]: " +
71 event.values[i] + "\n";
72
73 str = str + "Accuracy: " + event.accuracy;
74
75 tv.setText(str);
76 }
77 }
78 public void onAccuracyChanged (Sensor sensor, int accuracy) {
79 }
80 };
81 }
首先在第 14行,同樣使用 getSystemService方法取得 SensorManger物件,接著
利用 getDefaultSensor取得該種類的 Sensor物件,若同種類的感測器數目有兩個
以上,Intent需另外攜帶感測器的名稱,同時應該使用 getSensorList取代
getDefaultSensor。
為了讀取該感測器的資料,我們必須設計一個傾聽者給感測器,傾聽者類別需實
作 SensorEventListener介面[3],程式碼是位於 35~80行,一樣使用了匿名類別的
技巧,有兩個抽象方法需要實作:
onAccuracyChanged:當量測值的精準度改變時,這個方法會被呼叫。
onSensorChanged:當量測值改變時,這個方法會被呼叫。
我們把焦點放在 onSensorChanged方法,onSensorchanged會傳入一個 SensorEvent
物件[4],感測的值可從這個 SensorEvent物件中抓取,首先在 38行,先判斷發
生事件的感測器是否是我們所要量測的感測器,接著 42~67行,我們將感測器的
種類抓出來,並設定之後要用來顯示於 TextView上的字串,字串是告訴使用者
讀取到的感測器是哪一種感測器。
讀取感測器值的程式是寫在 69~71行,感測值會儲存在一個浮點數陣列(float []
values),這個浮點數陣列是 SensorEvent類別的成員變數,不同種類的感測器有
不同的意義[4],以方位感測器(Orientation Sensor)為例,當手機是正著直著拿在
手上時,如下圖所示,values[0]的值意義為:0代表北方、90代表東方、180代
表南方、270代表西方,values[1]是縱向旋轉角,現在一樣正著直著拿著手機,
向外旋轉,垂直向上時值為 90、面朝上平置時值為 0、垂直向下時值為 90、面
朝下平置時值為 180,values[2]是橫向旋轉角,現在一樣正著直著拿著手機,左
或向右旋轉,朝前時值為 0、往右橫放是-90、往左橫放是 90,詳細的定義可參
閱說明文件[4],若對說明文件[4]不是十分瞭解,筆者建議讀者可利用本程式自
行做一些測詴,便能體會出值是如何變化的。
而以加速度感測器為例的話,values[0]代表手機的 X軸所承受的加速度減去重力
於 X軸所帶來的加速度,以上圖為例,手機的 X軸是指比較窄的那一個邊所型
成的軸,往右較大,values[1]代表手機的 Y軸所承受的加速度減去重力於 Y軸
所帶來的加速度,以上圖為例,手機的 Y軸是指比較長的那一個邊所型成的軸,
往上較大,values[2] 代表手機的 Z軸所承受的加速度減去重力於 Z軸所帶來的
加速度,手機的 Z軸代表著向外或向內,往外較大。當正著直著拿著手機時,
可發現,values[0]與 values[2]會接近於 0,而 Y軸受到了向下的重力加速度(-9.81
公尺/秒平方),因為手機穩穩的拿在手上,因此 Y軸事實上並沒有任何加速度(0
公尺/秒平方),因此 0-(-9.81)=9.81,讀者可發現 values[1]的值會接近 9.81。其它
感測器的讀值就請讀者自行閱讀說明文件。
SensorEvent物件還有一個成員變數為 accuracy(73行),
SENSOR_STATUS_ACCURACY_HIGH(值為 3)代表這個回報的值是很精準的,
SENSOR_STATUS_UNRELIABLE(值為 0)則代表回報的值的精準度是最低的,
請讀者參考說明文件[1]。此外,若在精準度發生變化時,做些處理動做的話,
就要去確實實作 78行的 onAccuracyChanged方法。
現在傾聽者已經設計完成,我們要幫感測器跟其傾聽者連結在一起,當感測器註
冊了傾聽者後,程式就會一直去讀取感測器的資料,因此我們希望只有當程式在
前景執行時才讀取感測器的資料,因此於 onResume方法(第 24行)呼叫
SensorManager類別的 registerListener方法去做註冊,registerListener方法需要三
個參數,第一個參數傾聽者物件,第二個參數是感測器物件,第三個參數是設定
回報速率,亦即多久回報一次,共有四種速率可選擇:
SENSOR_DELAY_FASTEST、SENSOR_DELAY_GAME、SENSOR_DELAY_UI
與 SENSOR_DELAY_NORMAL。
程式於 onPause方法(第 31行)呼叫 unregisterListener方法取消註冊,如此當程式
進入背景或結束時就不會讀取感測器了。下圖是筆者於實機讀取方位感測器的一
個執行畫面:
14.2 方位感測器的應用
在這一節筆者將示範一個方位感測器的應用,這個應用程式的功能相當簡單,就
是出現一個箭頭指向亞洲大學行政大樓。請讀者引進光碟中『\範例程式
\Chapter14\OrientationApp』這個專案,裡面有兩個 Java原始碼檔案:
OrientationApp.java:這是一個Activity,會讀取GPS以及方位感測器的資料,
藉以判斷箭頭應該呈現的方向。
ArrowView.java:其繼承了 View類別,是一個客製化的 View,主要是用來
顯示箭頭。
先來討論 OrientationApp.java,其內容如下:
1 public class OrientationApp extends Activity {
2
3 private ArrowView av;
4 private MyLocationListener mll;
5 private MySensorListener msl;
6 private LocationManager lmgr;
7 private SensorManager smgr;
8 private List<Sensor> slist;
9 private float orientation, target;
10
11 @Override
12 public void onCreate(Bundle savedInstanceState) {
13 super.onCreate(savedInstanceState);
14
15 setRequestedOrientation(
16 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
17
18 av = new ArrowView(this);
19 setContentView(av);
20
21 lmgr = (LocationManager)getSystemService(LOCATION_SERVICE);
22 mll = new MyLocationListener();
23
24 smgr = (SensorManager)getSystemService(
25 Context.SENSOR_SERVICE);
26 msl = new MySensorListener();
27
28 slist = smgr.getSensorList(Sensor.TYPE_ORIENTATION);
29 if (slist.size() == 0) {
30 Toast.makeText(this, "No orientation sensor",
31 Toast.LENGTH_SHORT).show();
32 finish();
33 }
34 orientation = (float)0.0;
35 target = (float)0.0;
36 }
37
38 @Override
39 protected void onResume() {
40 super.onResume();
41 lmgr.requestLocationUpdates(
42 LocationManager.GPS_PROVIDER, 0, 0, mll);
43 smgr.registerListener(msl, slist.get(0),
44 SensorManager.SENSOR_DELAY_UI);
45 }
46
47 @Override
48 protected void onPause() {
49 super.onPause();
50 lmgr.removeUpdates(mll);
51 smgr.unregisterListener(msl, slist.get(0));
52 }
53
54 private void adjustArrow() {
55 float degree = target-orientation;
56 if (degree < 0) degree = degree + 360.0f;
57 av.setDegree(degree);
58 setContentView(av);
59 }
60
61 class MySensorListener implements SensorEventListener {
62 public void onSensorChanged (SensorEvent event) {
63 if (event.sensor == slist.get(0)) {
64 orientation = event.values[0];
65 adjustArrow();
66 }
67 }
68 public void onAccuracyChanged (Sensor sensor, int accuracy) {
69 }
70 }
71
72 class MyLocationListener implements LocationListener {
73 @Override
74 public void onLocationChanged(Location location) {
75 if (location == null)
76 return;
77
78 Location dest = new Location(location);
79 dest.setLatitude(24.045857);
80 dest.setLongitude(120.686681);
81 target = location.bearingTo(dest);
82 adjustArrow();
83 }
84 @Override
85 public void onProviderDisabled(String provider) {
86 }
87 @Override
88 public void onProviderEnabled(String provider) {
89 }
90 @Override
91 public void onStatusChanged(String provider, int status,
92 Bundle extras) {
93 }
94 }
95 }
這個程式使用到的大部份類別與方法都學過了,因此筆者只告訴讀者這個程式做
了哪幾件事,讓讀者自行去閱讀細節即可,這個程式主要做了幾件事:
設計感測器的傾聽者(61~70行):當發現感測值發生變化後,程式會設定變
數 orientation的值(values[0]),藉此來判斷使用者面對的方位,並呼叫我們
自行定義的 adjustArrow方法來設定箭頭的方向。
設計 GPS的傾聽者(72~94行):當發現使用者所在位置改變時,程式會去計
算目標物(亞洲大學行政大樓)是在使用者的哪個方位,要知道目標物是在使
用者的哪個方位可利用 Location類別 bearingTo方法[5],如果目標物在使用
者的正北方,變數 target的值為 0,如果目標物在使用者的正東方,變數 target
的值為 90,以此類推。接著一樣呼叫自行定義的 adjustArrow方法來設定箭
頭的方向。此處要提醒一下,120.686681及 24.045857是亞洲大學行政大樓
的經緯度值,讀者可換成在自己附近的目標物,以方便程式的測詴。
設定箭頭方向:寫於 adjustArrow方法(54~59行),我們假設手機正上方為 0
度,右側為 90度,則箭頭應該偏轉的角度只要將 target減去 orientation即可
算出。算出偏轉角度後呼叫 ArrowView類別(程式自行定義的,稍後會解說)
的 setDegree方法將角度傳送給 ArrowView物件,並將 ArrowView顯示於畫
面上。
於 onResume方法啟動定位與感測服務(39~45行)。
於 onPause方法停止定位與感測服務(48~52行)。
在這個 OrientationApp.java的程式中,我們沒學過的應該就只有第 15行 Activity
類別的 setRequestedOrientation方法,其可用來設定畫面的呈現方式,是要直著
看(SCREEN_ORIENTATION_PORTRAIT)或是橫著看
(SCREEN_ORIENTATION_LANDSCAPE),如下圖所示,其它的呈現方式可參考
ActivityInfo類別的說明[6]。
接下來可以開始來討論 ArrowView.java了,其內容如下:
1 public class ArrowView extends View {
2
3 private final float alength = (float)100.0;
4 private final float arrowd = (float)10.0;
5 private final float arroww = (float)5.0;
6
7 float startX, startY, stopX, stopY, degree;
8
9 public ArrowView(Context context) {
10 super(context);
11 startX = (float)160.0;
12 startY = (float)240.0;
13 degree = (float)0.0;
14 }
15
16 protected void setDegree(float degree) {
17 this.degree = degree;
18 }
19
20 @Override
21 protected void onDraw(Canvas canvas) {
22
23 Paint paint = new Paint();
24
25 float radian = (float)(degree*Math.PI/180.0);
26 stopX = startX + (float)(alength*Math.sin(radian));
27 stopY = startY - (float)(alength*Math.cos(radian));
28 canvas.drawColor(Color.WHITE);
29 canvas.drawLine(startX, startY, stopX, stopY, paint);
30
31 float v3x, v3y, diffx, diffy, leftax, leftay, rightax, rightay;
32
33 v3x = stopX + ((startX-stopX)*arrowd)/alength;
34 v3y = stopY + ((startY-stopY)*arrowd)/alength;
35
36 diffx = (float)Math.abs((arroww*(stopY-startY))/alength);
37 diffy = (float)Math.abs((arroww*(stopX-startX))/alength);
38
39 if ((startX-stopX) < 0.0) {
40 leftay = v3y - diffy;
41 rightay = v3y + diffy;
42 } else {
43 leftay = v3y + diffy;
44 rightay = v3y - diffy;
45 }
46
47 if ((startY-stopY) < 0.0) {
48 leftax = v3x + diffx;
49 rightax = v3x - diffx;
50 } else {
51 leftax = v3x - diffx;
52 rightax = v3x + diffx;
53 }
54 canvas.drawLine(leftax, leftay, stopX, stopY, paint);
55 canvas.drawLine(rightax, rightay, stopX, stopY, paint);
56 }
57 }
ArrowView繼承了 View類別,是一個客製化的 View,我們只要覆寫 onDraw方
法,便可呈現我們所設計的 View,onDraw會傳進一個畫布(Canvas)物件,我們
只要在畫布上畫出想要呈現的線條、圖形等即可設計出自己的 View,這部份請
讀者自行閱讀相關類別的說明文件[7][8],這裡不做太多的說明,然而筆者還是
附上一張圖,讓讀者能夠更容易瞭解箭頭是如何畫出來的。
最後一件要提醒讀者的事是,別忘了於 AndroidManifest.xml中聲明這個程式需
要得到精確位置資訊,亦即要讀取 GPS資訊。下面是執行畫面,然而要測詴這
個程式是否正確,還是要走出戶外:
14.3 加速度感測器的應用
在這一節筆者將示範一個加速度感測器的應用,這個應用程式的功能用來偵測手
機是否有劇烈晃動。請讀者引進光碟中『\範例程式\Chapter14\AccelerometerApp』
這個專案,裡面只有 AccelerometerApp這個 Activity,其內容如下:
1 public class AccelerometerApp extends Activity {
2
3 float max;
4 TextView tv;
5 private SensorManager smgr;
6 private List<Sensor> slist;
7 boolean isStarted;
8
9 @Override
10 public void onCreate(Bundle savedInstanceState) {
11 super.onCreate(savedInstanceState);
12
13 setRequestedOrientation(
14 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
15
16 setContentView(R.layout.main);
17 tv = (TextView)findViewById(R.id.tv_max);
18 Button btn_start = (Button)findViewById(R.id.btn_start);
19 btn_start.setOnClickListener(start_l);
20 Button btn_stop = (Button)findViewById(R.id.btn_stop);
21 btn_stop.setOnClickListener(stop_l);
22
23 smgr = (SensorManager)getSystemService(
24 Context.SENSOR_SERVICE);
25
26 slist = smgr.getSensorList(Sensor.TYPE_ACCELEROMETER);
27 if (slist.size() == 0) {
28 Toast.makeText(this, "No accelerometer sensor",
29 Toast.LENGTH_SHORT).show();
30 finish();
31 }
32 isStarted = false;
33 }
34
35 private final SensorEventListener mListener = new
36 SensorEventListener() {
37 public void onSensorChanged (SensorEvent event) {
38 if (event.sensor == slist.get(0)) {
39 if (isStarted == false) return;
40
41 float totalForce = (float)0.0;
42
43 totalForce += (float)Math.pow(
44 event.values[0]/SensorManager.GRAVITY_EARTH, 2.0);
45 totalForce += (float)Math.pow(
46 event.values[1]/SensorManager.GRAVITY_EARTH, 2.0);
47 totalForce += (float)Math.pow(
48 event.values[2]/SensorManager.GRAVITY_EARTH, 2.0);
49
50 totalForce = (float)Math.sqrt(totalForce);
51
52 if (totalForce > max)
53 max = totalForce;
54 }
55 }
56 public void onAccuracyChanged (Sensor sensor, int accuracy) {
57 }
58 };
59
60 OnClickListener start_l = new OnClickListener() {
61 public void onClick(View v) {
62 if (isStarted == true) return;
63 isStarted = true;
64 max = (float)0.0;
65 smgr.registerListener(mListener, slist.get(0),
66 SensorManager.SENSOR_DELAY_UI);
67 tv.setText("");
68 }
69 };
70
71 OnClickListener stop_l = new OnClickListener() {
72 public void onClick(View v) {
73 if (isStarted == false) return;
74 isStarted = false;
75 smgr.unregisterListener(mListener, slist.get(0));
76 if (max < 2.5) {
77 tv.setText("Fail! Try again!");
78 } else {
79 tv.setText("Max: " + max);
80 }
81 }
82 };
83 }
這個程式有兩個按鈕:
『Start』按鈕:其傾聽者是實作於 60~69行,其主要功能是啟動感測器的讀
取,並將最大值(max)的值做初始化。
『Stop』按鈕:其傾聽者是實作於 71~82行,其主要功能是停止讀取感測器,
並檢查所測得的最大值(max)是否有大於 2.5,若小於 2.5,會告訴使用者其
沒有盡全力晃動手機,若大於 2.5,則將測得的最大值告訴使用者。
晃動值的計算是寫於感測器的傾聽者內(35~58行),其主要是運用畢氏定理算出
總值(√a2 + b2 + c2),這裡順便跟讀者點出一件事,當手機歪斜著拿著不動時,
若把三軸的加速度值利用畢氏定理算出會得出接近 9.81(重力加速度)的數才對。
讀者按下『Start』按鈕就可開始進行測詴,而按下『Stop』按鈕就可觀看結果,
唯一要注意的是不要不小心把手機甩出去,甚至砸到電視。下面分別是晃動成功
與失敗的執行結果畫面:
14.4 摘要
本章將介紹了感測器的相關應用,首先我們學會了如何獲得手機上的感測器清單,
並瞭解如何讀取感測器的感測值。接著我們分別學習了一個方位感測器
(Orientation Sensor)與一個加速度感測器(Accelerometer Sensor)的應用。手機和感
測器的結合,讓手機產生更多的應用,除了應用於遊戲軟體,感測器也讓手機上
實作擴增實境變得更容易。
14.5 作業
1. 寫一個程式來判斷手機的放置方式:平放、水平立放或垂直立放,分別如下
圖所示。請使用加速度感測器。
2. 寫一個程式來判斷手機的放置方式:平放、水平立放或垂直立放。這次請改
用方位感測器。
3. 改寫 14.2的程式,選定兩個不同的目標物,程式會出現兩個箭頭分別指向
兩個目標物。
14.6 參考資料
[1] SensorManager | Android Developers,
http://developer.android.com/reference/android/hardware/SensorManager.html
[2] Sensor | Android Developers,
http://developer.android.com/reference/android/hardware/Sensor.html
[3] SensorEventListener | Android Developers,
http://developer.android.com/reference/android/hardware/SensorEventListener.html
[4] SensorEvent | Android Developers,
http://developer.android.com/reference/android/hardware/SensorEvent.html
[5] Location | Android Developers,
http://developer.android.com/reference/android/location/Location.html
[6] ActivityInfo | Android Developers,
http://developer.android.com/reference/android/content/pm/ActivityInfo.html
[7] Canvas | Android Developers,
http://developer.android.com/reference/android/graphics/Canvas.html
[8] Paint | Android Developers,
http://developer.android.com/reference/android/graphics/Paint.html
Recommended