diff --git a/build.gradle b/build.gradle index f194e8a..382c60b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' } } diff --git a/fsensor/build.gradle b/fsensor/build.gradle index 1084b45..23c3a2f 100644 --- a/fsensor/build.gradle +++ b/fsensor/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'com.github.dcendents.android-maven' group='com.github.KalebKE' android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 28 + buildToolsVersion '28.0.3' lintOptions { abortOnError false @@ -13,9 +13,9 @@ android { defaultConfig { minSdkVersion 14 - targetSdkVersion 26 - versionCode 5 - versionName "1.1.5" + targetSdkVersion 28 + versionCode 6 + versionName "1.2.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -29,11 +29,16 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:support-annotations:28.0.0' + + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - testCompile 'junit:junit:4.12' + testImplementation 'junit:junit:4.12' + + api files('libs/commons-math3-3.6.1.jar') - compile files('libs/commons-math3-3.6.1.jar') + api 'io.reactivex.rxjava2:rxandroid:2.1.0' + api 'io.reactivex.rxjava2:rxjava:2.x.x' } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/BaseFilter.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/BaseFilter.java index 5d013ec..500ca04 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/BaseFilter.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/BaseFilter.java @@ -1,5 +1,21 @@ package com.kircherelectronics.fsensor; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Created by kaleb on 4/1/18. */ diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/AveragingFilter.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/AveragingFilter.java index d296693..0ec1b5b 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/AveragingFilter.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/AveragingFilter.java @@ -1,13 +1,13 @@ package com.kircherelectronics.fsensor.filter.averaging; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -46,4 +46,6 @@ public void reset() { timestamp = 0; count = 0; } + + public abstract float[] filter(float[] data); } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/LowPassFilter.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/LowPassFilter.java index c6cc32a..2cc6ed1 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/LowPassFilter.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/LowPassFilter.java @@ -1,7 +1,7 @@ package com.kircherelectronics.fsensor.filter.averaging; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MeanFilter.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MeanFilter.java index 6d80dce..fd569ff 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MeanFilter.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MeanFilter.java @@ -4,7 +4,7 @@ import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,12 @@ public float[] filter(float[] data) { values.removeFirst(); } - output = getMean(values); + if(!values.isEmpty()) { + output = getMean(values); + } else { + output = new float[data.length]; + System.arraycopy(data, 0, output, 0, data.length); + } return output; } @@ -98,7 +103,7 @@ public float[] getOutput() { * @return the mean of the data set. */ private float[] getMean(ArrayDeque data) { - float[] mean = new float[3]; + float[] mean = new float[data.getFirst().length]; for (float[] axis : data) { for (int i = 0; i < axis.length; i++) { diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MedianFilter.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MedianFilter.java index c37e9af..c7458b3 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MedianFilter.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/averaging/MedianFilter.java @@ -6,7 +6,7 @@ import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,7 +89,12 @@ public float[] filter(float[] data) { values.removeFirst(); } - output = getMean(values); + if(!values.isEmpty()) { + output = getMean(values); + } else { + output = new float[data.length]; + System.arraycopy(data, 0, output, 0, data.length); + } return output; } @@ -106,9 +111,9 @@ public float[] getOutput() { * @return the mean of the data set. */ private float[] getMean(ArrayDeque data) { - float[] mean = new float[3]; + float[] mean = new float[data.getFirst().length]; - double[][] values = new double[3][data.size()]; + double[][] values = new double[data.getFirst().length][data.size()]; int index = 0; for (float[] axis : data) { diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/OrientationGyroscope.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/OrientationGyroscope.java index 16e46e8..1721b5b 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/OrientationGyroscope.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/OrientationGyroscope.java @@ -1,14 +1,19 @@ package com.kircherelectronics.fsensor.filter.gyroscope; -import android.hardware.SensorManager; +import android.util.Log; import com.kircherelectronics.fsensor.BaseFilter; import com.kircherelectronics.fsensor.util.rotation.RotationUtil; import org.apache.commons.math3.complex.Quaternion; +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention; +import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder; + +import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +84,7 @@ */ public class OrientationGyroscope extends BaseFilter { - private static final String tag = OrientationGyroscope.class.getSimpleName(); + private static final String TAG = OrientationGyroscope.class.getSimpleName(); private static final float NS2S = 1.0f / 1000000000.0f; private static final float EPSILON = 0.000000001f; private Quaternion rotationVectorGyroscope; @@ -105,34 +110,23 @@ public float[] getOutput() { * @return An orientation vector -> @link SensorManager#getOrientation(float[], float[])} */ public float[] calculateOrientation(float[] gyroscope, long timestamp) { - - if (rotationVectorGyroscope != null) { + if (isBaseOrientationSet()) { if (this.timestamp != 0) { final float dT = (timestamp - this.timestamp) * NS2S; rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); - } - - this.timestamp = timestamp; - - // Now we get a structure we can pass to get a rotation matrix, and then - // an orientation vector from Android. - - float[] fusedVector = new float[4]; - - fusedVector[0] = (float) rotationVectorGyroscope.getVectorPart()[0]; - fusedVector[1] = (float) rotationVectorGyroscope.getVectorPart()[1]; - fusedVector[2] = (float) rotationVectorGyroscope.getVectorPart()[2]; - fusedVector[3] = (float) rotationVectorGyroscope.getScalarPart(); - // rotation matrix from gyro data - float[] fusedMatrix = new float[9]; + Rotation rotation = new Rotation(rotationVectorGyroscope.getQ0(), rotationVectorGyroscope.getQ1(), rotationVectorGyroscope.getQ2(), + rotationVectorGyroscope.getQ3(), true); - // We need a rotation matrix so we can get the orientation vector - SensorManager.getRotationMatrixFromVector(fusedMatrix, fusedVector); + try { + output = doubleToFloat(rotation.getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)); + } catch(Exception e) { + Log.d(TAG, "", e); + } + } - // Get the OrientationFused - SensorManager.getOrientation(fusedMatrix, output); + this.timestamp = timestamp; return output; } else { @@ -160,6 +154,16 @@ public void reset() { } public boolean isBaseOrientationSet() { - return !(rotationVectorGyroscope == null); + return rotationVectorGyroscope != null; + } + + private static float[] doubleToFloat(double[] values) { + float[] f = new float[values.length]; + + for(int i = 0; i < f.length; i++){ + f[i] = (float) values[i]; + } + + return f; } } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/OrientationFused.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/OrientationFused.java index d2cf346..ba4257d 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/OrientationFused.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/OrientationFused.java @@ -5,7 +5,7 @@ import org.apache.commons.math3.complex.Quaternion; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ public void reset() { } public boolean isBaseOrientationSet() { - return !(rotationVectorGyroscope == null); + return rotationVectorGyroscope != null; } /** @@ -86,15 +86,6 @@ public void setTimeConstant(float timeConstant) { */ public abstract float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] acceleration, float[] magnetic); - /** - * Calculate the fused orientation of the device. - * @param gyroscope the gyroscope measurements. - * @param timestamp the gyroscope timestamp - * @param orientation an estimation of device orientation. - * @return the fused orientation estimation. - */ - public abstract float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] orientation); - public void setBaseOrientation(Quaternion baseOrientation) { rotationVectorGyroscope = baseOrientation; } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/complimentary/OrientationFusedComplimentary.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/complimentary/OrientationFusedComplimentary.java index 9103b85..605e871 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/complimentary/OrientationFusedComplimentary.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/complimentary/OrientationFusedComplimentary.java @@ -1,17 +1,19 @@ package com.kircherelectronics.fsensor.filter.gyroscope.fusion.complimentary; -import android.hardware.SensorManager; import android.util.Log; import com.kircherelectronics.fsensor.filter.gyroscope.fusion.OrientationFused; import com.kircherelectronics.fsensor.util.rotation.RotationUtil; import org.apache.commons.math3.complex.Quaternion; +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention; +import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder; import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,7 +84,7 @@ */ public class OrientationFusedComplimentary extends OrientationFused { - private static final String tag = OrientationFusedComplimentary.class.getSimpleName(); + private static final String TAG = OrientationFusedComplimentary.class.getSimpleName(); /** * Initialize a singleton instance. @@ -104,53 +106,43 @@ public OrientationFusedComplimentary(float timeConstant) { * @return the fused orientation estimation. */ public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] acceleration, float[] magnetic) { - if (rotationVectorGyroscope != null) { - + if (isBaseOrientationSet()) { if (this.timestamp != 0) { final float dT = (timestamp - this.timestamp) * NS2S; float alpha = timeConstant / (timeConstant + dT); float oneMinusAlpha = (1.0f - alpha); - Quaternion rotationVectorAccelerationMagnetic = RotationUtil.getOrientationQuaternionFromAccelerationMagnetic(acceleration, magnetic); - - rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); + Quaternion rotationVectorAccelerationMagnetic = RotationUtil.getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic); - // Apply the complementary fusedOrientation. // We multiply each rotation by their - // coefficients (scalar matrices)... - Quaternion scaledRotationVectorAccelerationMagnetic = rotationVectorAccelerationMagnetic.multiply - (oneMinusAlpha); + if(rotationVectorAccelerationMagnetic != null) { + rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); - // Scale our quaternion for the gyroscope - Quaternion scaledRotationVectorGyroscope = rotationVectorGyroscope.multiply(alpha); + // Apply the complementary fusedOrientation. // We multiply each rotation by their + // coefficients (scalar matrices)... + Quaternion scaledRotationVectorAccelerationMagnetic = rotationVectorAccelerationMagnetic.multiply + (oneMinusAlpha); - // ...and then add the two quaternions together. - // output[0] = alpha * output[0] + (1 - alpha) * input[0]; - rotationVectorGyroscope = scaledRotationVectorGyroscope.add - (scaledRotationVectorAccelerationMagnetic); - } - - this.timestamp = timestamp; + // Scale our quaternion for the gyroscope + Quaternion scaledRotationVectorGyroscope = rotationVectorGyroscope.multiply(alpha); - float[] fusedVector = new float[4]; + // ...and then add the two quaternions together. + // output[0] = alpha * output[0] + (1 - alpha) * input[0]; + rotationVectorGyroscope = scaledRotationVectorGyroscope.add + (scaledRotationVectorAccelerationMagnetic); + } - fusedVector[0] = (float) rotationVectorGyroscope.getVectorPart()[0]; - fusedVector[1] = (float) rotationVectorGyroscope.getVectorPart()[1]; - fusedVector[2] = (float) rotationVectorGyroscope.getVectorPart()[2]; - fusedVector[3] = (float) rotationVectorGyroscope.getScalarPart(); + Rotation rotation = new Rotation(rotationVectorGyroscope.getQ0(), rotationVectorGyroscope.getQ1(), rotationVectorGyroscope.getQ2(), + rotationVectorGyroscope.getQ3(), true); - // rotation matrix from gyro data - float[] fusedMatrix = new float[9]; - - // We need a rotation matrix so we can get the orientation vector... - // Getting Euler - // angles from a quaternion is not trivial, so this is the easiest way, - // but perhaps - // not the fastest way of doing this. - SensorManager.getRotationMatrixFromVector(fusedMatrix, fusedVector); + try { + output = doubleToFloat(rotation.getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)); + } catch(Exception e) { + Log.d(TAG, "", e); + } + } - // Get the fused orienatation - SensorManager.getOrientation(fusedMatrix, output); + this.timestamp = timestamp; return output; } else { @@ -158,62 +150,13 @@ public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, floa } } - /** - * Calculate the fused orientation of the device. - * @param gyroscope the gyroscope measurements. - * @param timestamp the gyroscope timestamp - * @param orientationVector an estimation of device orientation. - * @return the fused orientation estimation. - */ - public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] orientationVector) { - if (rotationVectorGyroscope != null) { - if (this.timestamp != 0) { - final float dT = (timestamp - this.timestamp) * NS2S; - - float alpha = timeConstant / (timeConstant + dT); - float oneMinusAlpha = (1.0f - alpha); - - Quaternion rotationVectorAccelerationMagnetic = RotationUtil.vectorToQuaternion(orientationVector); - rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); - - // Apply the complementary fusedOrientation. // We multiply each rotation by their - // coefficients (scalar matrices)... - Quaternion scaledRotationVectorAccelerationMagnetic = rotationVectorAccelerationMagnetic.multiply - (oneMinusAlpha); - - // Scale our quaternion for the gyroscope - Quaternion scaledRotationVectorGyroscope = rotationVectorGyroscope.multiply(alpha); - - // ...and then add the two quaternions together. - // output[0] = alpha * output[0] + (1 - alpha) * input[0]; - rotationVectorGyroscope = scaledRotationVectorGyroscope.add - (scaledRotationVectorAccelerationMagnetic); - } - - this.timestamp = timestamp; - float[] fusedVector = new float[4]; + private static float[] doubleToFloat(double[] values) { + float[] f = new float[values.length]; - fusedVector[0] = (float) rotationVectorGyroscope.getVectorPart()[0]; - fusedVector[1] = (float) rotationVectorGyroscope.getVectorPart()[1]; - fusedVector[2] = (float) rotationVectorGyroscope.getVectorPart()[2]; - fusedVector[3] = (float) rotationVectorGyroscope.getScalarPart(); - - // rotation matrix from gyro data - float[] fusedMatrix = new float[9]; - - // We need a rotation matrix so we can get the orientation vector... - // Getting Euler - // angles from a quaternion is not trivial, so this is the easiest way, - // but perhaps - // not the fastest way of doing this. - SensorManager.getRotationMatrixFromVector(fusedMatrix, fusedVector); - - // Get the fused orienatation - SensorManager.getOrientation(fusedMatrix, output); - - return output; - } else { - throw new IllegalStateException("You must call setBaseOrientation() before calling calculateFusedOrientation()!"); + for(int i = 0; i < f.length; i++){ + f[i] = (float) values[i]; } + + return f; } } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/OrientationFusedKalman.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/OrientationFusedKalman.java index be03efa..02b18d8 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/OrientationFusedKalman.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/OrientationFusedKalman.java @@ -1,6 +1,5 @@ package com.kircherelectronics.fsensor.filter.gyroscope.fusion.kalman; -import android.hardware.SensorManager; import android.util.Log; import com.kircherelectronics.fsensor.filter.gyroscope.fusion.OrientationFused; @@ -11,17 +10,20 @@ import com.kircherelectronics.fsensor.util.rotation.RotationUtil; import org.apache.commons.math3.complex.Quaternion; +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention; +import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder; import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -58,7 +60,7 @@ public class OrientationFusedKalman extends OrientationFused { - private static final String tag = OrientationFusedComplimentary.class.getSimpleName(); + private static final String TAG = OrientationFusedComplimentary.class.getSimpleName(); private RotationKalmanFilter kalmanFilter; private RotationProcessModel pm; @@ -84,7 +86,7 @@ public OrientationFusedKalman(float timeConstant) { } public void startFusion() { - if (run == false && thread == null) { + if (!run && thread == null) { run = true; thread = new Thread(new Runnable() { @@ -97,7 +99,7 @@ public void run() { try { Thread.sleep(20); } catch (InterruptedException e) { - Log.e(tag, "Kalman Thread Run", e); + Log.e(TAG, "Kalman Thread Run", e); Thread.currentThread().interrupt(); } } @@ -111,7 +113,7 @@ public void run() { } public void stopFusion() { - if (run == true && thread != null) { + if (run && thread != null) { run = false; thread.interrupt(); thread = null; @@ -149,30 +151,14 @@ private float[] calculate() { rotationVectorGyroscope = new Quaternion(kalmanFilter.getStateEstimation()[3], Arrays.copyOfRange(kalmanFilter.getStateEstimation(), 0, 3)); - // Now we get a structure we can pass to get a rotation matrix, and then - // an orientation vector from Android. + Rotation rotation = new Rotation(rotationVectorGyroscope.getQ0(), rotationVectorGyroscope.getQ1(), rotationVectorGyroscope.getQ2(), + rotationVectorGyroscope.getQ3(), true); - float[] fusedVector = new float[4]; - - // Now we get a structure we can pass to get a rotation matrix, and then - // an orientation vector from Android. - fusedVector[0] = (float) rotationVectorGyroscope.getVectorPart()[0]; - fusedVector[1] = (float) rotationVectorGyroscope.getVectorPart()[1]; - fusedVector[2] = (float) rotationVectorGyroscope.getVectorPart()[2]; - fusedVector[3] = (float) rotationVectorGyroscope.getScalarPart(); - - // rotation matrix from gyro data - float[] fusedMatrix = new float[9]; - - // We need a rotation matrix so we can get the orientation vector... - // Getting Euler - // angles from a quaternion is not trivial, so this is the easiest way, - // but perhaps - // not the fastest way of doing this. - SensorManager.getRotationMatrixFromVector(fusedMatrix, fusedVector); - - // Get the fused orientation - SensorManager.getOrientation(fusedMatrix, output); + try { + output = doubleToFloat(rotation.getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)); + } catch(Exception e) { + Log.d(TAG, "", e); + } return output; } @@ -191,11 +177,11 @@ private float[] calculate() { */ public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] acceleration, float[] magnetic) { - if(rotationVectorGyroscope != null) { + if(isBaseOrientationSet()) { if (this.timestamp != 0) { dT = (timestamp - this.timestamp) * NS2S; - rotationOrientation = RotationUtil.getOrientationQuaternionFromAccelerationMagnetic(acceleration, magnetic); + rotationOrientation = RotationUtil.getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic); rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); } this.timestamp = timestamp; @@ -206,27 +192,13 @@ public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, floa } } - /** - * Calculate the fused orientation of the device. - * - * @param gyroscope the gyroscope measurements. - * @param timestamp the gyroscope timestamp - * @param orientation an estimation of device orientation. - * @return the fused orientation estimation. - */ - public float[] calculateFusedOrientation(float[] gyroscope, long timestamp, float[] orientation) { - if(rotationVectorGyroscope != null) { - if (this.timestamp != 0) { - dT = (timestamp - this.timestamp) * NS2S; + private static float[] doubleToFloat(double[] values) { + float[] f = new float[values.length]; - rotationOrientation = RotationUtil.vectorToQuaternion(orientation); - rotationVectorGyroscope = RotationUtil.integrateGyroscopeRotation(rotationVectorGyroscope, gyroscope, dT, EPSILON); - } - this.timestamp = timestamp; - - return output; - } else { - throw new IllegalStateException("You must call setBaseOrientation() before calling calculateFusedOrientation()!"); + for(int i = 0; i < f.length; i++){ + f[i] = (float) values[i]; } + + return f; } } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationMeasurementModel.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationMeasurementModel.java index 5027fa1..9b013c7 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationMeasurementModel.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationMeasurementModel.java @@ -5,23 +5,20 @@ import org.apache.commons.math3.linear.RealMatrix; /* - * Acceleration Explorer - * Copyright (C) 2013-2015, Kaleb Kircher - Kircher Engineering, LLC + * Copyright 2018, Kircher Electronics, LLC * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * http://www.apache.org/licenses/LICENSE-2.0 * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - public class RotationMeasurementModel implements MeasurementModel { private double noiseCoefficient = 0.001; diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationProcessModel.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationProcessModel.java index b9a02bc..365725a 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationProcessModel.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/filter/gyroscope/fusion/kalman/filter/RotationProcessModel.java @@ -7,21 +7,19 @@ import org.apache.commons.math3.linear.RealVector; /* - * Acceleration Explorer - * Copyright (C) 2013-2015, Kaleb Kircher - Kircher Engineering, LLC + * Copyright 2018, Kircher Electronics, LLC * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * http://www.apache.org/licenses/LICENSE-2.0 * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ public class RotationProcessModel implements ProcessModel diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAcceleration.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAcceleration.java index 436392f..fd32b19 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAcceleration.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAcceleration.java @@ -1,13 +1,15 @@ package com.kircherelectronics.fsensor.linearacceleration; +import com.kircherelectronics.fsensor.BaseFilter; + /* - * Copyright 2017 Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,8 +18,6 @@ * limitations under the License. */ -import com.kircherelectronics.fsensor.BaseFilter; - /** * A base implementation of a linear acceleration fusedOrientation. Linear acceleration is defined as * linearAcceleration = (acceleration - gravity). An acceleration sensor by itself is not capable of determining the diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationAveraging.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationAveraging.java index 9403b6b..c21c966 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationAveraging.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationAveraging.java @@ -3,13 +3,13 @@ import com.kircherelectronics.fsensor.filter.averaging.AveragingFilter; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,7 +17,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** * A wrapper for Linear Acceleration filters that are backed by Averaging filters. * Created by kaleb on 7/6/17. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationFusion.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationFusion.java index ecbb051..b252121 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationFusion.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/linearacceleration/LinearAccelerationFusion.java @@ -3,15 +3,14 @@ import com.kircherelectronics.fsensor.filter.gyroscope.fusion.OrientationFused; import com.kircherelectronics.fsensor.util.gravity.GravityUtil; - /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/FSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/FSensor.java new file mode 100644 index 0000000..f266d4a --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/FSensor.java @@ -0,0 +1,23 @@ +package com.kircherelectronics.fsensor.sensor; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface FSensor { + PublishSubject getPublishSubject(); +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/AccelerationSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/AccelerationSensor.java new file mode 100644 index 0000000..defeb68 --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/AccelerationSensor.java @@ -0,0 +1,130 @@ +package com.kircherelectronics.fsensor.sensor.acceleration; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.sensor.FSensor; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class AccelerationSensor implements FSensor { + private static final String TAG = AccelerationSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private float[] acceleration = new float[3]; + private float[] output = new float[4]; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public AccelerationSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void reset() { + onStop(); + acceleration = new float[3]; + output = new float[4]; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void processAcceleration(float[] acceleration) { + System.arraycopy(acceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void registerSensors(int sensorDelay) { + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + processAcceleration(event.values); + setOutput(acceleration); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/ComplimentaryLinearAccelerationSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/ComplimentaryLinearAccelerationSensor.java new file mode 100644 index 0000000..b377dfd --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/ComplimentaryLinearAccelerationSensor.java @@ -0,0 +1,198 @@ +package com.kircherelectronics.fsensor.sensor.acceleration; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.gyroscope.fusion.complimentary.OrientationFusedComplimentary; +import com.kircherelectronics.fsensor.linearacceleration.LinearAcceleration; +import com.kircherelectronics.fsensor.linearacceleration.LinearAccelerationFusion; +import com.kircherelectronics.fsensor.sensor.FSensor; +import com.kircherelectronics.fsensor.util.rotation.RotationUtil; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class ComplimentaryLinearAccelerationSensor implements FSensor { + private static final String TAG = ComplimentaryLinearAccelerationSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private boolean hasRotation = false; + private boolean hasMagnetic = false; + + private float[] magnetic = new float[3]; + private float[] rawAcceleration = new float[3]; + private float[] rotation = new float[3]; + private float[] acceleration = new float[3]; + private float[] output = new float[4]; + + private LinearAcceleration linearAccelerationFilterComplimentary; + private OrientationFusedComplimentary orientationFusionComplimentary; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public ComplimentaryLinearAccelerationSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void setFSensorComplimentaryLinearAccelerationTimeConstant(float timeConstant) { + orientationFusionComplimentary.setTimeConstant(timeConstant); + } + + public void reset() { + onStop(); + magnetic = new float[3]; + acceleration = new float[3]; + rotation = new float[3]; + output = new float[4]; + hasRotation = false; + hasMagnetic = false; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void initializeFSensorFusions() { + orientationFusionComplimentary = new OrientationFusedComplimentary(); + linearAccelerationFilterComplimentary = new LinearAccelerationFusion(orientationFusionComplimentary); + } + + private void processRawAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.rawAcceleration, 0, this.rawAcceleration.length); + } + + private void processAcceleration(float[] acceleration) { + System.arraycopy(acceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void processMagnetic(float[] magnetic) { + System.arraycopy(magnetic, 0, this.magnetic, 0, this.magnetic.length); + } + + private void processRotation(float[] rotation) { + System.arraycopy(rotation, 0, this.rotation, 0, this.rotation.length); + } + + private void registerSensors(int sensorDelay) { + + orientationFusionComplimentary.reset(); + + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, + sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processRawAcceleration(event.values); + if (!orientationFusionComplimentary.isBaseOrientationSet()) { + if (hasRotation && hasMagnetic) { + orientationFusionComplimentary.setBaseOrientation(RotationUtil.getOrientationVectorFromAccelerationMagnetic(rawAcceleration, magnetic)); + } + } else { + orientationFusionComplimentary.calculateFusedOrientation(rotation, event.timestamp, rawAcceleration, magnetic); + processAcceleration(linearAccelerationFilterComplimentary.filter(rawAcceleration)); + + setOutput(acceleration); + } + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + processMagnetic(event.values); + hasMagnetic = true; + } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) { + processRotation(event.values); + hasRotation = true; + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/KalmanLinearAccelerationSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/KalmanLinearAccelerationSensor.java new file mode 100644 index 0000000..535558a --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/KalmanLinearAccelerationSensor.java @@ -0,0 +1,194 @@ +package com.kircherelectronics.fsensor.sensor.acceleration; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.gyroscope.fusion.kalman.OrientationFusedKalman; +import com.kircherelectronics.fsensor.linearacceleration.LinearAcceleration; +import com.kircherelectronics.fsensor.linearacceleration.LinearAccelerationFusion; +import com.kircherelectronics.fsensor.sensor.FSensor; +import com.kircherelectronics.fsensor.util.rotation.RotationUtil; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class KalmanLinearAccelerationSensor implements FSensor { + private static final String TAG = KalmanLinearAccelerationSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private boolean hasRotation = false; + private boolean hasMagnetic = false; + + private float[] magnetic = new float[3]; + private float[] rawAcceleration = new float[3]; + private float[] rotation = new float[3]; + private float[] acceleration = new float[3]; + private float[] output = new float[4]; + + private LinearAcceleration linearAccelerationFilterKalman; + private OrientationFusedKalman orientationFusionKalman; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public KalmanLinearAccelerationSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + registerSensors(sensorFrequency); + orientationFusionKalman.startFusion(); + } + + public void onStop() { + orientationFusionKalman.stopFusion(); + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void reset() { + onStop(); + magnetic = new float[3]; + acceleration = new float[3]; + rotation = new float[3]; + output = new float[4]; + hasRotation = false; + hasMagnetic = false; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void initializeFSensorFusions() { + orientationFusionKalman = new OrientationFusedKalman(); + linearAccelerationFilterKalman = new LinearAccelerationFusion(orientationFusionKalman); + } + + private void processRawAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.rawAcceleration, 0, this.rawAcceleration.length); + } + + private void processAcceleration(float[] acceleration) { + System.arraycopy(acceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void processMagnetic(float[] magnetic) { + System.arraycopy(magnetic, 0, this.magnetic, 0, this.magnetic.length); + } + + private void processRotation(float[] rotation) { + System.arraycopy(rotation, 0, this.rotation, 0, this.rotation.length); + } + + private void registerSensors(int sensorDelay) { + + orientationFusionKalman.reset(); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, + sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processRawAcceleration(event.values); + if (!orientationFusionKalman.isBaseOrientationSet()) { + if (hasRotation && hasMagnetic) { + orientationFusionKalman.setBaseOrientation(RotationUtil.getOrientationVectorFromAccelerationMagnetic(rawAcceleration, magnetic)); + } + } else { + orientationFusionKalman.calculateFusedOrientation(rotation, event.timestamp, rawAcceleration, magnetic); + processAcceleration(linearAccelerationFilterKalman.filter(rawAcceleration)); + + setOutput(acceleration); + } + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + processMagnetic(event.values); + hasMagnetic = true; + } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) { + processRotation(event.values); + hasRotation = true; + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LinearAccelerationSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LinearAccelerationSensor.java new file mode 100644 index 0000000..99b9a2c --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LinearAccelerationSensor.java @@ -0,0 +1,130 @@ +package com.kircherelectronics.fsensor.sensor.acceleration; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.sensor.FSensor; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class LinearAccelerationSensor implements FSensor { + private static final String TAG = LinearAccelerationSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private float[] acceleration = new float[3]; + private float[] output = new float[4]; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public LinearAccelerationSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void reset() { + onStop(); + acceleration = new float[3]; + output = new float[4]; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void processAcceleration(float[] acceleration) { + System.arraycopy(acceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void registerSensors(int sensorDelay) { + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION), + sensorDelay); + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) { + + processAcceleration(event.values); + setOutput(acceleration); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LowPassLinearAccelerationSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LowPassLinearAccelerationSensor.java new file mode 100644 index 0000000..a0e4920 --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/acceleration/LowPassLinearAccelerationSensor.java @@ -0,0 +1,165 @@ +package com.kircherelectronics.fsensor.sensor.acceleration; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.averaging.LowPassFilter; +import com.kircherelectronics.fsensor.linearacceleration.LinearAcceleration; +import com.kircherelectronics.fsensor.linearacceleration.LinearAccelerationAveraging; +import com.kircherelectronics.fsensor.sensor.FSensor; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class LowPassLinearAccelerationSensor implements FSensor { + private static final String TAG = LowPassLinearAccelerationSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private float[] rawAcceleration = new float[3]; + private float[] acceleration = new float[3]; + private float[] output = new float[4]; + + private LinearAcceleration linearAccelerationFilterLpf; + + private LowPassFilter lpfGravity; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public LowPassLinearAccelerationSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void setFSensorLpfLinearAccelerationTimeConstant(float timeConstant) { + lpfGravity.setTimeConstant(timeConstant); + } + + public void reset() { + onStop(); + acceleration = new float[3]; + output = new float[4]; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private float[] invert(float[] values) { + for (int i = 0; i < values.length; i++) { + values[i] = -values[i]; + } + + return values; + } + + private void initializeFSensorFusions() { + lpfGravity = new LowPassFilter(); + linearAccelerationFilterLpf = new LinearAccelerationAveraging(lpfGravity); + } + + private void processRawAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.rawAcceleration, 0, this.rawAcceleration.length); + } + + private void processAcceleration(float[] acceleration) { + System.arraycopy(acceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void registerSensors(int sensorDelay) { + + lpfGravity.reset(); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processRawAcceleration(event.values); + lpfGravity.filter(rawAcceleration); + processAcceleration(linearAccelerationFilterLpf.filter(rawAcceleration)); + setOutput(acceleration); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/ComplimentaryGyroscopeSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/ComplimentaryGyroscopeSensor.java new file mode 100644 index 0000000..750360a --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/ComplimentaryGyroscopeSensor.java @@ -0,0 +1,192 @@ +package com.kircherelectronics.fsensor.sensor.gyroscope; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.gyroscope.fusion.complimentary.OrientationFusedComplimentary; +import com.kircherelectronics.fsensor.sensor.FSensor; +import com.kircherelectronics.fsensor.util.rotation.RotationUtil; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class ComplimentaryGyroscopeSensor implements FSensor { + private static final String TAG = ComplimentaryGyroscopeSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private boolean hasAcceleration = false; + private boolean hasMagnetic = false; + + private float[] magnetic = new float[3]; + private float[] acceleration = new float[3]; + private float[] rotation = new float[3]; + private float[] output = new float[4]; + + private OrientationFusedComplimentary orientationFusionComplimentary; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public ComplimentaryGyroscopeSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void setFSensorComplimentaryTimeConstant(float timeConstant) { + orientationFusionComplimentary.setTimeConstant(timeConstant); + } + + public void reset() { + onStop(); + magnetic = new float[3]; + acceleration = new float[3]; + rotation = new float[3]; + output = new float[4]; + hasAcceleration = false; + hasMagnetic = false; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void initializeFSensorFusions() { + orientationFusionComplimentary = new OrientationFusedComplimentary(); + } + + private void processAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void processMagnetic(float[] magnetic) { + System.arraycopy(magnetic, 0, this.magnetic, 0, this.magnetic.length); + } + + private void processRotation(float[] rotation) { + System.arraycopy(rotation, 0, this.rotation, 0, this.rotation.length); + } + + private void registerSensors(int sensorDelay) { + + orientationFusionComplimentary.reset(); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, + sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setValue(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + private int sensorEventThreshold = 100; + private int numAccelerationEvents = 0; + private int numMagneticEvents = 0; + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processAcceleration(event.values); + if(numAccelerationEvents++ > sensorEventThreshold) { + hasAcceleration = true; + } + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + processMagnetic(event.values); + if(numMagneticEvents ++ > sensorEventThreshold) { + hasMagnetic = true; + } + } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) { + processRotation(event.values); + if (!orientationFusionComplimentary.isBaseOrientationSet()) { + if (hasAcceleration && hasMagnetic) { + orientationFusionComplimentary.setBaseOrientation(RotationUtil.getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic)); + } + } else { + setValue(orientationFusionComplimentary.calculateFusedOrientation(rotation, event.timestamp, acceleration, magnetic)); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/GyroscopeSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/GyroscopeSensor.java new file mode 100644 index 0000000..3e24925 --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/GyroscopeSensor.java @@ -0,0 +1,192 @@ +package com.kircherelectronics.fsensor.sensor.gyroscope; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.gyroscope.OrientationGyroscope; +import com.kircherelectronics.fsensor.sensor.FSensor; +import com.kircherelectronics.fsensor.util.rotation.RotationUtil; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class GyroscopeSensor implements FSensor { + + private static final String TAG = GyroscopeSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private boolean hasAcceleration = false; + private boolean hasMagnetic = false; + + private float[] magnetic = new float[3]; + private float[] acceleration = new float[3]; + private float[] rotation = new float[3]; + private float[] output = new float[4]; + + private OrientationGyroscope orientationGyroscope; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public GyroscopeSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + registerSensors(sensorFrequency); + } + + public void onStop() { + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void reset() { + onStop(); + magnetic = new float[3]; + acceleration = new float[3]; + rotation = new float[3]; + output = new float[4]; + hasAcceleration = false; + hasMagnetic = false; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void initializeFSensorFusions() { + orientationGyroscope = new OrientationGyroscope(); + } + + private void processAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void processMagnetic(float[] magnetic) { + System.arraycopy(magnetic, 0, this.magnetic, 0, this.magnetic.length); + } + + private void processRotation(float[] rotation) { + System.arraycopy(rotation, 0, this.rotation, 0, this.rotation.length); + } + + private void registerSensors(int sensorDelay) { + + orientationGyroscope.reset(); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, + sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + private int sensorEventThreshold = 500; + private int numAccelerationEvents = 0; + private int numMagneticEvents = 0; + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processAcceleration(event.values); + if(numAccelerationEvents++ > sensorEventThreshold) { + hasAcceleration = true; + } + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + processMagnetic(event.values); + if(numMagneticEvents ++ > sensorEventThreshold) { + hasMagnetic = true; + } + } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { + processRotation(event.values); + + if (!orientationGyroscope.isBaseOrientationSet()) { + if (hasAcceleration && hasMagnetic) { + orientationGyroscope.setBaseOrientation(RotationUtil.getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic)); + } + } else { + setOutput(orientationGyroscope.calculateOrientation(rotation, event.timestamp)); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} + + diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/KalmanGyroscopeSensor.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/KalmanGyroscopeSensor.java new file mode 100644 index 0000000..d6e083b --- /dev/null +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/sensor/gyroscope/KalmanGyroscopeSensor.java @@ -0,0 +1,183 @@ +package com.kircherelectronics.fsensor.sensor.gyroscope; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import com.kircherelectronics.fsensor.filter.gyroscope.fusion.kalman.OrientationFusedKalman; +import com.kircherelectronics.fsensor.sensor.FSensor; +import com.kircherelectronics.fsensor.util.rotation.RotationUtil; + +import io.reactivex.subjects.PublishSubject; + +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class KalmanGyroscopeSensor implements FSensor { + private static final String TAG = KalmanGyroscopeSensor.class.getSimpleName(); + + private SensorManager sensorManager; + private SimpleSensorListener listener; + private float startTime = 0; + private int count = 0; + + private boolean hasAcceleration = false; + private boolean hasMagnetic = false; + + private float[] magnetic = new float[3]; + private float[] acceleration = new float[3]; + private float[] rotation = new float[3]; + private float[] output = new float[4]; + + private OrientationFusedKalman orientationFusionKalman; + + private int sensorFrequency = SensorManager.SENSOR_DELAY_FASTEST; + + private PublishSubject publishSubject; + + public KalmanGyroscopeSensor(Context context) { + this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + this.listener = new SimpleSensorListener(); + this.publishSubject = PublishSubject.create(); + initializeFSensorFusions(); + } + + @Override + public PublishSubject getPublishSubject() { + return publishSubject; + } + + public void onStart() { + startTime = 0; + count = 0; + registerSensors(sensorFrequency); + orientationFusionKalman.startFusion(); + } + + public void onStop() { + orientationFusionKalman.stopFusion(); + unregisterSensors(); + } + + public void setSensorFrequency(int sensorFrequency) { + this.sensorFrequency = sensorFrequency; + } + + public void reset() { + onStop(); + magnetic = new float[3]; + acceleration = new float[3]; + rotation = new float[3]; + output = new float[4]; + hasAcceleration = false; + hasMagnetic = false; + onStart(); + } + + private float calculateSensorFrequency() { + // Initialize the start time. + if (startTime == 0) { + startTime = System.nanoTime(); + } + + long timestamp = System.nanoTime(); + + // Find the sample period (between updates) and convert from + // nanoseconds to seconds. Note that the sensor delivery rates can + // individually vary by a relatively large time frame, so we use an + // averaging technique with the number of sensor updates to + // determine the delivery rate. + + return (count++ / ((timestamp - startTime) / 1000000000.0f)); + } + + private void initializeFSensorFusions() { + orientationFusionKalman = new OrientationFusedKalman(); + } + + private void processAcceleration(float[] rawAcceleration) { + System.arraycopy(rawAcceleration, 0, this.acceleration, 0, this.acceleration.length); + } + + private void processMagnetic(float[] magnetic) { + System.arraycopy(magnetic, 0, this.magnetic, 0, this.magnetic.length); + } + + private void processRotation(float[] rotation) { + System.arraycopy(rotation, 0, this.rotation, 0, this.rotation.length); + } + + private void registerSensors(int sensorDelay) { + + orientationFusionKalman.reset(); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, sensorManager + .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), + sensorDelay); + + // Register for sensor updates. + sensorManager.registerListener(listener, + sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED), + sensorDelay); + + } + + private void unregisterSensors() { + sensorManager.unregisterListener(listener); + } + + private void setOutput(float[] value) { + System.arraycopy(value, 0, output, 0, value.length); + output[3] = calculateSensorFrequency(); + publishSubject.onNext(output); + } + + private class SimpleSensorListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + processAcceleration(event.values); + hasAcceleration = true; + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + processMagnetic(event.values); + hasMagnetic = true; + } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) { + processRotation(event.values); + + if (!orientationFusionKalman.isBaseOrientationSet()) { + if (hasAcceleration && hasMagnetic) { + orientationFusionKalman.setBaseOrientation(RotationUtil.getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic)); + } + } else { + setOutput(orientationFusionKalman.calculateFusedOrientation(rotation, event.timestamp, acceleration, magnetic)); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/gravity/GravityUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/gravity/GravityUtil.java index 73777c8..98d2ead 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/gravity/GravityUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/gravity/GravityUtil.java @@ -1,9 +1,12 @@ package com.kircherelectronics.fsensor.util.gravity; import android.hardware.SensorManager; +import android.util.Log; + +import java.util.Arrays; /* - * Copyright 2017, Kircher Electronics, LLC + * Copyright 2018, Kircher Electronics, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,27 +31,33 @@ public class GravityUtil { private static float[] gravity = new float[]{SensorManager.GRAVITY_EARTH, SensorManager.GRAVITY_EARTH, SensorManager.GRAVITY_EARTH}; + /** + * Assumes a positive, counter-clockwise, right-handed rotation + * orientation[0] = pitch, rotation around the X axis. + * orientation[1] = roll, rotation around the Y axis + * orientation[2] = azimuth, rotation around the Z axis + * @param orientation The orientation. + * @return The gravity components of the orientation. + */ public static float[] getGravityFromOrientation(float[] orientation) { - // components[0]: azimuth, rotation around the Z axis. - // components[1]: pitch, rotation around the X axis. - // components[2]: roll, rotation around the Y axis. + float[] components = new float[3]; // Find the gravity component of the X-axis // = g*-cos(pitch)*sin(roll); - components[0] = (float) (gravity[0] - * -Math.cos(orientation[1]) * Math - .sin(orientation[2])); + components[0] = (float) -(gravity[0] + * -Math.cos(orientation[0]) * Math + .sin(orientation[1])); // Find the gravity component of the Y-axis // = g*-sin(pitch); components[1] = (float) (gravity[1] * -Math - .sin(orientation[1])); + .sin(orientation[0])); // Find the gravity component of the Z-axis // = g*cos(pitch)*cos(roll); components[2] = (float) (gravity[2] - * Math.cos(orientation[1]) * Math.cos(orientation[2])); + * Math.cos(orientation[0]) * Math.cos(orientation[1])); return components; } diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/AzimuthUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/AzimuthUtil.java index 1506c8a..fbf82f0 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/AzimuthUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/AzimuthUtil.java @@ -1,5 +1,21 @@ package com.kircherelectronics.fsensor.util.magnetic; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * A helper class for * Created by kaleb on 3/18/18. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/declination/DeclinationUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/declination/DeclinationUtil.java index bbf4536..6932d74 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/declination/DeclinationUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/declination/DeclinationUtil.java @@ -3,6 +3,22 @@ import android.hardware.GeomagneticField; import android.location.Location; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Compensate for magnetic declination based on a location. * diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/tilt/TiltCompensationUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/tilt/TiltCompensationUtil.java index 1642867..5192643 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/tilt/TiltCompensationUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/magnetic/tilt/TiltCompensationUtil.java @@ -1,5 +1,21 @@ package com.kircherelectronics.fsensor.util.magnetic.tilt; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Providers helpers to compensate for the tilt of a magnetic sensor. * Created by kaleb on 3/18/18. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/Calibration.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/Calibration.java index 33cd762..38fc052 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/Calibration.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/Calibration.java @@ -3,6 +3,22 @@ import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.linear.RealVector; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Represents a field calibration. * Created by kaleb on 3/18/18. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/CalibrationUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/CalibrationUtil.java index 8277cd1..d2f5062 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/CalibrationUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/CalibrationUtil.java @@ -7,6 +7,22 @@ import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.linear.RealVector; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Transforms the ellipsoid into a sphere with the offset vector = [0,0,0] and * the radii vector = [1,1,1]. diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/FitPoints.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/FitPoints.java index fe0771b..ad511d1 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/FitPoints.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/FitPoints.java @@ -14,6 +14,22 @@ import java.util.ArrayList; import java.util.Arrays; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Determines the offset, radii, eigenvalues and eigenvectors of the ellipse * using an expanded algorithm based on Yury Petrov's Ellipsoid Fit MATLAB script. The diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/ThreeSpacePoint.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/ThreeSpacePoint.java index edc063f..66d844b 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/ThreeSpacePoint.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/offset/ThreeSpacePoint.java @@ -1,5 +1,21 @@ package com.kircherelectronics.fsensor.util.offset; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * A representation of a three space point with double precision. * diff --git a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/rotation/RotationUtil.java b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/rotation/RotationUtil.java index 879ac6d..7b9da76 100644 --- a/fsensor/src/main/java/com/kircherelectronics/fsensor/util/rotation/RotationUtil.java +++ b/fsensor/src/main/java/com/kircherelectronics/fsensor/util/rotation/RotationUtil.java @@ -3,9 +3,28 @@ import android.hardware.SensorManager; import org.apache.commons.math3.complex.Quaternion; +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention; +import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder; import java.util.Arrays; +/* + * Copyright 2018, Kircher Electronics, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Created by kaleb on 4/1/18. */ @@ -58,84 +77,42 @@ public static Quaternion integrateGyroscopeRotation(Quaternion previousRotationV /** * Calculates orientation vector from accelerometer and magnetometer output. + * * @param acceleration the acceleration measurement. - * @param magnetic the magnetic measurement. - * @return {@link SensorManager#getOrientation(float[], float[])} + * @param magnetic the magnetic measurement. + * @return */ - public static float[] getOrientationVectorFromAccelerationMagnetic(float[] acceleration, float[] magnetic) { + public static Quaternion getOrientationVectorFromAccelerationMagnetic(float[] acceleration, float[] magnetic) { float[] rotationMatrix = new float[9]; if (SensorManager.getRotationMatrix(rotationMatrix, null, acceleration, magnetic)) { - float[] baseOrientation = new float[3]; - SensorManager.getOrientation(rotationMatrix, baseOrientation); - - return baseOrientation; + float[] rv = new float[3]; + SensorManager.getOrientation(rotationMatrix,rv); + // SensorManager.getOrientation() returns an orientation in Earth frame and that needs to be rotated into device frame so the reported angles + // are indexed with the orientation of the sensors + Rotation rotation = new Rotation(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR, rv[1], -rv[2], rv[0]); + return new Quaternion(rotation.getQ0(), rotation.getQ1(),rotation.getQ2(),rotation.getQ3()); } return null; } - /** - * Calculates orientation vector from accelerometer and magnetometer output. - * @param acceleration the acceleration measurement. - * @param magnetic the magnetic measurement. - * @return A Quaternion representation of the vector returned by {@link SensorManager#getOrientation(float[], float[])} - */ - public static Quaternion getOrientationQuaternionFromAccelerationMagnetic(float[] acceleration, float[] magnetic) { - return vectorToQuaternion(getOrientationVectorFromAccelerationMagnetic(acceleration, magnetic)); - } - - /** - * Create an quaternion vector, in this case a unit quaternion, from the - * provided Euler angle's (presumably from SensorManager.getFusedOrientation()). - *

- * - * @see Equation - * @param vector The vector to convert to a Quaternion - * @return A Quaternion representation of the vector. - */ - public static Quaternion vectorToQuaternion(float[] vector) { - if (vector != null) { - // Assuming the angles are in radians. - - // getFusedOrientation() values: - // values[0]: azimuth, rotation around the Z axis. - // values[1]: pitch, rotation around the X axis. - // values[2]: roll, rotation around the Y axis. - - // Heading, AzimuthUtil, Yaw - double c1 = Math.cos(vector[0] / 2); - double s1 = Math.sin(vector[0] / 2); - - // Pitch, Attitude - // The equation assumes the pitch is pointed in the opposite direction - // of the orientation vector provided by Android, so we invert it. - double c2 = Math.cos(vector[1] / 2); - double s2 = Math.sin(vector[1] / 2); - - // Roll, Bank - double c3 = Math.cos(vector[2] / 2); - double s3 = Math.sin(vector[2] / 2); - - double c1c2 = c1 * c2; - double s1s2 = s1 * s2; - - double w = c1c2 * c3 - s1s2 * s3; - double x = c1c2 * s3 + s1s2 * c3; - double y = s1 * c2 * c3 + c1 * s2 * s3; - double z = c1 * s2 * c3 - s1 * c2 * s3; - - // The quaternion in the equation does not share the same coordinate - // system as the Android gyroscope quaternion we are using. We reorder - // it here. - - // Android X (pitch) = Equation Z (pitch) - // Android Y (roll) = Equation X (roll) - // Android Z (azimuth) = Equation Y (azimuth) - - // Note that the gyroscope sensor reports the rotation as positive in the counter-clockwise direction so we invert. - return new Quaternion(w, -z, -x, -y); + private static double[][] convertTo2DArray(float[] rotation) { + if (rotation.length != 9) { + throw new IllegalStateException("Length must be of 9! Length: " + rotation.length); } - return null; + double[][] rm = new double[3][3]; + + rm[0][0] = rotation[0]; + rm[0][1] = rotation[1]; + rm[0][2] = rotation[2]; + rm[1][0] = rotation[3]; + rm[1][1] = rotation[4]; + rm[1][2] = rotation[5]; + rm[2][0] = rotation[6]; + rm[2][1] = rotation[7]; + rm[2][2] = rotation[8]; + + return rm; } } diff --git a/fsensor/src/main/res/values/strings.xml b/fsensor/src/main/res/values/strings.xml index d98be84..8dc39d8 100644 --- a/fsensor/src/main/res/values/strings.xml +++ b/fsensor/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - FSesnor + FSensor diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e8f7599..0d19dc3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 11 16:38:00 MDT 2018 +#Sun Oct 28 11:42:27 MDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip