Search code examples
androidmultithreadingwear-ossensormanager

Prevent inconsistent queuing of onSensorChanged()


So I have been struggling with this challenge for a while now, exploring different possibilities and narrowing down where the 'queuing' happens. Let me first explain what I am trying to do.

What do I want to do?
I am writing an Android app (running on Google Pixel) with a Wearable service (running on a Sony Smartwatch 3) to retrieve sensor data from the smartwatch as fast as possible, logging the result as Verbose. This currently happens at around 150Hz, data from the TYPE_ROTATION_VECTOR. This data is send over a channel (outputstream) using a PrintWriter and retreived by the phone on the same channel (inputstream) using a BufferReader. I am using the ChannelApi, as the DataApi ensures delivery, yet my aim is to deliver sensor data as fast as possible to real-time, where data loss is of less importance. I am currently reading the ADB on my laptop to use this in another application.

What is the issue / challenge?
After getting the system time at several stages in my program I discovered that that the queuing of data was not the fault of the ADB connection, nor the input or outputstream nor the printwriter. It seems that the function onSensorChanged() is not called instantly as the sensorEvent happens. To illustrate, below are the times send as data, for each onSensorChanged() event, with the sensorListener set to SENSOR_DELAY_GAME (but also accounts for SENSOR_DELAY_FASTEST.

  • 1st row: System.currentTimeMillis() on the watch when onSensorChanged() is called
  • 2nd row: the event.timestamp (divided by 1000000 to go from nano to milli seconds) from the sensorEvent

This gives an overview of a few sensor readings:

1479407287638;    687629;
1479407287638;    687649;
1479407287681;    687669;
1479407287681;    687689;
1479407287718;    687709;
1479407287718;    687729;
1479407287768;    687749;
1479407287768;    687769;
1479407287810;    687789;
1479407287811;    687809;

If you look at the differences between the times you get:

-      -
 0;    20
49;    20
 0;    20
37;    20
 0;    20
50;    20
 0;    20
42;    20
 1;    20

As you can see, the sensorTimestamp indicates there are readings every 20ms. However, the onSensorChanged() is not called at the same intervals, or at least consistency. To point out, even on high speeds the Channel and its input- and outputwriter are able to keep up with the amount of bytes/messages, even on longer periods or time.

What have I tried?

I tried to remove all tasks from onSensorChanged

 public void onSensorChanged(SensorEvent event) {
    final int accuracy = event.accuracy;
    final long timestamp = event.timestamp;
    final float[] values = event.values;
    final String sensorName = event.sensor.getStringType();

    if (data_transfer) {                        // if interaction is initiated
        printWriter.println(message);
    }
}

I tried to remove all tasks from onSensorChanged and executed them elsewhere in an initiated thread

if (data_transfer) {                        // if interaction is initiated
    new Thread(new convertAndSend(sensorName,timestamp,accuracy,values)).run();
}

At first I wrote the wearable app as an activity, but I also converted it to a service (running in the background)

public class SensorService extends Service implements SensorEventListener {
...
    public void onSensorChanged(SensorEvent event) {
        client.sendSensorData(event.sensor.getType(), event.accuracy, event.timestamp/1000000, event.values); //client takes care of outputstream
    }
}

Lastly, I thought about implemeting the SensorEventListener in a seperate thread (based on this and this Stackoverflow Q&A, so it would not be influenced by the Activity or Service thread. However, this also showed the same issue / challenge as mentioned before.

public class SensorListenerThread implements Runnable {

    private static final String TAG = "SensorService";
    private final static int SENS_ROTATION_VECTOR = Sensor.TYPE_ROTATION_VECTOR;

    SensorManager mSensorManager;

    @Override
    public void run() {
        Log.d( "RunTag", Thread.currentThread().getName() ); // To display thread

        mSensorManager = ((SensorManager)getSystemService(SENSOR_SERVICE));

        Looper.prepare();
        Handler handler = new Handler(){
            // process incoming messages here??
        };
        Sensor rotationVectorSensor = mSensorManager.getDefaultSensor(SENS_ROTATION_VECTOR);
        MySensorListener msl = new MySensorListener();
        mSensorManager.registerListener(msl, rotationVectorSensor, SensorManager.SENSOR_DELAY_FASTEST, handler);

        Looper.loop();
    }

    private class MySensorListener implements SensorEventListener {
        public void onAccuracyChanged (Sensor sensor, int accuracy) {}
        public void onSensorChanged(SensorEvent sensorEvent) {
            Log.d( "ListenerTag", Thread.currentThread().getName() ); // To display thread
        }
    }

}

Help!
My understanding is that the onSensorChanged event is called however not at the same times as the timestamp of the sensor seems to indicate. The speed and using the channel work very well, however I cannot seem to get a workaround this issue. I have a plan B in my mind: I can use the sensor timestamp to calculate/adjust the program I am running on my laptop. However, I prefer only to adjust for the delay in communication (from sensor --> laptop application, which could be a variable that is changed every now and then) and not also adjust for the inconsistency in communication (differences in printing the values, which need to be calculated for each reading).

I hope this long (sorry!) story makes sense and someone can help me or point me in the correct direction!


Solution

  • As it seems, it might not be possible to rely on a consistent call of onSensorChanged(). I am therefore using the timestamps rather than the time of onSensorChanged. I would advice others to do the same!