7 package com.google.appinventor.components.runtime;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.hardware.Sensor;
23 import android.hardware.SensorEvent;
24 import android.hardware.SensorEventListener;
25 import android.hardware.SensorManager;
26 import android.util.Log;
32 @DesignerComponent(version = YaVersion.PEDOMETER_COMPONENT_VERSION,
33 description =
"A Component that acts like a Pedometer. It senses motion via the " +
34 "Accerleromter and attempts to determine if a step has been " +
35 "taken. Using a configurable stride length, it can estimate the " +
36 "distance traveled as well. ",
37 category = ComponentCategory.SENSORS,
39 iconName =
"images/pedometer.png")
43 private static final String TAG =
"Pedometer";
44 private static final String PREFS_NAME =
"PedometerPrefs";
46 private static final int INTERVAL_VARIATION = 250;
47 private static final int NUM_INTERVALS = 2;
48 private static final int WIN_SIZE = 100;
49 private static final float STRIDE_LENGTH = (float) 0.73;
50 private static final float PEAK_VALLEY_RANGE = (float) 40.0;
52 private final Context context;
53 private final SensorManager sensorManager;
55 private int stopDetectionTimeout = 2000;
56 private int winPos = 0, intervalPos = 0;
57 private int numStepsWithFilter = 0, numStepsRaw = 0;
58 private float lastValley = 0;
59 private float[] lastValues =
new float[WIN_SIZE];
60 private float strideLength = STRIDE_LENGTH;
61 private float totalDistance = 0;
62 private long[] stepInterval =
new long[NUM_INTERVALS];
63 private long stepTimestamp = 0;
64 private long startTime = 0, prevStopClockTime = 0;
65 private boolean foundValley =
false;
66 private boolean startPeaking =
false;
67 private boolean foundNonStep =
true;
68 private boolean pedometerPaused =
true;
70 private float[] avgWindow =
new float[10];
71 private int avgPos = 0;
75 super(container.
$form());
80 numStepsWithFilter = 0;
86 sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
89 SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
90 strideLength = settings.getFloat(
"Pedometer.stridelength", STRIDE_LENGTH);
91 totalDistance = settings.getFloat(
"Pedometer.distance", 0);
92 numStepsRaw = settings.getInt(
"Pedometer.prevStepCount", 0);
93 prevStopClockTime = settings.getLong(
"Pedometer.clockTime", 0);
94 numStepsWithFilter = numStepsRaw;
95 startTime = System.currentTimeMillis();
96 Log.d(TAG,
"Pedometer Created");
106 if (pedometerPaused) {
107 pedometerPaused =
false;
108 sensorManager.registerListener(
this,
109 sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0),
110 SensorManager.SENSOR_DELAY_FASTEST);
111 startTime = System.currentTimeMillis();
120 if (!pedometerPaused) {
121 pedometerPaused =
true;
122 sensorManager.unregisterListener(
this);
123 Log.d(TAG,
"Unregistered listener on pause");
124 prevStopClockTime += (System.currentTimeMillis() - startTime);
132 @
SimpleFunction(description =
"Resets the step counter, distance measure and time running.")
134 numStepsWithFilter = 0;
137 prevStopClockTime = 0;
138 startTime = System.currentTimeMillis();
145 @
SimpleFunction(description =
"Resumes counting, synonym of Start.")
154 @
SimpleFunction(description =
"Pause counting of steps and distance.")
163 @
SimpleFunction(description =
"Saves the pedometer state to the phone. Permits " +
164 "permits accumulation of steps and distance between invocations of an App that uses " +
165 "the pedometer. Different Apps will have their own saved state.")
168 SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
169 SharedPreferences.Editor editor = settings.edit();
170 editor.putFloat(
"Pedometer.stridelength", strideLength);
171 editor.putFloat(
"Pedometer.distance", totalDistance);
172 editor.putInt(
"Pedometer.prevStepCount", numStepsRaw);
173 if (pedometerPaused) {
174 editor.putLong(
"Pedometer.clockTime", prevStopClockTime);
176 editor.putLong(
"Pedometer.clockTime", prevStopClockTime +
177 (System.currentTimeMillis() - startTime));
179 editor.putLong(
"Pedometer.closeTime", System.currentTimeMillis());
181 Log.d(TAG,
"Pedometer state saved.");
193 @
SimpleEvent(description =
"This event is run when a raw step is detected.")
206 @
SimpleEvent(description =
"This event is run when a walking step is detected. " +
207 "A walking step is a step that appears to be involved in forward motion.")
208 public
void WalkStep(
int walkSteps,
float distance) {
222 defaultValue =
"0.73")
224 description =
"Set the average stride length in meters.",
227 strideLength = length;
247 defaultValue =
"2000")
250 description =
"The duration in milliseconds of idleness (no steps detected) " +
251 "after which to go into a \"stopped\" state")
253 stopDetectionTimeout = timeout;
263 return stopDetectionTimeout;
274 return totalDistance;
283 category =
PropertyCategory.
BEHAVIOR, description =
"Time elapsed in milliseconds since the pedometer was started.")
285 if (pedometerPaused) {
286 return prevStopClockTime;
288 return prevStopClockTime + (System.currentTimeMillis() - startTime);
298 description =
"The number of simple steps taken since the pedometer has started.")
309 description =
"the number of walk steps taken since the pedometer has started.")
311 return numStepsWithFilter;
318 private boolean areStepsEquallySpaced() {
321 for (
long interval : stepInterval) {
328 for (
long interval : stepInterval) {
329 if (Math.abs(interval - avg) > INTERVAL_VARIATION) {
339 private boolean isPeak() {
340 int mid = (winPos + WIN_SIZE / 2) % WIN_SIZE;
341 for (
int i = 0; i < WIN_SIZE; i++) {
342 if (i != mid && lastValues[i] > lastValues[mid]) {
352 private boolean isValley() {
353 int mid = (winPos + WIN_SIZE / 2) % WIN_SIZE;
354 for (
int i = 0; i < WIN_SIZE; i++) {
355 if (i != mid && lastValues[i] < lastValues[mid]) {
366 Log.d(TAG,
"Accelerometer accuracy changed.");
371 if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
374 float[] values =
event.values;
376 for (
float v : values) magnitude += v * v;
379 int mid = (winPos + WIN_SIZE / 2) % WIN_SIZE;
382 if (startPeaking && isPeak()) {
383 if (foundValley && lastValues[mid] - lastValley > PEAK_VALLEY_RANGE) {
385 long timestamp = System.currentTimeMillis();
386 stepInterval[intervalPos] = timestamp - stepTimestamp;
387 intervalPos = (intervalPos + 1) % NUM_INTERVALS;
388 stepTimestamp = timestamp;
389 if (areStepsEquallySpaced()) {
391 numStepsWithFilter += NUM_INTERVALS;
392 totalDistance += strideLength * NUM_INTERVALS;
393 foundNonStep =
false;
395 numStepsWithFilter++;
396 WalkStep(numStepsWithFilter, totalDistance);
397 totalDistance += strideLength;
407 if (startPeaking && isValley()) {
409 lastValley = lastValues[mid];
412 avgWindow[avgPos] = magnitude;
413 avgPos = (avgPos + 1) % avgWindow.length;
414 lastValues[winPos] = 0;
415 for (
float m : avgWindow) lastValues[winPos] += m;
416 lastValues[winPos] /= avgWindow.length;
417 if (startPeaking || winPos > 1) {
419 if (--i < 0) i += WIN_SIZE;
420 lastValues[winPos] += 2 * lastValues[i];
421 if (--i < 0) i += WIN_SIZE;
422 lastValues[winPos] += lastValues[i];
423 lastValues[winPos] /= 4;
424 }
else if (!startPeaking && winPos == 1) {
425 lastValues[1] = (lastValues[1] + lastValues[0]) / 2f;
428 long elapsedTimestamp = System.currentTimeMillis();
429 if (elapsedTimestamp - stepTimestamp > stopDetectionTimeout) {
430 stepTimestamp = elapsedTimestamp;
433 if (winPos == WIN_SIZE - 1 && !startPeaking) {
437 winPos = (winPos + 1) % WIN_SIZE;
443 sensorManager.unregisterListener(
this);
454 @
SimpleEvent(description =
"This event has been deprecated.")
459 @
SimpleEvent(description =
"This event has been deprecated.")
466 description =
"This property has been deprecated.")
471 @
SimpleEvent(description =
"This event has been deprecated.")
476 @
SimpleEvent(description =
"This event has been deprecated.")
481 @
SimpleEvent(description =
"This event has been deprecated.")
489 description =
"This property has been deprecated.")
494 @
SimpleProperty(description =
"This property has been deprecated.")
500 @
SimpleProperty(description =
"This property has been deprecated.")