Artyom Okun
Artyom Okun

Reputation: 985

Android Wear Accelerometer data gaps

I have a service which continuously runs in the background on Moto 360. It measures the accelerometer data in 50Hz. I am packing the samples in chunks(15,000 samples each chunk == 5 mins of data) and send these chunks to the mobile device through DataApi. Everything works great but randomly I have gaps in events coming from sensor change listener. The gaps in sampling can occur once or twice in 10 hours and the time period of gaps can be 1-10 minutes each gap.

What was done till now:

  1. All sampling processing/packaging/logging/sending done on the separate thread so nothing blocking the sensor listener.
  2. The Service is sticky and logs are added to the life cycle callback, so I can be sure the service is alive at the sampling gaps time.
  3. The service declared as foreground service to get more CPU time and higher priority.
  4. I acquire PARTIAL_WAKE_LOCK in the onCreate of the service and release it in onDestroy (I know the possible impact on the battery, but at this point, the continuous data is more important).

What can be the reason and a solution for the gaps in sampling?

Thanks in advance.

My Service code:

public class MeasurementService extends Service implements SensorEventListener {

public final static int SENS_ACCELEROMETER = Sensor.TYPE_ACCELEROMETER;

private static int mCounter;

private ArrayList<AccelerometerSampleData> mAccelerometerSensorSamples;

@Inject
SensorManager mSensorManager;

@Inject
DataTransferHolder mDataTransferHolder;

@Inject
EventBus mEventBus;

@Inject
WearSharedPrefsController mSharedPrefsController;

@Inject
WearConfigController mConfigController;

@Inject
MyWearLogger mMyWearLogger;

private Sensor mAccelerometerSensor;

private AccelerometerSampleData mLastEventData;

protected HandlerThread handlerThread;

private PowerManager.WakeLock mWakeLock;

@Override
public void onCreate() {
    super.onCreate();
    ((MyWearApplication)getApplication()).getApplicationComponent().inject(this);
    mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
            "onCreate");
    initSensors();
    mEventBus.register(this);
    startThread();
    acquireWakeLock();
    resetPackageValues();
    mSharedPrefsController.setMessagePackageIndex(0);
    startForeground();
    startMeasurement();
    mEventBus.postSticky(new MeasurementServiceStatus(true));
}

private void startForeground() {
    Notification.Builder builder = new Notification.Builder(this);
    builder.setContentTitle("Measurement Service");
    builder.setContentText("Collecting sensor data..");
    builder.setSmallIcon(R.drawable.ic_play_circle_outline_black_48dp);
    startForeground(1, builder.build());
}

private void acquireWakeLock() {
    if (mWakeLock == null || !mWakeLock.isHeld()) {
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SensorAdapter Lock");
        mWakeLock.acquire();
    }
}

private void releaseWakeLock() {
    if (mWakeLock != null && mWakeLock.isHeld()) {
        mWakeLock.release();
        mWakeLock = null;
    }
}

@SuppressWarnings("unused")
@Subscribe
public void onSamplingRateUpdated(UpdateSamplingRateMessage updateSamplingRateMessage) {
    stopMeasurement();
    startMeasurement();
}

@SuppressWarnings("unused")
@Subscribe
public void onSamplesPerPackageLimitUpdated(UpdateChunkLimitMessage chunkLimitMessage) {
    stopMeasurement();
    startMeasurement();
}

private void startThread() {
    if (handlerThread == null) {
        handlerThread = new HandlerThread(this.getClass().getSimpleName() + "Thread");
    }
    if (handlerThread.getState() == Thread.State.NEW) {
        handlerThread.start();
    }
}

private void resetPackageValues() {
    mCounter = 0;
    mAccelerometerSensorSamples = new ArrayList<>();
}

private void initSensors() {
    Timber.d("initiating sensors");

    if (mAccelerometerSensor == null) {
        mAccelerometerSensor = mSensorManager.getDefaultSensor(SENS_ACCELEROMETER, true);
        if (mAccelerometerSensor == null){
            mAccelerometerSensor = mSensorManager.getDefaultSensor(SENS_ACCELEROMETER);
        }
    }

}

private void startMeasurement() {
    Timber.d("starting measurement");
    if (checkNotNull()) {
        Timber.d("sensors are valid, registering listeners");
        Handler handler = new Handler(handlerThread.getLooper());
        // This buffer is max 300 on Moto 360, so we use 250;
        int maxSamplesBuffer = 250 * mConfigController.getSamplingRateMicrosecond();
        mSensorManager.registerListener(this,
                mAccelerometerSensor,
                mConfigController.getSamplingRateMicrosecond(),
                maxSamplesBuffer,
                handler);
    } else {
        Timber.w("sensors are null");
    }
}

private void stopMeasurement() {
    mSensorManager.unregisterListener(this);
}

private boolean checkNotNull() {
    Timber.d("checking sensors validity");
    return mSensorManager != null
            && mAccelerometerSensor != null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
            "onStartCommand");
    return START_STICKY;
}

@Override
public void onDestroy() {
    super.onDestroy();
    mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
            "onDestroy");
    stopMeasurement();
    releaseWakeLock();
    stopThread();
    mEventBus.unregister(this);
    mEventBus.postSticky(new MeasurementServiceStatus(false));
}

private void stopThread() {
    if (handlerThread != null) {
        handlerThread.quit();
    }
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onSensorChanged(SensorEvent newEvent) {

    AccelerometerSampleData newEventData = new AccelerometerSampleData(
            System.currentTimeMillis() + ((newEvent.timestamp - SystemClock.elapsedRealtimeNanos()) / 1000000L),
            newEvent.values[0],
            newEvent.values[1],
            newEvent.values[2]);

    if (DefaultConfiguration.LOG_EACH_SAMPLE) {
        logData(newEventData, calculateTimeDiff(newEventData));
    }

    addNewEventToPackage(newEventData);
    updateCurrentValues(newEventData);

    if (mCounter >= mConfigController.getSamplesPerChunk()) {
        float batteryPercentage = getBatteryStatus();
        sendPackageToMobileDevice(batteryPercentage);
        resetPackageValues();
    }
}

private void sendPackageToMobileDevice(float batteryPercentage) {
    Timber.i("sending package to processing service");

    long messagePackageId = System.currentTimeMillis();
    MessagePackage messagePackage = createMessagePackage(mAccelerometerSensorSamples, batteryPercentage);
    mMyWearLogger.logChunkToFile(messagePackage);

    // Sending package in singleton holder
    mDataTransferHolder.getQueueOfMessagePackages().put(messagePackageId, messagePackage);

    Intent sendPackageIntent = new Intent(this, DataProcessingService.class);
    sendPackageIntent.putExtra(MESSAGE_PACKAGE_ID, messagePackageId);
    startService(sendPackageIntent);
}

private MessagePackage createMessagePackage(ArrayList<AccelerometerSampleData> mAccelerometerSensorSamples, float batteryPercentage) {
    MessagePackage messagePackage = new MessagePackage();
    messagePackage.setAccelerometerSamples(mAccelerometerSensorSamples);
    messagePackage.setBatteryPercentage(batteryPercentage);
    return messagePackage;
}

private float getBatteryStatus() {

    IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = this.registerReceiver(null, ifilter);

    float batteryPercentage;
    if (batteryStatus != null) {
        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        batteryPercentage = level / (float) scale;
    } else {
        Timber.w("failed to retrieve battery percentage");
        batteryPercentage = -1;
    }

    return batteryPercentage;
}

private void addNewEventToPackage(AccelerometerSampleData newEventData) {
    mAccelerometerSensorSamples.add(newEventData);
}

private void updateCurrentValues(AccelerometerSampleData newEventData) {
    mLastEventData = newEventData;
    mCounter++;
}

private void logData(AccelerometerSampleData newEventData, long diff) {
//        if (diff > MAX_ALLOWED_SAMPLES_DIFF_IN_MILLIS
//                || diff < MIN_ALLOWED_SAMPLES_DIFF_IN_MILLIS) {
    Timber.d("new accelerometer event, timestamp: %s, time difference: %s milliseconds",
            newEventData.getTimestamp(), diff);
//        }
}

private long calculateTimeDiff(AccelerometerSampleData newEventData) {

    if (mLastEventData == null) {
        return 0;
    }

    return (newEventData.getTimestamp() - mLastEventData.getTimestamp());

}

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

}

}

Upvotes: 0

Views: 182

Answers (0)

Related Questions