Skip to content

Instantly share code, notes, and snippets.

@bananaumai
Created December 3, 2019 09:37
Show Gist options
  • Select an option

  • Save bananaumai/ad3c4d20b56e3eef6d9e78fb972f878b to your computer and use it in GitHub Desktop.

Select an option

Save bananaumai/ad3c4d20b56e3eef6d9e78fb972f878b to your computer and use it in GitHub Desktop.
Android Accelerometer to event (with calibration steps)
@ExperimentalCoroutinesApi
private fun accelerationFlow(context: Context): Flow<Acceleration> = channelFlow {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val listener = object : SensorEventListener {
private val sampleNumber = 20
private val samples: MutableList<Acceleration.Coordinate> = mutableListOf()
private var offset: Acceleration.Coordinate? = null
private var calibrated: Acceleration.Coordinate? = null
private var isReadyToEmit = false
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type != Sensor.TYPE_ACCELEROMETER) {
return
}
// The unit of Android's 3 axes acceleration data is m/s2.
// Convert into gravity acceleration
val raw = Acceleration.Coordinate(
lat = event.values[0].toDouble() / 9.80665,
lon = event.values[2].toDouble() / 9.80665 * -1,
vert = event.values[1].toDouble() / 9.80665
)
if (!isReadyToEmit) {
samples.add(raw)
if (samples.count() >= sampleNumber) {
isReadyToEmit = true
Timber.d("Ready to emit acceleration data, offset is $offset")
}
return
}
calibrate(raw)
CoroutineScope(coroutineContext).launch {
send(Acceleration(raw, calibrated!!))
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
private fun calibrate(raw: Acceleration.Coordinate) {
/*
* offset calculation is based on the following logic
* (applying low-pass filter to offset calculation)
* https://developer.android.com/guide/topics/sensors/sensors_motion
*/
val offsetAlpha = 0.9
offset = if (offset == null) {
Acceleration.Coordinate(
samples.map { it.lat }.average(),
samples.map { it.lon }.average(),
samples.map { it.vert }.average()
)
} else {
Acceleration.Coordinate(
offsetAlpha * offset!!.lat + (1 - offsetAlpha) * raw.lat,
offsetAlpha * offset!!.lon + (1 - offsetAlpha) * raw.lon,
offsetAlpha * offset!!.vert + (1 - offsetAlpha) * raw.vert
)
}
/*
* applying low-pass filter in order to ignore noisy acceleration
*/
val calibrationAlpha = 0.8
calibrated = if (calibrated == null) {
Acceleration.Coordinate(
raw.lat - offset!!.lat,
raw.lon - offset!!.lon,
raw.vert - offset!!.vert
)
} else {
Acceleration.Coordinate(
calibrationAlpha * calibrated!!.lat + (1 - calibrationAlpha) * (raw.lat - offset!!.lat),
calibrationAlpha * calibrated!!.lon + (1 - calibrationAlpha) * (raw.lon - offset!!.lon),
calibrationAlpha * calibrated!!.vert + (1 - calibrationAlpha) * (raw.vert - offset!!.vert)
)
}
}
}
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
val handlerThread =
HandlerThread("accelerometer-handler-thread", Process.THREAD_PRIORITY_BACKGROUND).apply { start() }
val handler = Handler(handlerThread.looper)
withContext(handler.asCoroutineDispatcher()) {
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL, null)
}
awaitClose {
sensorManager.unregisterListener(listener, sensor)
handlerThread.quitSafely()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment