Tuesday 5 February 2019

[Tutorial] Create a Sensor Data Collection Android App

Bang Wu, Guangyuan Zhang, Stefan Poslad
IoTUSLab, QMUL
This lab is available at the IoTUSLab website where

Contents

Background & Motivation
Aims
An Introduction to Smartphone sensors
Development Process
    1. Create a new project (using Android Studio)
    2. Add a Control in the activity_main.xml
    3. Define a TextView type variable in Main_activity.java.
    4. To collect the sensor data, we first need to get the system sensor service
    5. Declare the sensors, get the services and register as shown below. //MainActivity.java
    6. Get the sensors' response
    7. Three self-defined functions or variables are used to format the output. //MainActivity.java
    8. Test the sensor app to check it generates sensor data output and this changes as the phone moves
Conclusion
Further Work
Reference

Background & Motivation

Mobile applications are a very important segment in the software market and the competition in the mobile application market is fiercer every year. The need for rapid application development and deployment has never been greater. In recent years the majority of mobile applications have been developed primarily for smartphones. Today’s smartphones incorporate numerous sensors.  

Depending on the particular smartphone model, the sensor count may include an accelerometer, gyroscope, compass, GPS, microphone, camera, proximity sensor, light sensor, temperature sensor, pressure sensor, etc. Smartphone sensors are being employed in a growing number of mobile applications. Among these, mobile sensing applications are playing an increasingly important role in many aspects of our lives, such as health, fitness, sports, social networking, environmental monitoring, public infrastructure, navigation, and urban sensing.

Smartphone sensors are so important that can be widely used in mobile services. So in this lab, we will teach you how to create a sensor data collecting Android app. In the lab sheet of last week, you learned how to build and launch a basic "HelloWorld!" android app. So you may be familiar with the development environment of Android. In this lab sheet, you may need to recall the last lab to smooth your development.

Aims

Main Expectation: To build and launch an Android app using Android studio to collect data of smartphone sensors.
The aims of this session are:
  1. To know some common-used smartphone sensors (Motion sensors: Accelerometer, Gyroscope, Magnetometer; Environment Sensors: Ambient Temperature, Light, Pressure, Humidity, Device Temperature)
  2. To become familiar with the data structure of these sensors in Google’s Android.
  3. To become familiar with using the control "TextView" and setting its attributions. 
  4. To become familiar with the process of calling built-in sensors including variable declaration, getting system sensor services, registering listeners and automatically updating the sensor readings.
  5. be able to create self-defined functions.

An Introduction to Smartphone sensors

The Android platform provides several sensors that let you monitor the motion of a device. The sensors' implementation vary by sensor type: The gravity, linear acceleration, rotation vector, significant motion, step counter, and step detector sensors are either hardware-based or software-based. But here, we only focus on three main hardware-based sensors: Accelerometer, Gyroscope, Magnetometer. Before we talk about these motion sensors, we need to know the definition of the coordinate system used by the SensorEvent API.
The coordinate system is defined relative to the screen of the phone in its default orientation. The axes are not swapped when the device's screen orientation changes. The X axis is horizontal and points to the right, the Y axis is vertical and points up and the Z-axis points towards the outside of the front face of the screen. In this system, coordinates behind the screen have negative Z values.
Fig. 1 The definition of the coordinate system used by the SensorEvent API

An accelerometer is a device that measures proper acceleration. On the Android platform, the data structure of accelerometer is in the form of: 
All values are in SI units (m/s^2)
values[0]: Acceleration minus Gx on the x-axis
values[1]: Acceleration minus Gy on the y-axis
values[2]: Acceleration minus Gz on the z-axis
A sensor of this type measures the acceleration applied to the device (Ad). Conceptually, it does so by measuring forces applied to the sensor itself (Fs) using the relation:
Ad = - ∑Fs / mass
In particular, the force of gravity is always influencing the measured acceleration:
Ad = -g - ∑F / mass
For this reason, when the device is sitting on a table (and obviously not accelerating), the accelerometer reads a magnitude of g = 9.81 m/s^2. Similarly, when the device is in free-fall and therefore dangerously accelerating towards to ground at 9.81 m/s^2, its accelerometer reads a magnitude of 0 m/s^2.
All values are in radians/second and measure the rate of rotation around the device's local X, Y and Z axis. The coordinate system is the same as is used for the acceleration sensor. Rotation is positive in the counter-clockwise direction. That is, an observer looking from some positive location on the x, y or z axis at a device positioned on the origin would report positive rotation if the device appeared to be rotating counter clockwise. Note that this is the standard mathematical definition of positive rotation and does not agree with the definition of roll given earlier.
values[0]: Angular speed around the x-axis
values[1]: Angular speed around the y-axis
values[2]: Angular speed around the z-axis
Typically the output of the gyroscope is integrated over time to calculate a rotation describing the change of angles over the time step,

All values are in micro-Tesla (uT) and measure the ambient magnetic field in the X, Y and Z axis.
Similarly, There are also three values for this sensor:
values[0]: Magnetic field around the x-axis
values[1]: Magnetic field around the y-axis
values[2]: Magnetic field around the z-axis

The Android platform provides four sensors that let you monitor various environmental properties. You can use these sensors to monitor relative ambient humidity, illuminance, ambient pressure, and ambient temperature near an Android-powered device. All four environment sensors are hardware-based and are available only if a device manufacturer has built them into a device. With the exception of the light sensor, which most device manufacturers use to control screen brightness, environment sensors are not always available on devices. Because of this, it's particularly important that you verify at runtime whether an environment sensor exists before you attempt to acquire data from it. 
The data struture for these five sensors (Ambient Temperature, Light, Pressure, Humidity,Temperature) is in the form:

Development Process

This has the following steps
1. Create a new project (using Android Studio)
(Define the layout of the view)
2. Add a Control – define where to control the sensor data start and end
3. Define a TextView- define where to present the data
(Setup the sensor system to acquire data)
4. Request the system sensor service - we do this first
5. Declare the sensors, get the services and register
6. Get the sensors' response – start the data collection
7. Display and format the sensor data output
8. Test the sensor app - to check it generates sensor data output and this changes as the phone moves

1. Create a new project (using Android Studio)

Fig. 2
Fig. 3
Fig. 4

2. Add a Control in the activity_main.xml

Fig. 5

Because there is a default textview "Hello World". You can just edit this one instead of creating a new one.
Fig. 6
Do some simple setting for this control as shown below (activity_main.xml/Text)
//activity_main.xml/Text
<?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=".MainActivity">

    <TextView
        android:id="@+id/txSensors"
android:layout_width="331dp" android:layout_height="508dp"
        android:lines="10"        android:maxLines="10"
android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>

3. Define a TextView type variable in Main_activity.java

Create it for your app in the function protected void onCreate(Bundle savedInstanceState)
//MainActivity.java
public class MainActivity extends AppCompatActivity {

    //variables definition    TextView textView;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.txSensors);

    }
}

4. To collect the sensor data, we first need to get the system sensor service. 

The code here is used to display all sensors.
//MainActivity.java
public class MainActivity extends AppCompatActivity {

    //variables definition    TextView textView;
    private SensorManager sensorManager;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.txSensors);

        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        List<Sensor> list = sensorManager.getSensorList(Sensor.TYPE_ALL);  // collection of sensors        for (Sensor sensor:list){
            textView.append(sensor.getName() + "\n");  //display all sensors the Android platform provides. 
                            //However, some hardware-based sensors need support from current smartphone        }

    }
}

Then we will focus on some motion sensors and environment sensors as described above.

5. Declare the sensors, get the services and register as shown below.

//MainActivity.java
// declare
private Sensor accelerometer;
private Sensor gyroscope;
private Sensor magnetic;
private Sensor pressure;
private Sensor ambientTemperature;
private Sensor proximity;
private Sensor light;
private Sensor temperature;

// get the services
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
ambientTemperature = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
light = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
pressure = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
temperature = sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);

// registerListener//SENSOR_DELAY_NORMAL determines the update rate of sensor data.sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); 
sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, ambientTemperature, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, light, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, pressure, SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this, temperature, SensorManager.SENSOR_DELAY_NORMAL);
Then we need to let our class to implements SensorEventListener. Manually change the declaration of class like:
//MainActivity.java
public class MainActivity extends AppCompatActivity implements SensorEventListener {
Then the system will automatically generate two functions:
//MainActivity.java
@Overridepublic void onSensorChanged(SensorEvent sensorEvent) {

}

@Overridepublic void onAccuracyChanged(Sensor sensor, int i) {

}

So the final code for this step is:
//MainActivity.java
public class MainActivity extends AppCompatActivity implements SensorEventListener {

    //variables definition    TextView textView;
    private SensorManager sensorManager;
    // declare    private Sensor accelerometer;
    private Sensor gyroscope;
    private Sensor magnetic;
    private Sensor pressure;
    private Sensor ambientTemperature;
    private Sensor proximity;
    private Sensor light;
    private Sensor temperature;
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.txSensors);

        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
//        List<Sensor> list = sensorManager.getSensorList(Sensor.TYPE_ALL);  // collection of sensors//        for (Sensor sensor:list){//            textView.append(sensor.getName() + "\n");  //display all sensors the Android platform provides. However, some hardware-based sensors need support from current smartphone//        }
        // get the services        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
        magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        ambientTemperature = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        light = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        pressure = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
        temperature = sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);

        // registerListener        //SENSOR_DELAY_NORMAL determines the update rate of sensor data.        sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, ambientTemperature, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, light, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, pressure, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, temperature, SensorManager.SENSOR_DELAY_NORMAL);

    }

    @Override    public void onSensorChanged(SensorEvent sensorEvent) {

    }

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

    }
}

6. Get the sensors' response

These two automatically generated functions (onSensorChanged,onAccuracyChanged) provide interfaces to get the sensors' response.
The sensorChanged-based function will automatically be triggered when sensorManager changes the sensor type from one to another.
similarly, the accuracyChanged-based function will automatically be triggered when the accuracy of sensor readings changes.
Here, I will take the sensorChanged-based function to perform our requirements.
//MainActivity.java
private List<String> rawDataList = new ArrayList<>();

@Override
public void onSensorChanged(SensorEvent sensorEvent) {
        Sensor sensor = sensorEvent.sensor;
        int i = sensor.getType();
        String singleLine = null;

        if (motionSensors.contains(i)) {
            singleLine = String.format(Locale.ENGLISH, "%s,%.3f,%.3f,%.3f", getSensorName(i),sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2]);
        }
        else if (environmentSensors.contains(i)) {
            singleLine = String.format(Locale.ENGLISH, "%s,%.3f", getSensorName(i), sensorEvent.values[0]);
        }

        if (singleLine!=null){
//            Log.d(TAG,"The output: " + singleLine);            rawDataList.add(singleLine);
        }

        if (rawDataList.size() > 10){
            String outPut = "";
            for(String str: rawDataList)
            {
                outPut = outPut + str + ";";
            }
            textView.setText(outPut);
        }
}

7. Three self-defined functions or variables are used to format the output.

//MainActivity.java
private Set<Integer> motionSensors = new HashSet<>(Arrays.asList(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE        , Sensor.TYPE_MAGNETIC_FIELD));

private Set<Integer> environmentSensors = new HashSet<>(Arrays.asList(Sensor.TYPE_AMBIENT_TEMPERATURE, Sensor.TYPE_PRESSURE, Sensor.TYPE_PROXIMITY,
        Sensor.TYPE_LIGHT, Sensor.TYPE_TEMPERATURE));

public String getSensorName(int senorType){
    String senStringName;
    switch (senorType) {
        case Sensor.TYPE_ACCELEROMETER:
            senStringName = "ACCE";
            break;
        case Sensor.TYPE_GYROSCOPE:
            senStringName = "GYRO";
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            senStringName = "MAGN";
            break;
        case Sensor.TYPE_LIGHT:
            senStringName = "LIGH";
            break;
        case Sensor.TYPE_PRESSURE:
            senStringName = "PRES";
            break;
        case Sensor.TYPE_PROXIMITY:
            senStringName = "PROX";
            break;
        case Sensor.TYPE_RELATIVE_HUMIDITY:
            senStringName = "HUMI";
            break;
        case Sensor.TYPE_AMBIENT_TEMPERATURE:
            senStringName = "ATEM";
            break;
        case Sensor.TYPE_TEMPERATURE:
            senStringName = "TEMP";
            break;
        default:
            senStringName = "";
            break;
    }
    return senStringName;
}

8. Test the sensor app to check it generates sensor data output and this changes as the phone moves.
Fig. 7 Output in Virtual Machine

