From 10864996ec0c2e0e3db0c34a1fc020e69571335d Mon Sep 17 00:00:00 2001 From: Simon Lightfoot Date: Fri, 5 Oct 2018 00:14:25 +0100 Subject: [PATCH] Update the Android sensor code. --- .../aeyrium/sensor/AeyriumSensorPlugin.java | 241 +++++++++++------- lib/aeyrium_sensor.dart | 85 ++++-- 2 files changed, 200 insertions(+), 126 deletions(-) diff --git a/android/src/main/java/com/aeyrium/sensor/AeyriumSensorPlugin.java b/android/src/main/java/com/aeyrium/sensor/AeyriumSensorPlugin.java index d96d2bb..3b39528 100644 --- a/android/src/main/java/com/aeyrium/sensor/AeyriumSensorPlugin.java +++ b/android/src/main/java/com/aeyrium/sensor/AeyriumSensorPlugin.java @@ -1,120 +1,165 @@ package com.aeyrium.sensor; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; +import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.view.Surface; import android.view.WindowManager; -import android.app.Activity; -import android.content.Context; -/** AeyriumSensorPlugin */ -public class AeyriumSensorPlugin implements EventChannel.StreamHandler { - - private static final String SENSOR_CHANNEL_NAME = - "plugins.aeyrium.com/sensor"; - private static final int SENSOR_DELAY_MICROS = 1000 * 1000;//16 * 1000; - private WindowManager mWindowManager; - private SensorEventListener sensorEventListener; - private SensorManager sensorManager; - private Sensor sensor; - private int mLastAccuracy; - - /** Plugin registration. */ - public static void registerWith(Registrar registrar) { - final EventChannel sensorChannel = - new EventChannel(registrar.messenger(), SENSOR_CHANNEL_NAME); - sensorChannel.setStreamHandler( - new AeyriumSensorPlugin(registrar.context(), Sensor.TYPE_ROTATION_VECTOR, registrar)); - - } - - private AeyriumSensorPlugin(Context context, int sensorType, Registrar registrar) { - mWindowManager = registrar.activity().getWindow().getWindowManager(); - sensorManager = (SensorManager) context.getSystemService(context.SENSOR_SERVICE); - sensor = sensorManager.getDefaultSensor(sensorType); - } - - @Override - public void onListen(Object arguments, EventChannel.EventSink events) { - sensorEventListener = createSensorEventListener(events); - sensorManager.registerListener(sensorEventListener, sensor, sensorManager.SENSOR_DELAY_UI); - } - - @Override - public void onCancel(Object arguments) { - if (sensorManager != null && sensorEventListener != null){ - sensorManager.unregisterListener(sensorEventListener); +import java.util.Arrays; + +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.PluginRegistry.Registrar; + + +/** + * AeyriumSensorPlugin + */ +public class AeyriumSensorPlugin implements EventChannel.StreamHandler, SensorEventListener { + + private static final String SENSOR_CHANNEL_NAME = "plugins.aeyrium.com/sensor"; + private static final int SENSOR_DELAY_uS = SensorManager.SENSOR_DELAY_UI; + + private final float[] mVec4Rotation = new float[4]; + private final float[] mMat4Rotation = new float[16]; + private final float[] mMat4RotDisplay = new float[16]; + private final float[] mMat4RotRemappedXZ = new float[16]; + private final double[] mVec3Orientation = new double[3]; + + private WindowManager mWindowManager; + private SensorManager mSensorManager; + private Sensor mSensor; + private int mLastAccuracy; + private EventChannel.EventSink mEventSink; + + + /** + * Plugin registration. + */ + public static void registerWith(Registrar registrar) { + final EventChannel sensorChannel = new EventChannel(registrar.messenger(), SENSOR_CHANNEL_NAME); + sensorChannel.setStreamHandler(new AeyriumSensorPlugin(registrar.context(), registrar)); + } - } - SensorEventListener createSensorEventListener(final EventChannel.EventSink events) { - return new SensorEventListener() { - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { + private AeyriumSensorPlugin(Context context, Registrar registrar) { + mWindowManager = registrar.activity().getWindow().getWindowManager(); + mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (mSensorManager == null) { + throw new IllegalStateException("Sensor Manager not found"); + } + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + } + + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + Arrays.fill(mVec4Rotation, 0); + mEventSink = events; + mSensorManager.registerListener(this, mSensor, SENSOR_DELAY_uS); + } + + @Override + public void onCancel(Object arguments) { + mSensorManager.unregisterListener(this, mSensor); + mEventSink = null; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { if (mLastAccuracy != accuracy) { - mLastAccuracy = accuracy; + mLastAccuracy = accuracy; } - } + } + - @Override - public void onSensorChanged(SensorEvent event) { - if (mLastAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { - return; + @Override + public void onSensorChanged(SensorEvent event) { + if (mLastAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE || mEventSink == null) { + return; } - updateOrientation(event.values, events); - } - }; - } - - private void updateOrientation(float[] rotationVector, EventChannel.EventSink events) { - float[] rotationMatrix = new float[9]; - SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector); + // Low-pass filter the incoming event data to smooth it out. + lowPassFilter(event.values, mVec4Rotation, 0.3f); + + // Get the rotation matrix from the smoothed event rotation vector. + SensorManager.getRotationMatrixFromVector(mMat4Rotation, mVec4Rotation); + + // Adjust the matrix to take into account the device orientation. + remapRotationMatrixByDisplay(mWindowManager, mMat4RotDisplay, mMat4Rotation); + + // Remap to adjust for display orientation. + SensorManager.remapCoordinateSystem(mMat4RotDisplay, SensorManager.AXIS_X, + SensorManager.AXIS_Z, mMat4RotRemappedXZ); + + // Capture the orientation vector from the rotation matrix. + //SensorManager.getOrientation(mMat4RotRemappedXZ, mVec3Orientation); + mVec3Orientation[0] = Math.atan2(mMat4RotRemappedXZ[1], mMat4RotRemappedXZ[5]); + mVec3Orientation[1] = Math.asin(-mMat4RotRemappedXZ[9]); + mVec3Orientation[2] = Math.atan2(-mMat4RotRemappedXZ[8], mMat4RotRemappedXZ[10]); - final int worldAxisForDeviceAxisX; - final int worldAxisForDeviceAxisY; + mEventSink.success(mVec3Orientation); + } + + private static void lowPassFilter(float[] input, float[] prev, float alpha) { + if (input == null || prev == null) { + throw new NullPointerException("input and prev float arrays must be non-NULL"); + } + int length = Math.min(input.length, prev.length); + for (int i = 0; i < length; i++) { + prev[i] = prev[i] + alpha * (input[i] - prev[i]); + } + } + /** + * https://android-developers.blogspot.co.uk/2010/09/one-screen-turn-deserves-another.html + */ + @SuppressWarnings("SuspiciousNameCombination") + private static void remapRotationMatrixByDisplay(WindowManager windowManager, float[] outMatrix, float[] inMatrix) { + int x = SensorManager.AXIS_X, y = SensorManager.AXIS_Y; + switch (windowManager.getDefaultDisplay().getRotation()) { + case Surface.ROTATION_0: + x = SensorManager.AXIS_X; + y = SensorManager.AXIS_Y; + break; + case Surface.ROTATION_90: + x = SensorManager.AXIS_Y; + y = SensorManager.AXIS_MINUS_X; + break; + case Surface.ROTATION_180: + x = SensorManager.AXIS_MINUS_X; + y = SensorManager.AXIS_MINUS_Y; + break; + case Surface.ROTATION_270: + x = SensorManager.AXIS_MINUS_Y; + y = SensorManager.AXIS_X; + break; + } + SensorManager.remapCoordinateSystem(inMatrix, x, y, outMatrix); + } + + /* // Remap the axes as if the device screen was the instrument panel, // and adjust the rotation matrix for the device orientation. switch (mWindowManager.getDefaultDisplay().getRotation()) { - case Surface.ROTATION_0: - default: - worldAxisForDeviceAxisX = SensorManager.AXIS_X; - worldAxisForDeviceAxisY = SensorManager.AXIS_Z; - break; - case Surface.ROTATION_90: - worldAxisForDeviceAxisX = SensorManager.AXIS_Z; - worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X; - break; - case Surface.ROTATION_180: - worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X; - worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z; - break; - case Surface.ROTATION_270: - worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z; - worldAxisForDeviceAxisY = SensorManager.AXIS_X; - break; + case Surface.ROTATION_0: + default: + worldAxisForDeviceAxisX = SensorManager.AXIS_X; + worldAxisForDeviceAxisY = SensorManager.AXIS_Z; + break; + case Surface.ROTATION_90: + worldAxisForDeviceAxisX = SensorManager.AXIS_Z; + worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X; + break; + case Surface.ROTATION_180: + worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X; + worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z; + break; + case Surface.ROTATION_270: + worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z; + worldAxisForDeviceAxisY = SensorManager.AXIS_X; + break; } - - - float[] adjustedRotationMatrix = new float[9]; - SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX, - worldAxisForDeviceAxisY, adjustedRotationMatrix); - - // Transform rotation matrix into azimuth/pitch/roll - float[] orientation = new float[3]; - SensorManager.getOrientation(adjustedRotationMatrix, orientation); - - double pitch = - orientation[1]; - double roll = - orientation[2]; - double[] sensorValues = new double[2]; - sensorValues[0] = pitch; - sensorValues[1] = roll; - events.success(sensorValues); - } -} \ No newline at end of file + */ +} diff --git a/lib/aeyrium_sensor.dart b/lib/aeyrium_sensor.dart index 7d133e5..82de6be 100644 --- a/lib/aeyrium_sensor.dart +++ b/lib/aeyrium_sensor.dart @@ -2,40 +2,69 @@ import 'dart:async'; import 'package:flutter/services.dart'; -const EventChannel _sensorEventChannel = - EventChannel('plugins.aeyrium.com/sensor'); +const EventChannel _sensorEventChannel = EventChannel('plugins.aeyrium.com/sensor'); class SensorEvent { - /// Pitch from the device in radians - /// A pitch is a rotation around a lateral (X) axis that passes through the device from side to side - final double pitch; - ///Roll value from the device in radians - ///A roll is a rotation around a longitudinal (Y) axis that passes through the device from its top to bottom - final double roll; + /// Azimuth, angle of rotation about the -z axis in radians. + /// + /// This value represents the angle between the device's y axis and the magnetic + /// north pole. When facing north, this angle is 0, when facing south, this + /// angle is π. Likewise, when facing east, this angle is π/2, and when facing + /// west, this angle is -π/2. + /// + /// The range of values is -π to π. + final double azimuth; - SensorEvent(this.pitch, this.roll); + /// Pitch, angle of rotation about the x axis in radians. + /// + /// A pitch is a rotation around a lateral (X) axis that passes through the device + /// from side to side. + /// + /// This value represents the angle between a plane parallel to the device's screen + /// and a plane parallel to the ground. Assuming that the bottom edge of the device + /// faces the user and that the screen is face-up, tilting the top edge of the + /// device toward the ground creates a positive pitch angle. + /// + /// The range of values is -π to π. + final double pitch; - @override - String toString() => '[Event: (pitch: $pitch, roll: $roll)]'; + ///Roll value from the device in radians + /// Roll, angle of rotation about the y axis in radians. + /// + /// A roll is a rotation around a longitudinal (Y) axis that passes through the device + /// from top to bottom. + /// + /// This value represents the angle between a plane perpendicular to the device's + /// screen and a plane perpendicular to the ground. Assuming that the bottom edge of + /// the device faces the user and that the screen is face-up, tilting the left edge + /// of the device toward the ground creates a positive roll angle. + /// + /// The range of values is -π/2 to π/2. + final double roll; + + SensorEvent(this.azimuth, this.pitch, this.roll); + + @override + String toString() => '[SensorEvent: (azimuth: $azimuth, pitch: $pitch, roll: $roll)]'; } class AeyriumSensor { - static Stream _sensorEvents; - - AeyriumSensor._(); - - /// A broadcast stream of events from the device rotation sensor. - static Stream get sensorEvents { - if (_sensorEvents == null) { - _sensorEvents = _sensorEventChannel - .receiveBroadcastStream() - .map((dynamic event) => _listToSensorEvent(event.cast())); - } - return _sensorEvents; - } - - static SensorEvent _listToSensorEvent(List list) { - return SensorEvent(list[0], list[1]); - } + static Stream _sensorEvents; + + AeyriumSensor._(); + + /// A broadcast stream of events from the device rotation sensor. + static Stream get sensorEvents { + if (_sensorEvents == null) { + _sensorEvents = _sensorEventChannel + .receiveBroadcastStream() + .map((dynamic event) => _listToSensorEvent(event.cast())); + } + return _sensorEvents; + } + + static SensorEvent _listToSensorEvent(List list) { + return SensorEvent(list[0], list[1], list[2]); + } }