summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java386
-rwxr-xr-xtools/orientationplot/orientationplot.py116
2 files changed, 303 insertions, 199 deletions
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index c3c74a7c59c5..c28b22050519 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -21,6 +21,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.util.FloatMath;
import android.util.Log;
import android.util.Slog;
@@ -48,6 +49,8 @@ public abstract class WindowOrientationListener {
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG || false;
+ private static final boolean USE_GRAVITY_SENSOR = false;
+
private SensorManager mSensorManager;
private boolean mEnabled;
private int mRate;
@@ -79,7 +82,8 @@ public abstract class WindowOrientationListener {
private WindowOrientationListener(Context context, int rate) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
- mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
+ ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
mSensorEventListener = new SensorEventListenerImpl(this);
@@ -179,7 +183,7 @@ public abstract class WindowOrientationListener {
* cartesian space because the orientation calculations are sensitive to the
* absolute magnitude of the acceleration. In particular, there are singularities
* in the calculation as the magnitude approaches 0. By performing the low-pass
- * filtering early, we can eliminate high-frequency impulses systematically.
+ * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
*
* - Convert the acceleromter vector from cartesian to spherical coordinates.
* Since we're dealing with rotation of the device, this is the sensible coordinate
@@ -204,11 +208,17 @@ public abstract class WindowOrientationListener {
* new orientation proposal.
*
* Details are explained inline.
+ *
+ * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
+ * signal processing background.
*/
static final class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
+ // Number of nanoseconds per millisecond.
+ private static final long NANOS_PER_MS = 1000000;
+
// Indices into SensorEvent.values for the accelerometer sensor.
private static final int ACCELEROMETER_DATA_X = 0;
private static final int ACCELEROMETER_DATA_Y = 1;
@@ -216,38 +226,41 @@ public abstract class WindowOrientationListener {
private final WindowOrientationListener mOrientationListener;
- /* State for first order low-pass filtering of accelerometer data.
- * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
- * signal processing background.
- */
-
- private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds
- private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
-
- // The current proposal. We wait for the proposal to be stable for a
- // certain amount of time before accepting it.
- //
- // The basic idea is to ignore intermediate poses of the device while the
- // user is picking up, putting down or turning the device.
- private int mProposalRotation;
- private long mProposalAgeMS;
-
- // A historical trace of tilt and orientation angles. Used to determine whether
- // the device posture has settled down.
- private static final int HISTORY_SIZE = 20;
- private int mHistoryIndex; // index of most recent sample
- private int mHistoryLength; // length of historical trace
- private final long[] mHistoryTimestampMS = new long[HISTORY_SIZE];
- private final float[] mHistoryMagnitudes = new float[HISTORY_SIZE];
- private final int[] mHistoryTiltAngles = new int[HISTORY_SIZE];
- private final int[] mHistoryOrientationAngles = new int[HISTORY_SIZE];
+ // The minimum amount of time that a predicted rotation must be stable before it
+ // is accepted as a valid rotation proposal. This value can be quite small because
+ // the low-pass filter already suppresses most of the noise so we're really just
+ // looking for quick confirmation that the last few samples are in agreement as to
+ // the desired orientation.
+ private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the device last exited
+ // the flat state (time since it was picked up) before the proposed rotation
+ // can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
+
+ // The mininum amount of time that must have elapsed since the device stopped
+ // swinging (time since device appeared to be in the process of being put down
+ // or put away into a pocket) before the proposed rotation can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
+
+ // If the tilt angle remains greater than the specified angle for a minimum of
+ // the specified time, then the device is deemed to be lying flat
+ // (just chillin' on a table).
+ private static final float FLAT_ANGLE = 75;
+ private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
+
+ // If the tilt angle has increased by at least delta degrees within the specified amount
+ // of time, then the device is deemed to be swinging away from the user
+ // down towards flat (tilt = 90).
+ private static final float SWING_AWAY_ANGLE_DELTA = 20;
+ private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
// The maximum sample inter-arrival time in milliseconds.
// If the acceleration samples are further apart than this amount in time, we reset the
// state of the low-pass filter and orientation properties. This helps to handle
// boundary conditions when the device is turned on, wakes from suspend or there is
// a significant gap in samples.
- private static final float MAX_FILTER_DELTA_TIME_MS = 1000;
+ private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
// The acceleration filter time constant.
//
@@ -267,8 +280,10 @@ public abstract class WindowOrientationListener {
//
// Filtering adds latency proportional the time constant (inversely proportional
// to the cutoff frequency) so we don't want to make the time constant too
- // large or we can lose responsiveness.
- private static final float FILTER_TIME_CONSTANT_MS = 100.0f;
+ // large or we can lose responsiveness. Likewise we don't want to make it too
+ // small or we do a poor job suppressing acceleration spikes.
+ // Empirically, 100ms seems to be too small and 500ms is too large.
+ private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
/* State for orientation detection. */
@@ -286,9 +301,9 @@ public abstract class WindowOrientationListener {
//
// In both cases, we postpone choosing an orientation.
private static final float MIN_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY * 0.5f;
+ SensorManager.STANDARD_GRAVITY * 0.3f;
private static final float MAX_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY * 1.5f;
+ SensorManager.STANDARD_GRAVITY * 1.25f;
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
// when screen is facing the sky or ground), we completely ignore orientation data.
@@ -306,10 +321,10 @@ public abstract class WindowOrientationListener {
// The ideal tilt angle is 0 (when the device is vertical) so the limits establish
// how close to vertical the device must be in order to change orientation.
private static final int[][] TILT_TOLERANCE = new int[][] {
- /* ROTATION_0 */ { -20, 70 },
- /* ROTATION_90 */ { -20, 60 },
- /* ROTATION_180 */ { -20, 50 },
- /* ROTATION_270 */ { -20, 60 }
+ /* ROTATION_0 */ { -25, 70 },
+ /* ROTATION_90 */ { -25, 65 },
+ /* ROTATION_180 */ { -25, 60 },
+ /* ROTATION_270 */ { -25, 65 }
};
// The gap angle in degrees between adjacent orientation angles for hysteresis.
@@ -319,29 +334,38 @@ public abstract class WindowOrientationListener {
// orientation.
private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
- // The number of milliseconds for which the device posture must be stable
- // before we perform an orientation change. If the device appears to be rotating
- // (being picked up, put down) then we keep waiting until it settles.
- private static final int SETTLE_TIME_MS = 200;
+ // Timestamp and value of the last accelerometer sample.
+ private long mLastFilteredTimestampNanos;
+ private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The last proposed rotation, -1 if unknown.
+ private int mProposedRotation;
+
+ // Value of the current predicted rotation, -1 if unknown.
+ private int mPredictedRotation;
+
+ // Timestamp of when the predicted rotation most recently changed.
+ private long mPredictedRotationTimestampNanos;
- // The maximum change in magnitude that can occur during the settle time.
- // Tuning this constant particularly helps to filter out situations where the
- // device is being picked up or put down by the user.
- private static final float SETTLE_MAGNITUDE_MAX_DELTA =
- SensorManager.STANDARD_GRAVITY * 0.2f;
+ // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
+ private long mFlatTimestampNanos;
- // The maximum change in tilt angle that can occur during the settle time.
- private static final int SETTLE_TILT_ANGLE_MAX_DELTA = 5;
+ // Timestamp when the device last appeared to be swinging.
+ private long mSwingTimestampNanos;
- // The maximum change in orientation angle that can occur during the settle time.
- private static final int SETTLE_ORIENTATION_ANGLE_MAX_DELTA = 5;
+ // History of observed tilt angles.
+ private static final int TILT_HISTORY_SIZE = 40;
+ private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
+ private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
+ private int mTiltHistoryIndex;
public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
mOrientationListener = orientationListener;
+ reset();
}
public int getProposedRotation() {
- return mProposalAgeMS >= SETTLE_TIME_MS ? mProposalRotation : -1;
+ return mProposedRotation;
}
@Override
@@ -359,8 +383,9 @@ public abstract class WindowOrientationListener {
float z = event.values[ACCELEROMETER_DATA_Z];
if (log) {
- Slog.v(TAG, "Raw acceleration vector: " +
- "x=" + x + ", y=" + y + ", z=" + z);
+ Slog.v(TAG, "Raw acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
}
// Apply a low-pass filter to the acceleration up vector in cartesian space.
@@ -368,14 +393,16 @@ public abstract class WindowOrientationListener {
// or when we see values of (0, 0, 0) which indicates that we polled the
// accelerometer too soon after turning it on and we don't have any data yet.
final long now = event.timestamp;
- final float timeDeltaMS = (now - mLastTimestamp) * 0.000001f;
- boolean skipSample;
- if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS
+ final long then = mLastFilteredTimestampNanos;
+ final float timeDeltaMS = (now - then) * 0.000001f;
+ final boolean skipSample;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
|| (x == 0 && y == 0 && z == 0)) {
if (log) {
Slog.v(TAG, "Resetting orientation listener.");
}
- clearProposal();
+ reset();
skipSample = true;
} else {
final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
@@ -383,27 +410,28 @@ public abstract class WindowOrientationListener {
y = alpha * (y - mLastFilteredY) + mLastFilteredY;
z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
if (log) {
- Slog.v(TAG, "Filtered acceleration vector: " +
- "x=" + x + ", y=" + y + ", z=" + z);
+ Slog.v(TAG, "Filtered acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
}
skipSample = false;
}
- mLastTimestamp = now;
+ mLastFilteredTimestampNanos = now;
mLastFilteredX = x;
mLastFilteredY = y;
mLastFilteredZ = z;
- final int oldProposedRotation = getProposedRotation();
+ boolean isFlat = false;
+ boolean isSwinging = false;
if (!skipSample) {
// Calculate the magnitude of the acceleration vector.
- final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
+ final float magnitude = FloatMath.sqrt(x * x + y * y + z * z);
if (magnitude < MIN_ACCELERATION_MAGNITUDE
|| magnitude > MAX_ACCELERATION_MAGNITUDE) {
if (log) {
- Slog.v(TAG, "Ignoring sensor data, magnitude out of range: "
- + "magnitude=" + magnitude);
+ Slog.v(TAG, "Ignoring sensor data, magnitude out of range.");
}
- clearProposal();
+ clearPredictedRotation();
} else {
// Calculate the tilt angle.
// This is the angle between the up vector and the x-y plane (the plane of
@@ -414,14 +442,25 @@ public abstract class WindowOrientationListener {
final int tiltAngle = (int) Math.round(
Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ // Determine whether the device appears to be flat or swinging.
+ if (isFlat(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (isSwinging(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+ addTiltHistoryEntry(now, tiltAngle);
+
// If the tilt angle is too close to horizontal then we cannot determine
// the orientation angle of the screen.
if (Math.abs(tiltAngle) > MAX_TILT) {
if (log) {
Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
- + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle);
+ + "tiltAngle=" + tiltAngle);
}
- clearProposal();
+ clearPredictedRotation();
} else {
// Calculate the orientation angle.
// This is the angle between the x-y projection of the up vector onto
@@ -439,89 +478,93 @@ public abstract class WindowOrientationListener {
nearestRotation = 0;
}
- // Determine the proposed orientation.
- // The confidence of the proposal is 1.0 when it is ideal and it
- // decays exponentially as the proposal moves further from the ideal
- // angle, tilt and magnitude of the proposed orientation.
- if (!isTiltAngleAcceptable(nearestRotation, tiltAngle)
- || !isOrientationAngleAcceptable(nearestRotation,
+ // Determine the predicted orientation.
+ if (isTiltAngleAcceptable(nearestRotation, tiltAngle)
+ && isOrientationAngleAcceptable(nearestRotation,
orientationAngle)) {
+ updatePredictedRotation(now, nearestRotation);
if (log) {
- Slog.v(TAG, "Ignoring sensor data, no proposal: "
- + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle);
+ Slog.v(TAG, "Predicted: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle
+ + ", predictedRotation=" + mPredictedRotation
+ + ", predictedRotationAgeMS="
+ + ((now - mPredictedRotationTimestampNanos)
+ * 0.000001f));
}
- clearProposal();
} else {
if (log) {
- Slog.v(TAG, "Proposal: "
- + "magnitude=" + magnitude
- + ", tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle
- + ", proposalRotation=" + mProposalRotation);
+ Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle);
}
- updateProposal(nearestRotation, now / 1000000L,
- magnitude, tiltAngle, orientationAngle);
+ clearPredictedRotation();
}
}
}
}
+ // Determine new proposed rotation.
+ final int oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+
// Write final statistics about where we are in the orientation detection process.
- final int proposedRotation = getProposedRotation();
if (log) {
- final float proposalConfidence = Math.min(
- mProposalAgeMS * 1.0f / SETTLE_TIME_MS, 1.0f);
Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation
- + ", proposedRotation=" + proposedRotation
+ + ", proposedRotation=" + mProposedRotation
+ + ", predictedRotation=" + mPredictedRotation
+ ", timeDeltaMS=" + timeDeltaMS
- + ", proposalRotation=" + mProposalRotation
- + ", proposalAgeMS=" + mProposalAgeMS
- + ", proposalConfidence=" + proposalConfidence);
+ + ", isFlat=" + isFlat
+ + ", isSwinging=" + isSwinging
+ + ", timeUntilSettledMS=" + remainingMS(now,
+ mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
+ + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
+ mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
+ + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
+ mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
}
// Tell the listener.
- if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
+ if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
if (log) {
- Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation
+ Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation
+ ", oldProposedRotation=" + oldProposedRotation);
}
- mOrientationListener.onProposedRotationChanged(proposedRotation);
+ mOrientationListener.onProposedRotationChanged(mProposedRotation);
}
}
/**
- * Returns true if the tilt angle is acceptable for a proposed
- * orientation transition.
+ * Returns true if the tilt angle is acceptable for a given predicted rotation.
*/
- private boolean isTiltAngleAcceptable(int proposedRotation,
- int tiltAngle) {
- return tiltAngle >= TILT_TOLERANCE[proposedRotation][0]
- && tiltAngle <= TILT_TOLERANCE[proposedRotation][1];
+ private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) {
+ return tiltAngle >= TILT_TOLERANCE[rotation][0]
+ && tiltAngle <= TILT_TOLERANCE[rotation][1];
}
/**
- * Returns true if the orientation angle is acceptable for a proposed
- * orientation transition.
+ * Returns true if the orientation angle is acceptable for a given predicted rotation.
*
* This function takes into account the gap between adjacent orientations
* for hysteresis.
*/
- private boolean isOrientationAngleAcceptable(int proposedRotation, int orientationAngle) {
+ private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) {
// If there is no current rotation, then there is no gap.
// The gap is used only to introduce hysteresis among advertised orientation
// changes to avoid flapping.
final int currentRotation = mOrientationListener.mCurrentRotation;
if (currentRotation >= 0) {
- // If the proposed rotation is the same or is counter-clockwise adjacent,
- // then we set a lower bound on the orientation angle.
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation angle.
// For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
// then we want to check orientationAngle > 45 + GAP / 2.
- if (proposedRotation == currentRotation
- || proposedRotation == (currentRotation + 1) % 4) {
- int lowerBound = proposedRotation * 90 - 45
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45
+ ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (proposedRotation == 0) {
+ if (rotation == 0) {
if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
return false;
}
@@ -532,15 +575,15 @@ public abstract class WindowOrientationListener {
}
}
- // If the proposed rotation is the same or is clockwise adjacent,
+ // If the specified rotation is the same or is clockwise adjacent,
// then we set an upper bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_270,
+ // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
// then we want to check orientationAngle < 315 - GAP / 2.
- if (proposedRotation == currentRotation
- || proposedRotation == (currentRotation + 3) % 4) {
- int upperBound = proposedRotation * 90 + 45
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45
- ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (proposedRotation == 0) {
+ if (rotation == 0) {
if (orientationAngle <= 45 && orientationAngle > upperBound) {
return false;
}
@@ -554,58 +597,97 @@ public abstract class WindowOrientationListener {
return true;
}
- private void clearProposal() {
- mProposalRotation = -1;
- mProposalAgeMS = 0;
- }
+ /**
+ * Returns true if the predicted rotation is ready to be advertised as a
+ * proposed rotation.
+ */
+ private boolean isPredictedRotationAcceptable(long now) {
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+
+ // The last flat state (time since picked up) must have been sufficiently long ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
- private void updateProposal(int rotation, long timestampMS,
- float magnitude, int tiltAngle, int orientationAngle) {
- if (mProposalRotation != rotation) {
- mProposalRotation = rotation;
- mHistoryIndex = 0;
- mHistoryLength = 0;
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
}
- final int index = mHistoryIndex;
- mHistoryTimestampMS[index] = timestampMS;
- mHistoryMagnitudes[index] = magnitude;
- mHistoryTiltAngles[index] = tiltAngle;
- mHistoryOrientationAngles[index] = orientationAngle;
- mHistoryIndex = (index + 1) % HISTORY_SIZE;
- if (mHistoryLength < HISTORY_SIZE) {
- mHistoryLength += 1;
+ // Looks good!
+ return true;
+ }
+
+ private void reset() {
+ mLastFilteredTimestampNanos = Long.MIN_VALUE;
+ mProposedRotation = -1;
+ mFlatTimestampNanos = Long.MIN_VALUE;
+ mSwingTimestampNanos = Long.MIN_VALUE;
+ clearPredictedRotation();
+ clearTiltHistory();
+ }
+
+ private void clearPredictedRotation() {
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = Long.MIN_VALUE;
+ }
+
+ private void updatePredictedRotation(long now, int rotation) {
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
}
+ }
- long age = 0;
- for (int i = 1; i < mHistoryLength; i++) {
- final int olderIndex = (index + HISTORY_SIZE - i) % HISTORY_SIZE;
- if (Math.abs(mHistoryMagnitudes[olderIndex] - magnitude)
- > SETTLE_MAGNITUDE_MAX_DELTA) {
+ private void clearTiltHistory() {
+ mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
+ mTiltHistoryIndex = 1;
+ }
+
+ private void addTiltHistoryEntry(long now, float tilt) {
+ mTiltHistory[mTiltHistoryIndex] = tilt;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
+ mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
+ }
+
+ private boolean isFlat(long now) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
+ if (mTiltHistory[i] < FLAT_ANGLE) {
break;
}
- if (angleAbsoluteDelta(mHistoryTiltAngles[olderIndex],
- tiltAngle) > SETTLE_TILT_ANGLE_MAX_DELTA) {
- break;
+ if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
}
- if (angleAbsoluteDelta(mHistoryOrientationAngles[olderIndex],
- orientationAngle) > SETTLE_ORIENTATION_ANGLE_MAX_DELTA) {
+ }
+ return false;
+ }
+
+ private boolean isSwinging(long now, float tilt) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
+ if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
break;
}
- age = timestampMS - mHistoryTimestampMS[olderIndex];
- if (age >= SETTLE_TIME_MS) {
- break;
+ if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
}
}
- mProposalAgeMS = age;
+ return false;
}
- private static int angleAbsoluteDelta(int a, int b) {
- int delta = Math.abs(a - b);
- if (delta > 180) {
- delta = 360 - delta;
- }
- return delta;
+ private int nextTiltHistoryIndex(int index) {
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
+ }
+
+ private static float remainingMS(long now, long until) {
+ return now >= until ? 0 : (until - now) * 0.000001f;
}
}
}
diff --git a/tools/orientationplot/orientationplot.py b/tools/orientationplot/orientationplot.py
index 3a44cb2cbfd8..f4e6b453c9df 100755
--- a/tools/orientationplot/orientationplot.py
+++ b/tools/orientationplot/orientationplot.py
@@ -82,6 +82,7 @@ class Plotter:
self.raw_acceleration_x = self._make_timeseries()
self.raw_acceleration_y = self._make_timeseries()
self.raw_acceleration_z = self._make_timeseries()
+ self.raw_acceleration_magnitude = self._make_timeseries()
self.raw_acceleration_axes = self._add_timeseries_axes(
1, 'Raw Acceleration', 'm/s^2', [-20, 20],
yticks=range(-15, 16, 5))
@@ -91,6 +92,8 @@ class Plotter:
self.raw_acceleration_axes, 'y', 'green')
self.raw_acceleration_line_z = self._add_timeseries_line(
self.raw_acceleration_axes, 'z', 'blue')
+ self.raw_acceleration_line_magnitude = self._add_timeseries_line(
+ self.raw_acceleration_axes, 'magnitude', 'orange', linewidth=2)
self._add_timeseries_legend(self.raw_acceleration_axes)
shared_axis = self.raw_acceleration_axes
@@ -98,7 +101,7 @@ class Plotter:
self.filtered_acceleration_x = self._make_timeseries()
self.filtered_acceleration_y = self._make_timeseries()
self.filtered_acceleration_z = self._make_timeseries()
- self.magnitude = self._make_timeseries()
+ self.filtered_acceleration_magnitude = self._make_timeseries()
self.filtered_acceleration_axes = self._add_timeseries_axes(
2, 'Filtered Acceleration', 'm/s^2', [-20, 20],
sharex=shared_axis,
@@ -109,7 +112,7 @@ class Plotter:
self.filtered_acceleration_axes, 'y', 'green')
self.filtered_acceleration_line_z = self._add_timeseries_line(
self.filtered_acceleration_axes, 'z', 'blue')
- self.magnitude_line = self._add_timeseries_line(
+ self.filtered_acceleration_line_magnitude = self._add_timeseries_line(
self.filtered_acceleration_axes, 'magnitude', 'orange', linewidth=2)
self._add_timeseries_legend(self.filtered_acceleration_axes)
@@ -133,32 +136,46 @@ class Plotter:
self.current_rotation = self._make_timeseries()
self.proposed_rotation = self._make_timeseries()
- self.proposal_rotation = self._make_timeseries()
+ self.predicted_rotation = self._make_timeseries()
self.orientation_axes = self._add_timeseries_axes(
- 5, 'Current / Proposed Orientation and Confidence', 'rotation', [-1, 4],
+ 5, 'Current / Proposed Orientation', 'rotation', [-1, 4],
sharex=shared_axis,
yticks=range(0, 4))
self.current_rotation_line = self._add_timeseries_line(
self.orientation_axes, 'current', 'black', linewidth=2)
- self.proposal_rotation_line = self._add_timeseries_line(
- self.orientation_axes, 'proposal', 'purple', linewidth=3)
+ self.predicted_rotation_line = self._add_timeseries_line(
+ self.orientation_axes, 'predicted', 'purple', linewidth=3)
self.proposed_rotation_line = self._add_timeseries_line(
self.orientation_axes, 'proposed', 'green', linewidth=3)
self._add_timeseries_legend(self.orientation_axes)
- self.proposal_confidence = [[self._make_timeseries(), self._make_timeseries()]
- for i in range(0, 4)]
- self.proposal_confidence_polys = []
+ self.time_until_settled = self._make_timeseries()
+ self.time_until_flat_delay_expired = self._make_timeseries()
+ self.time_until_swing_delay_expired = self._make_timeseries()
+ self.stability_axes = self._add_timeseries_axes(
+ 6, 'Proposal Stability', 'ms', [-10, 600],
+ sharex=shared_axis,
+ yticks=range(0, 600, 100))
+ self.time_until_settled_line = self._add_timeseries_line(
+ self.stability_axes, 'time until settled', 'black', linewidth=2)
+ self.time_until_flat_delay_expired_line = self._add_timeseries_line(
+ self.stability_axes, 'time until flat delay expired', 'green')
+ self.time_until_swing_delay_expired_line = self._add_timeseries_line(
+ self.stability_axes, 'time until swing delay expired', 'blue')
+ self._add_timeseries_legend(self.stability_axes)
self.sample_latency = self._make_timeseries()
self.sample_latency_axes = self._add_timeseries_axes(
- 6, 'Accelerometer Sampling Latency', 'ms', [-10, 500],
+ 7, 'Accelerometer Sampling Latency', 'ms', [-10, 500],
sharex=shared_axis,
yticks=range(0, 500, 100))
self.sample_latency_line = self._add_timeseries_line(
self.sample_latency_axes, 'latency', 'black')
self._add_timeseries_legend(self.sample_latency_axes)
+ self.fig.canvas.mpl_connect('button_press_event', self._on_click)
+ self.paused = False
+
self.timer = self.fig.canvas.new_timer(interval=100)
self.timer.add_callback(lambda: self.update())
self.timer.start()
@@ -166,13 +183,22 @@ class Plotter:
self.timebase = None
self._reset_parse_state()
+ # Handle a click event to pause or restart the timer.
+ def _on_click(self, ev):
+ if not self.paused:
+ self.paused = True
+ self.timer.stop()
+ else:
+ self.paused = False
+ self.timer.start()
+
# Initialize a time series.
def _make_timeseries(self):
return [[], []]
# Add a subplot to the figure for a time series.
def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
- num_graphs = 6
+ num_graphs = 7
height = 0.9 / num_graphs
top = 0.95 - height * index
axes = self.fig.add_axes([0.1, top, 0.8, height],
@@ -214,16 +240,19 @@ class Plotter:
self.parse_raw_acceleration_x = None
self.parse_raw_acceleration_y = None
self.parse_raw_acceleration_z = None
+ self.parse_raw_acceleration_magnitude = None
self.parse_filtered_acceleration_x = None
self.parse_filtered_acceleration_y = None
self.parse_filtered_acceleration_z = None
- self.parse_magnitude = None
+ self.parse_filtered_acceleration_magnitude = None
self.parse_tilt_angle = None
self.parse_orientation_angle = None
self.parse_current_rotation = None
self.parse_proposed_rotation = None
- self.parse_proposal_rotation = None
- self.parse_proposal_confidence = None
+ self.parse_predicted_rotation = None
+ self.parse_time_until_settled = None
+ self.parse_time_until_flat_delay_expired = None
+ self.parse_time_until_swing_delay_expired = None
self.parse_sample_latency = None
# Update samples.
@@ -252,14 +281,13 @@ class Plotter:
self.parse_raw_acceleration_x = self._get_following_number(line, 'x=')
self.parse_raw_acceleration_y = self._get_following_number(line, 'y=')
self.parse_raw_acceleration_z = self._get_following_number(line, 'z=')
+ self.parse_raw_acceleration_magnitude = self._get_following_number(line, 'magnitude=')
if line.find('Filtered acceleration vector:') != -1:
self.parse_filtered_acceleration_x = self._get_following_number(line, 'x=')
self.parse_filtered_acceleration_y = self._get_following_number(line, 'y=')
self.parse_filtered_acceleration_z = self._get_following_number(line, 'z=')
-
- if line.find('magnitude=') != -1:
- self.parse_magnitude = self._get_following_number(line, 'magnitude=')
+ self.parse_filtered_acceleration_magnitude = self._get_following_number(line, 'magnitude=')
if line.find('tiltAngle=') != -1:
self.parse_tilt_angle = self._get_following_number(line, 'tiltAngle=')
@@ -270,17 +298,20 @@ class Plotter:
if line.find('Result:') != -1:
self.parse_current_rotation = self._get_following_number(line, 'currentRotation=')
self.parse_proposed_rotation = self._get_following_number(line, 'proposedRotation=')
- self.parse_proposal_rotation = self._get_following_number(line, 'proposalRotation=')
- self.parse_proposal_confidence = self._get_following_number(line, 'proposalConfidence=')
+ self.parse_predicted_rotation = self._get_following_number(line, 'predictedRotation=')
self.parse_sample_latency = self._get_following_number(line, 'timeDeltaMS=')
+ self.parse_time_until_settled = self._get_following_number(line, 'timeUntilSettledMS=')
+ self.parse_time_until_flat_delay_expired = self._get_following_number(line, 'timeUntilFlatDelayExpiredMS=')
+ self.parse_time_until_swing_delay_expired = self._get_following_number(line, 'timeUntilSwingDelayExpiredMS=')
self._append(self.raw_acceleration_x, timeindex, self.parse_raw_acceleration_x)
self._append(self.raw_acceleration_y, timeindex, self.parse_raw_acceleration_y)
self._append(self.raw_acceleration_z, timeindex, self.parse_raw_acceleration_z)
+ self._append(self.raw_acceleration_magnitude, timeindex, self.parse_raw_acceleration_magnitude)
self._append(self.filtered_acceleration_x, timeindex, self.parse_filtered_acceleration_x)
self._append(self.filtered_acceleration_y, timeindex, self.parse_filtered_acceleration_y)
self._append(self.filtered_acceleration_z, timeindex, self.parse_filtered_acceleration_z)
- self._append(self.magnitude, timeindex, self.parse_magnitude)
+ self._append(self.filtered_acceleration_magnitude, timeindex, self.parse_filtered_acceleration_magnitude)
self._append(self.tilt_angle, timeindex, self.parse_tilt_angle)
self._append(self.orientation_angle, timeindex, self.parse_orientation_angle)
self._append(self.current_rotation, timeindex, self.parse_current_rotation)
@@ -288,17 +319,13 @@ class Plotter:
self._append(self.proposed_rotation, timeindex, self.parse_proposed_rotation)
else:
self._append(self.proposed_rotation, timeindex, None)
- if self.parse_proposal_rotation >= 0:
- self._append(self.proposal_rotation, timeindex, self.parse_proposal_rotation)
+ if self.parse_predicted_rotation >= 0:
+ self._append(self.predicted_rotation, timeindex, self.parse_predicted_rotation)
else:
- self._append(self.proposal_rotation, timeindex, None)
- for i in range(0, 4):
- self._append(self.proposal_confidence[i][0], timeindex, i)
- if i == self.parse_proposal_rotation:
- self._append(self.proposal_confidence[i][1], timeindex,
- i + self.parse_proposal_confidence)
- else:
- self._append(self.proposal_confidence[i][1], timeindex, i)
+ self._append(self.predicted_rotation, timeindex, None)
+ self._append(self.time_until_settled, timeindex, self.parse_time_until_settled)
+ self._append(self.time_until_flat_delay_expired, timeindex, self.parse_time_until_flat_delay_expired)
+ self._append(self.time_until_swing_delay_expired, timeindex, self.parse_time_until_swing_delay_expired)
self._append(self.sample_latency, timeindex, self.parse_sample_latency)
self._reset_parse_state()
@@ -309,45 +336,40 @@ class Plotter:
self._scroll(self.raw_acceleration_x, bottom)
self._scroll(self.raw_acceleration_y, bottom)
self._scroll(self.raw_acceleration_z, bottom)
+ self._scroll(self.raw_acceleration_magnitude, bottom)
self._scroll(self.filtered_acceleration_x, bottom)
self._scroll(self.filtered_acceleration_y, bottom)
self._scroll(self.filtered_acceleration_z, bottom)
- self._scroll(self.magnitude, bottom)
+ self._scroll(self.filtered_acceleration_magnitude, bottom)
self._scroll(self.tilt_angle, bottom)
self._scroll(self.orientation_angle, bottom)
self._scroll(self.current_rotation, bottom)
self._scroll(self.proposed_rotation, bottom)
- self._scroll(self.proposal_rotation, bottom)
- for i in range(0, 4):
- self._scroll(self.proposal_confidence[i][0], bottom)
- self._scroll(self.proposal_confidence[i][1], bottom)
+ self._scroll(self.predicted_rotation, bottom)
+ self._scroll(self.time_until_settled, bottom)
+ self._scroll(self.time_until_flat_delay_expired, bottom)
+ self._scroll(self.time_until_swing_delay_expired, bottom)
self._scroll(self.sample_latency, bottom)
# Redraw the plots.
self.raw_acceleration_line_x.set_data(self.raw_acceleration_x)
self.raw_acceleration_line_y.set_data(self.raw_acceleration_y)
self.raw_acceleration_line_z.set_data(self.raw_acceleration_z)
+ self.raw_acceleration_line_magnitude.set_data(self.raw_acceleration_magnitude)
self.filtered_acceleration_line_x.set_data(self.filtered_acceleration_x)
self.filtered_acceleration_line_y.set_data(self.filtered_acceleration_y)
self.filtered_acceleration_line_z.set_data(self.filtered_acceleration_z)
- self.magnitude_line.set_data(self.magnitude)
+ self.filtered_acceleration_line_magnitude.set_data(self.filtered_acceleration_magnitude)
self.tilt_angle_line.set_data(self.tilt_angle)
self.orientation_angle_line.set_data(self.orientation_angle)
self.current_rotation_line.set_data(self.current_rotation)
self.proposed_rotation_line.set_data(self.proposed_rotation)
- self.proposal_rotation_line.set_data(self.proposal_rotation)
+ self.predicted_rotation_line.set_data(self.predicted_rotation)
+ self.time_until_settled_line.set_data(self.time_until_settled)
+ self.time_until_flat_delay_expired_line.set_data(self.time_until_flat_delay_expired)
+ self.time_until_swing_delay_expired_line.set_data(self.time_until_swing_delay_expired)
self.sample_latency_line.set_data(self.sample_latency)
- for poly in self.proposal_confidence_polys:
- poly.remove()
- self.proposal_confidence_polys = []
- for i in range(0, 4):
- self.proposal_confidence_polys.append(self.orientation_axes.fill_between(
- self.proposal_confidence[i][0][0],
- self.proposal_confidence[i][0][1],
- self.proposal_confidence[i][1][1],
- facecolor='goldenrod', edgecolor='goldenrod'))
-
self.fig.canvas.draw_idle()
# Scroll a time series.