02 March 2013

Sensor API in Android - การใช้งาน Accelerometer

Updated on



        คราวนี้ขอข้ามมาเรื่อง Sensor ในอุปกรณ์แอนดรอยด์กันบ้าง โดยที่บทความนี้ก็จะขอเริ่มกับ Sensor ที่ชื่อว่า Accelerometer หลายๆคนจะรู้จักกันในชื่อเซนเซอร์วัดความเอียงนั่นเอง

        สำหรับ Accelerometer เรียกได้ว่าเป็นเซนเซอร์ที่โคตรจะพื้นฐานของอุปกรณ์แอนดรอยด์เลยก็ว่าได้ เรียกได้ว่าทุกๆรุ่นนั้นต้องมีเซนเซอร์ตัวนี้เป็นอย่างน้อย และก็ยังเป็นเซนเซอร์ที่ผู้ที่หลงเข้ามาอ่านส่วนใหญ่จะเขียนใช้งานกันด้วย


        โดยที่ Accelerometer จะวัดความเร่งในการเอียงเครื่องทั้ง 3 ทิศ สำหรับแกน XYZ บนอุปกรณ์แอนดรอยด์ใดๆจะมีตำแหน่งดังนี้



        สำหรับแกน X กับ Y จะขึ้นอยู่กับตัวอุปกรณ์แอนดรอยด์ ในภาพข้างบนนี้จะเป็นเครื่องที่เป็นแทบเลตที่เป็น X-Large หรือเครื่องที่หน้าจอใหญ่กว่า 7 นิ้วขึ้นไป (ไม่รวม 7 นิ้ว) มีแกน X ตามแนวกว้างของจอ และแกน Y ตามแนวสูง


        ส่วนภาพข้างบนนี้คือแกน XYZ บนเครื่องที่เป็นมือถือและแทบเลตที่มีขนาดหน้าจอ Large หรือตั้งแต่ 7 นิ้วลงมา จะเห็นว่าแนวแกน X กับ Y ไม่เหมือนกับบนแทบเลต X-Large เพราะโดยธรรมชาติแล้ว มือถือและแทบเลตขนาด 7 นิ้วลงมา การใช้งานเครื่องจะอยู่ในลักษณะการถือแนวตั้งเป็นหลัก แต่ไซส์ที่ใหญกว่านั้นจะอยู่ในลักษณะการถือแนวนอน ดังนั้นเวลาใช้ Accelerometer ก็ให้คำนึงถึงเรื่องนี้ด้วย

        การวัดความเร่งในการเอียงก็คือการเอียงในแต่ละทิศที่จะมีลักษณะการเอียงในทิศทางต่างๆ ดังนี้


        ทีนี้ให้สังเกตว่า แกน X และ Y จะมีแค่ขึ้นลงเท่านั้น แต่แกน Z จะพิเศษกว่าคือมีทั้งสองแกนที่เคลื่อนที่

        ดังนั้นเวลาที่เอียงไปทางแกน X แกน Z ก็จะเอียงด้วย และเมื่อเอียงไปทางแกน Y แกน Z ก็จะเอียงตาม


        จากที่บอกไปแล้วว่า Accelerometer จะวัดความเร่งในแต่ละแกน ง่ายๆก็คือ เวลาที่เครื่องอยู่นิ่งๆไม่มีการขยับ ค่าแต่ละแกนก็เป็น 0 แต่ในความเป็นจริงอย่าลืมว่ายังมีแรงโน่มถ่วงของโลกอยู่ด้วย ดังนั้นค่าจาก Accelerometer จึงไม่ได้เป็น 0 ทั้งหมด เวลาไม่เคลื่อนที่ ถ้าเราตั้งเครื่องให้แกน Z ตั้งฉากกับพื้นโลก แกน X และ Y จะเป็น 0 แต่ว่าแกน Z จะไม่เป็น 0 เพราะมีแรงโน้มถ่วงของโลกกระทำอยู่ ดังนั้นค่าที่ได้จากแกน Z จะเป็น 9.8 m/s^2 แต่มันเป็นค่าในอุดมคติ

        ในความเป็นจริงเป็นไปไม่ได้อยู่แล้วที่จะได้ค่าเป็น 9.8 ตลอดเวลา แต่ค่าจะเปลี่ยนแปลงไปมาระหว่างค่า 9.8 ต่ำกว่าบ้าง มากกว่าบ้าง ในบทความนี้ขอปัดเป็น 10 ไปเลยนะ (แต่จริงๆแล้วเป็นค่า 9.8)

        ค่าแกน XYZ จะเป็นไปตามทิศทางของเครื่องที่เป็นมือถือดังนี้




        ในกรณีนี้คือเครื่องอยู่นิ่ง ค่า 10 ที่ได้ก็จะเป็นผลของแรง G ของโลก ทีนี้ให้ดูภาพที่เครื่องวางตั้งฉากกับพื้นโลก ที่ค่า X = 0, Y = 10 และ Z = 0 สมมติว่าเครื่องเคลื่อนที่ลง (ความเร่งทิศเดียวกับแรง G ของโลก) ค่าความเร่งที่ได้ก็จะมีมากกว่า 10 (แรงโน้มถ่วงโลก + ความเร่งจากเครื่อง) แต่ถ้าเคลื่อนที่ขึ้นข้างบน ก็จะเป็นการสวนทางกับแรงโน้มถ่วงโลก ค่าที่ได้ก็จะน้อยกว่า 10 (แรงโน้มถ่วงโลก - ความเร่งจากเครื่อง)

        ในกรณีที่อยู่นิ่งๆ แต่แกน XYZ ไม่ได้ตั้งฉากกับพื้นโลกโดยตรงล่ะ? แรงโน้มถ่วงของโลกที่กระทำกับแต่ละแกนของ Accelerometer ก็จะกระจายออกไปในแต่ละแกน ขึ้นอยู่กับการเอียงนั้นๆ แต่เมื่อคิดเวคเตอร์ลัพธ์ที่ตั้งฉากกับพื้นโลกก็มีค่าประมาณ 9.8 อยู่ดี


        และเมื่อเครื่องเคลื่อนที่ ความเร่งก็จะเปลี่ยนแปลงไปตามทิศทางการเคลื่อนที่

        ดังนั้นค่าความเอียง จริงๆแล้วก็คือความเร่งที่เกิดจากแรงโน้มถ่วงของโลกและตอนอ่านค่าจริงจาก Accelerometer ให้ดูวีดีโอข้างล่างนี้เลย


        ถึงแม้ว่า Accelerometer จะอ่านค่าจากความเอียงได้ก็จริง แต่ก็ยังมีจุดบอดคือกรณีที่หมุนตัวเครื่องระนาบกับพื้นโลก Acceletometer จะไม่สามารถรับรู้ถึงความเปลี่ยนแปลงนี้ได้ ทั้งนี้ก็เพราะว่าการที่รับรู้จากการเอียงเครื่องเท่านั้น จึงไม่สามารถรับรู้ถึงการหมุนที่ระนาบกับพื้นโลกได้ ซึ่งในกรณีแบบนี้ก็ให้ใช้ Gyroscope ที่อ่านค่าในเชิงมุม

        ในการใช้งาน Acceleromter นิยมใช้กับ "การเอียงเครื่องหรือเขย่า"

        ทีนี้ก็มาดูตัวอย่างการเขียนเพื่อใช้งาน Accelerometer ต่อเลย สำหรับการใช้งานเซนเซอร์ใดๆก็ตามในอุปกรณ์แอนดรอยด์จะต้องมีคำสั่งเพื่อขอใช้งานเซนเซอร์ตัวนั้นๆด้วย ดังนี้

SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        โดยที่จะให้ sensorManager ดึง Service จำพวกเซนเซอร์มาเก็บไว้ แล้วให้ sensor ซึ่งเป็นคลาสของ Sensor ดึงค่าของ Accelerometer จากตัวแปร sensorManager ตอนนี้ตัวแปร sensor ก็คือ Accelerometer

        ต่อไปก็จะเป็นการเริ่มใช้งาน Accelerometer กันต่อเลย สำหรับการใช้งานจะต้องสั่งผ่านคำสั่ง registerListener เป็นคำสั่งของ Sensor Manager กำหนดให้อ่านค่าจากเซนเซอร์ เพราะปกติเซนเซอร์ Accelerometer จะถูกเรียกใช้งานอยู่แล้ว จึงไม่ต้องมานั่งสั่งให้เซนเซอร์เริ่มทำงานแต่อย่างใดเลย เพียงแค่กำหนด Listener เพื่อรับค่าจากเซนเซอร์ก็ได้แล้ว

sensorManager.registerListener(Listener, Sensor, SensorManager.SENSOR_DELAY_NORMAL);

        สำหรับ Listener ก็คือตัว Listener ที่จะต้องสร้างไว้ เพื่อรอรับค่าจาก Accelerometer เดี๋ยวจะให้ดูทีหลัง ส่วน Sensor ก็คือตัวแปรของคลาส Sensor นั่นเอง ซึ่งจากคำสั่งก่อนหน้า ตัวแปรนั้นก็คือ sensor ดังนั้นในตรงนี้ก็ให้ใส่จาก Sensor เป็น sensor ลงไป ส่วน SensorManager.SENSOR_DELAY_NORMAL คือความเร็วในการอ่านค่าจากเซนเซอร์ มีทั้งหมดดังนี้

        • SENSOR_DELAY_NORMAL (Default) : กำหนดให้เหมาะสมกับความเร็วในเปลี่ยนทิศของหน้าจอ

        • SENSOR_DELAY_UI : กำหนดให้เหมาะสมกับการแสดง UI

        • SENSOR_DELAY_GAME : กำหนดให้เหมาะสมกับแอปพลิเคชันจำพวกเกม

        • SENSOR_DELAY_FASTEST : กำหนดให้เร็วที่สุดเท่าที่จะทำได้

        จากที่เจ้าของบล็อกได้ลอง ก็จะได้ระยะเวลาในการอัพเดตของแต่ละแบบดังนี้

SENSOR_DELAY_NORMAL : 63 - 64 ms
SENSOR_DELAY_UI : 63 - 64 ms
SENSOR_DELAY_GAME : 16 - 18 ms
SENSOR_DELAY_FASTEST : 7 - 10 ms

        อัตราเร็วของข้อมูลเหล่านี้ไม่ได้ตายตัวเสมอไป แต่จะขึ้นอยู่กับการทำงานของเครื่องในตอนนั้นๆด้วยโดยในตัวอย่างนี้ไม่ได้ต้องการความเร็วที่มากมายนัก ก็ขอใช้แค่ SENSOR_DELAY_NORMAL ก็พอแล้ว


        และการกำหนด Listener ให้กับเซนเซอร์ ที่เป็น registerListener ก็ย่อมต้องมีการหยุดการใช้ Listener โดยจะใช้ unregisterListener โดยจะใช้คำสั่งนี้ก็ต่อเมื่อไม่ต้องการจะใช้งานเซนเซอร์แล้ว

SensorManager.unregisterListener(Listener);

        โดยที่ Listener ก็คือตัวแปรของ Listener ที่ประกาศไว้ตอนแรก ในคำสั่ง registerListener นั่นเอง กรณีนี้ก็จะยกเลิก Listener ตัวนั้นๆ

        สำหรับ registerListener จะนิยมเรียกใช้ใน onResume กัน เพื่อให้แอปฯ onResume เมื่อไรก็ให้ใช้งาน Accelerometer ทันที และสำหรับ unregisterListener ก็จะเรียกใช้ใน onPause เพื่อที่ว่าเวลาปิดหรือย่อแอปฯก็จะให้ Listener หยุดทำงาน

public void onResume() {
    super.onResume();
    sensorManager.registerListener(Listener, Sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
 
public void onStop() {
    super.onStop();
    sensorManager.unregisterListener(Listener);
}
     
        ทีนี้มาดูกันต่อที่ส่วนของ Listener ที่ใช้กับเซนเซอร์ โดยจะใช้เป็น Listener แบบ SensorEventListener
ซึ่งจะประกาศเป็นตัวแปรและกำหนดค่าให้เลย เพราะว่าจะต้องมีการเรียกใช้ตัวแปรนี้จากคำสั่ง registerListener และ unregisterListener นั่นเอง

SensorEventListener accelListener = new SensorEventListener() {
    public void onAccuracyChanged(Sensor sensor, int acc) { }
 
    public void onSensorChanged(SensorEvent event) {
        float x = event.values[0];
        float y = event.values[1];
        float z = event.values[2];

        ...
        ...
    }
};

        โดยที่ฟังก์ชัน onAccuracyChanged จะเป็นกรณีที่ ค่าความแม่นยำของเซนเซอร์มีการเปลี่ยนแปลง ซึ่งในฟังก์ชันนี้ ปกติจะไม่ค่อยได้ใช้อะไรกัน จะไปใช้งานในฟังก์ชัน onSensorChanged มากกว่า ซึ่งเป็นฟังก์ชันที่ทำงานเมื่อเซนเซอร์อ่านค่าใหม่หรือก็คือค่าจากเซนเซอร์มีการเปลี่ยนแปลงนั่นเอง

        ให้สังเกตที่ตัวแปรที่ชื่อ event ที่ส่งเข้ามาในฟังก์ชันนี้เป็นของคลาส SensorEvent ที่ส่งค่าจากเซนเซอร์เข้ามา ดังนั้นเวลาที่อ่านค่าจากเซนเซอร์ก็ให้อ่านจากตัวแปรนี้ สำหรับ Accelerometer จะส่งค่ามาในตัวแปรนี้ทั้งสามแกน โดยจะเก็บไว้ในอาร์เรย์แต่ละช่อง X Y Z ตามลำดับ เวลาจะอ่านค่าจากเซนเซอร์ก็อ่านจากคำสั่งดังนี้

float value = event.values[int]; 

        โดย int ก็คือลำดับของอาร์เรย์ที่ต้องการอ่านค่า

        ทีนี้มาดูตัวอย่างในบทความนี้กันบ้างดีกว่า

Main.java
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.widget.TextView;

public class Main extends Activity {
    TextView textX, textY, textZ;
    SensorManager sensorManager;
    Sensor sensor;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        
        textX = (TextView) findViewById(R.id.textX);
        textY = (TextView) findViewById(R.id.textY);
        textZ = (TextView) findViewById(R.id.textZ);
    }
 
    public void onResume() {
        super.onResume();
        sensorManager.registerListener(accelListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
    }
 
    public void onStop() {
        super.onStop();
        sensorManager.unregisterListener(accelListener);
    }

    SensorEventListener accelListener = new SensorEventListener() {
        public void onAccuracyChanged(Sensor sensor, int acc) { }
 
        public void onSensorChanged(SensorEvent event) {
            float x = event.values[0];
            float y = event.values[1];
            float z = event.values[2];

            textX.setText("X : " + (int)x);
            textY.setText("Y : " + (int)y);
            textZ.setText("Z : " + (int)z);
        }
    };
}

        1. ประกาศตัวแปรสำหรับคลาส TextView, Sensormanager และ Sensor

        2. กำหนดค่าให้กับ sensorManager และ sensor โดยเป็น Accelerometer

        3. กำหนดค่าให้กับ TextView ทั้งสาม ที่ใช้สำหรับแสดงค่าจาก XYZ

        4. ฟังก์ชัน onResume โดยจะกำหนด Listener ให้กับ Accelerometer โดยกำหนดให้ใช้ Listener เป็น accelListener ที่ได้สร้างขึ้นมา และกำหนดให้ใช้เซนเซอร์เป็น sensor ที่เป็น Accelerometer

        5. ฟังก์ชัน onPause โดยจะยกเลิก Listener ที่เป็น accelListener

        6. สร้าง SensorEventListener เพื่อเป็น Listener ของ Accelerometer โดยกำหนดชื่อตัวแปรของคลาสนี้เป็นชื่อว่า accelListener

        7. ฟังก์ชันเมื่อค่าความแม่นยำของเซนเซอร์เปลี่ยนแปลง

        8. ฟังก์ชันเมื่อค่าจากเซนเซอร์มีการเปลี่ยนแปลง โดยจะให้อ่านค่าจากแกน XYZ แล้วแสดงบน TextView จะเห็นว่าค่าจาก XYZ จะแปลงจาก Float เป็น Integer ด้วย ทั้งนี้ก็เพราะว่าเพื่อให้ดูค่าได้สบายตา เพราะถ้าค่าเป็น Float ตอนที่แสดงค่าจะมีทศนิยมหลายหลักและเปลี่ยนแปลงไวมาก

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center" >
    
    <TextView
        android:id="@+id/textX"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp" 
        android:text="" />
    <TextView
        android:id="@+id/textY"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20sp" 
        android:textSize="30sp" 
        android:text="" />
    <TextView
        android:id="@+id/textZ"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20sp" 
        android:textSize="30sp" 
        android:text="" />

</LinearLayout>

        เมื่อลองทดสอบดูก็จะเห็นว่ามีค่าแสดงขึ้นที่หน้าจอ โดยเป็นค่าของแกน XYZ ที่ได้จาก Accelerometer จากนั้นก็เอาไปประยุกต์ใช้งานได้ตามใจชอบได้เลย