The complete code for MainActivity.java is:
//MainActivity.java
public class MainActivity extends AppCompatActivity implements SensorEventListener {

    private static final String TAG = "MainActivity";
    //variables definition    TextView textView;
    private SensorManager sensorManager;
    // declare    private Sensor accelerometer;
    private Sensor gyroscope;
    private Sensor magnetic;
    private Sensor pressure;
    private Sensor ambientTemperature;
    private Sensor proximity;
    private Sensor light;
    private Sensor temperature;

    private List<String> rawDataList = new ArrayList<>();
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.txSensors);

        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
//        List<Sensor> list = sensorManager.getSensorList(Sensor.TYPE_ALL);  // collection of sensors//        for (Sensor sensor:list){//            textView.append(sensor.getName() + "\n");  //display all sensors the Android platform provides. However, some hardware-based sensors need support from current smartphone//        }
        // get the services        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
        magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        ambientTemperature = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        light = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        pressure = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
        temperature = sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);

        // registerListener        //SENSOR_DELAY_NORMAL determines the update rate of sensor data.        sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, ambientTemperature, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, light, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, pressure, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, temperature, SensorManager.SENSOR_DELAY_NORMAL);

    }

    @Override    public void onSensorChanged(SensorEvent sensorEvent) {
        Sensor sensor = sensorEvent.sensor;
        int i = sensor.getType();
        String singleLine = null;

        if (motionSensors.contains(i)) {
            singleLine = String.format(Locale.ENGLISH, "%s,%.3f,%.3f,%.3f", getSensorName(i),sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2]);
        }
        else if (environmentSensors.contains(i)) {
            singleLine = String.format(Locale.ENGLISH, "%s,%.3f", getSensorName(i), sensorEvent.values[0]);
        }

        if (singleLine!=null){
//            Log.d(TAG,"The output: " + singleLine);            rawDataList.add(singleLine);
        }

        if (rawDataList.size() > 10){
            String outPut = "";
            for(String str: rawDataList)
            {
                outPut = outPut + str + ";";
            }
            textView.setText(outPut);
        }

    }

    private Set<Integer> motionSensors = new HashSet<>(Arrays.asList(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE            , Sensor.TYPE_MAGNETIC_FIELD));

    private Set<Integer> environmentSensors = new HashSet<>(Arrays.asList(Sensor.TYPE_AMBIENT_TEMPERATURE, Sensor.TYPE_PRESSURE, Sensor.TYPE_PROXIMITY,
            Sensor.TYPE_LIGHT, Sensor.TYPE_TEMPERATURE));

    public String getSensorName(int senorType){
        String senStringName;
        switch (senorType) {
            case Sensor.TYPE_ACCELEROMETER:
                senStringName = "ACCE";
                break;
            case Sensor.TYPE_GYROSCOPE:
                senStringName = "GYRO";
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                senStringName = "MAGN";
                break;
            case Sensor.TYPE_LIGHT:
                senStringName = "LIGH";
                break;
            case Sensor.TYPE_PRESSURE:
                senStringName = "PRES";
                break;
            case Sensor.TYPE_PROXIMITY:
                senStringName = "PROX";
                break;
            case Sensor.TYPE_RELATIVE_HUMIDITY:
                senStringName = "HUMI";
                break;
            case Sensor.TYPE_AMBIENT_TEMPERATURE:
                senStringName = "ATEM";
                break;
            case Sensor.TYPE_TEMPERATURE:
                senStringName = "TEMP";
                break;
            default:
                senStringName = "";
                break;
        }
        return senStringName;
    }

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

    }
}

Conclusion

In this lab sheet, we gave a tutorial about how to create a smartphone sensor data collection Android application. In this application, you are able to collect data from some typical motion sensors and environmental sensors, and display the data in your app.
Of course, this sample is not complicated so it is very suitable for novices.

ConclusionFeature work

But if you have much experience with the Android platform. Here are some suggestions about further editing the functions of this app:
1) how to record the time information as well as the accuracy of each reading.
2) how to set/change the sampling frequency of these sensors.
3) how to save readings into a file in your phone. 

Reference



No comments:

Post a Comment

[Research] Recurrent Neural Network (RNN)

1. Introduction As we all know, forward neural networks (FNN) have no connection between neurons in the same layer or in cross layers. Th...