diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml
index 9d0a6c603..960e631e9 100644
--- a/MyTracks/res/values/keys.xml
+++ b/MyTracks/res/values/keys.xml
@@ -19,7 +19,7 @@ limitations under the License.
allowAccess
announcementFrequency
antCadenceSensorId
- antCadSpdSensorId
+ antCadenceSpeedSensorId
antHeartRateSensorId
antSRMBridgeSensorId
autoResumeTrackTimeout
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java
index c2e32f92e..a16883f98 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntDirectSensorManager.java
@@ -200,13 +200,13 @@ public class AntDirectSensorManager extends AntSensorManager
Log.i(TAG, "setup channel=" + channel + " deviceType=" + sensor.getDeviceType());
return setupAntSensorChannel(sensor.getNetworkNumber(),
- channel,
- sensor.getDeviceNumber(),
- sensor.getDeviceType(),
- (byte) 0x01,
- sensor.getChannelPeriod(),
- sensor.getFrequency(),
- (byte) 0);
+ channel,
+ sensor.getDeviceNumber(),
+ sensor.getDeviceType(),
+ (byte) 0x01,
+ sensor.getChannelPeriod(),
+ sensor.getFrequency(),
+ (byte) 0);
}
private void sendSensorData(byte index, int value) {
@@ -242,11 +242,11 @@ public class AntDirectSensorManager extends AntSensorManager
sensorData = b.setCreationTime(now).build();
}
- public void sendCadence(int value) {
+ public void setCadence(int value) {
sendSensorData(CADENCE_DATA_INDEX, value);
}
- public void sendHeartRate(int value) {
+ public void setHeartRate(int value) {
sendSensorData(HEART_RATE_DATA_INDEX, value);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorBase.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorBase.java
index 4212eed72..6e2dfb3f1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorBase.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorBase.java
@@ -32,9 +32,9 @@ public abstract class AntSensorBase {
public static final byte NETWORK_NUMBER = 1;
public static final byte RF_FREQUENCY = 57;
- protected short deviceNumber;
- private byte deviceType;
- private short channelPeriod;
+ private short deviceNumber;
+ private final byte deviceType;
+ private final short channelPeriod;
AntSensorBase(short deviceNumber, byte deviceType,
String deviceTypeString, short channelPeriod) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorDataCollector.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorDataCollector.java
index 88990a65a..3efbaa696 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorDataCollector.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorDataCollector.java
@@ -17,14 +17,19 @@
package com.google.android.apps.mytracks.services.sensors.ant;
/**
- * Interface for collecting data from the sensor classes
- *
+ * Interface for collecting data from the sensors.
* @author Laszlo Molnar
*/
public interface AntSensorDataCollector {
- public void sendCadence(int value);
+ /**
+ * Sets the current cadence to the value specified.
+ */
+ void setCadence(int value);
- public void sendHeartRate(int value);
+ /**
+ * Sets the current heart rate to the value specified.
+ */
+ void setHeartRate(int value);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSensor.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSensor.java
index 1cc557a78..99a2ea0e1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSensor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSensor.java
@@ -17,7 +17,7 @@ package com.google.android.apps.mytracks.services.sensors.ant;
/**
- * Cadence sensor.
+ * Ant+ cadence sensor.
*
* @author Laszlo Molnar
*/
@@ -29,20 +29,20 @@ public class CadenceSensor extends AntSensorBase {
public static final byte CADENCE_DEVICE_TYPE = 122;
public static final short CADENCE_CHANNEL_PERIOD = 8102;
- SensorDataProcessor cadence = new SensorDataProcessor();
+ SensorDataProcessor dataProcessor = new SensorDataProcessor();
CadenceSensor(short devNum) {
- super(devNum, CADENCE_DEVICE_TYPE,
- "cadence sensor", CADENCE_CHANNEL_PERIOD);
+ super(devNum, CADENCE_DEVICE_TYPE, "cadence sensor", CADENCE_CHANNEL_PERIOD);
}
/**
* Decode an ANT+ cadence sensor message.
* @param antMessage The byte array received from the cadence sensor.
*/
+ @Override
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
int sensorTime = ((int) antMessage[5] & 0xFF) + ((int) antMessage[6] & 0xFF) * 256;
int crankRevs = ((int) antMessage[7] & 0xFF) + ((int) antMessage[8] & 0xFF) * 256;
- c.sendCadence(cadence.getValue(crankRevs, sensorTime));
+ c.setCadence(dataProcessor.getValue(crankRevs, sensorTime));
}
};
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSpeedSensor.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSpeedSensor.java
index 1331fea66..93c8aa6dd 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSpeedSensor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/CadenceSpeedSensor.java
@@ -17,7 +17,7 @@ package com.google.android.apps.mytracks.services.sensors.ant;
/**
- * Combined cadence and speed sensor.
+ * Ant+ combined cadence and speed sensor.
*
* @author Laszlo Molnar
*/
@@ -29,20 +29,20 @@ public class CadenceSpeedSensor extends AntSensorBase {
public static final byte CADENCE_SPEED_DEVICE_TYPE = 121;
public static final short CADENCE_SPEED_CHANNEL_PERIOD = 8086;
- SensorDataProcessor cadence = new SensorDataProcessor();
+ SensorDataProcessor dataProcessor = new SensorDataProcessor();
CadenceSpeedSensor(short devNum) {
- super(devNum, CADENCE_SPEED_DEVICE_TYPE,
- "speed&cadence sensor", CADENCE_SPEED_CHANNEL_PERIOD);
+ super(devNum, CADENCE_SPEED_DEVICE_TYPE, "speed&cadence sensor", CADENCE_SPEED_CHANNEL_PERIOD);
}
/**
* Decode an ANT+ cadence&speed sensor message.
* @param antMessage The byte array received from the sensor.
*/
+ @Override
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
int sensorTime = ((int) antMessage[1] & 0xFF) + ((int) antMessage[2] & 0xFF) * 256;
int crankRevs = ((int) antMessage[3] & 0xFF) + ((int) antMessage[4] & 0xFF) * 256;
- c.sendCadence(cadence.getValue(crankRevs, sensorTime));
+ c.setCadence(dataProcessor.getValue(crankRevs, sensorTime));
}
};
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/HeartRateSensor.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/HeartRateSensor.java
index a1331578f..406e56c2c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/HeartRateSensor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/HeartRateSensor.java
@@ -20,7 +20,7 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import android.util.Log;
/**
- * Heart reate monitor sensor.
+ * Ant+ heart reate monitor sensor.
*
* @author Laszlo Molnar
*/
@@ -33,17 +33,17 @@ public class HeartRateSensor extends AntSensorBase {
public static final short HEART_RATE_CHANNEL_PERIOD = 8070;
HeartRateSensor(short devNum) {
- super(devNum, HEART_RATE_DEVICE_TYPE,
- "heart rate monitor", HEART_RATE_CHANNEL_PERIOD);
+ super(devNum, HEART_RATE_DEVICE_TYPE, "heart rate monitor", HEART_RATE_CHANNEL_PERIOD);
}
/**
* Decode an ANT+ heart rate monitor message.
* @param antMessage The byte array received from the heart rate monitor.
*/
+ @Override
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
int bpm = (int) antMessage[8] & 0xFF;
Log.d(TAG, "now:" + System.currentTimeMillis() + " heart rate=" + bpm);
- c.sendHeartRate(bpm);
+ c.setHeartRate(bpm);
}
};
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/SensorDataProcessor.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/SensorDataProcessor.java
index 9306aeb3f..5aec3733c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/SensorDataProcessor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/SensorDataProcessor.java
@@ -20,7 +20,7 @@ import android.util.Log;
import java.util.LinkedList;
/**
- * Processes an ANT sensor data (counter) + timestamp pair,
+ * Processes an ANT+ sensor data (counter) + timestamp pair,
* and returns the instantaneous value of the sensor
*
* @author Laszlo Molnar
@@ -28,84 +28,141 @@ import java.util.LinkedList;
public class SensorDataProcessor {
/**
- * History is used for looking back at the old data
- * when no new data is present, but an instantaneous value is needed
+ * HistoryElement stores a time stamped sensor counter
*/
- private class HistoryElement {
- public long sysTime;
- public int data;
- public int sensorTime;
+ private static class HistoryElement {
+ final long systemTime;
+ final int data;
+ final int sensorTime;
- HistoryElement(long sys, int d, int sens) {
- sysTime = sys;
- data = d;
- sensorTime = sens;
+ HistoryElement(long systemTime, int data, int sensorTime) {
+ this.systemTime = systemTime;
+ this.data = data;
+ this.sensorTime = sensorTime;
}
- };
+ }
- // Removes old data from the history.
- // Returns true if the remaining history is not empty.
+ /**
+ * Removes old data from the history.
+ *
+ * @param now the current system time
+ * @return true if the remaining history is not empty
+ */
protected boolean removeOldHistory(long now) {
HistoryElement h;
while ((h = history.peek()) != null) {
- if (now - h.sysTime <= historyLengthMillis) {
+ // if the first element of the list is in our desired time range then return
+ if (now - h.systemTime <= HISTORY_LENGTH_MILLIS) {
return true;
}
+ // otherwise remove the too old element, and look at the next (newer) one
history.removeFirst();
}
return false;
}
- private int counter = -1; // the latest counter value reported by the sensor
- private int actValue = 0;
- public static final int historyLengthMillis = 5000; // 5 sec
+ /**
+ * The latest counter value reported by the sensor
+ */
+ private int counter = -1;
+
+ /**
+ * The calculated instantaneous sensor value to be displayed
+ */
+ private int displayedValue = 0;
+
+ private static final int ONE_SECOND_MILLIS = 1000;
+ private static final int HISTORY_LENGTH_MILLIS = ONE_SECOND_MILLIS * 5;
+ private static final int ONE_MINUTE_MILLIS = ONE_SECOND_MILLIS * 60;
+ private static final int SENSOR_TIME_RESOLUTION = 1024; // in a second
+ private static final int SENSOR_TIME_ONE_MINUTE = SENSOR_TIME_RESOLUTION * 60;
+
+ /**
+ * History of previous sensor data - oldest first
+ * only the latest HISTORY_LENGTH_MILLIS milliseconds of data is stored
+ */
private LinkedList history = new LinkedList();
+ /**
+ * Calculates the instantaneous sensor value to be displayed
+ * using the history when the sensor only resends the old data
+ */
+ private int getValueFromHistory(int data, long now) {
+ if (!removeOldHistory(now)) {
+ // there is nothing in the history, return 0
+ return displayedValue = 0;
+ }
+ HistoryElement f = history.getFirst();
+ HistoryElement l = history.getLast();
+ int sensorTimeChange = (l.sensorTime - f.sensorTime) & 0xFFFF;
+ int counterChange = (data - f.data) & 0xFFFF;
+
+ // difference between now and systemTime of the oldest history entry
+ // for better precision sensor timestamps are considered between
+ // the first and the last history entry (could be overkill)
+ int systemTimeChange = (int) (now - l.systemTime
+ + (sensorTimeChange * ONE_SECOND_MILLIS) / SENSOR_TIME_RESOLUTION);
+
+ // displayedValue is not overwritten by this calculated value
+ // because it is still needed when a new sensor event arrives
+ int v = (counterChange * ONE_MINUTE_MILLIS) / systemTimeChange;
+ Log.d(TAG, "getValue returns (2):" + v);
+
+ // do not return larger number than displayedValue
+ return v < displayedValue ? v : displayedValue;
+ }
+
+ /**
+ * Calculates the instantaneous sensor value to be displayed
+ *
+ * @param data sensor reported counter value
+ * @param sensorTime sensor reported timestamp
+ * @return the calculated value
+ */
public int getValue(int data, int sensorTime) {
long now = System.currentTimeMillis();
- int dDelta = (data - counter) & 0xFFFF;
+ int counterChange = (data - counter) & 0xFFFF;
Log.d(TAG, "now=" + now + " data=" + data + " sensortime=" + sensorTime);
if (counter < 0) {
- // store the actual counter value from the sensor
+ // store the initial counter value reported by the sensor
+ // the timestamp is probably out of date, so the history is not updated
counter = data;
- return actValue = 0;
+ return displayedValue = 0;
}
counter = data;
- if (dDelta != 0) {
+ if (counterChange != 0) {
+ // if new data is arrived from the sensor ...
if (removeOldHistory(now)) {
+ // ... and the history is not empty, then use the latest entry
HistoryElement h = history.getLast();
- actValue = ((int) ((data - h.data) & 0xFFFF)) * 1024 * 60
- / (int) ((sensorTime - h.sensorTime) & 0xFFFF);
+ int sensorTimeChange = (sensorTime - h.sensorTime) & 0xFFFF;
+ counterChange = (data - h.data) & 0xFFFF;
+ displayedValue = counterChange * SENSOR_TIME_ONE_MINUTE / sensorTimeChange;
}
history.addLast(new HistoryElement(now, data, sensorTime));
} else if (!history.isEmpty()) {
- HistoryElement h = history.getLast();
- if (60000 < (now - h.sysTime) * actValue) {
- if (!removeOldHistory(now)) {
- actValue = 0;
- } else {
- HistoryElement f = history.getFirst();
- HistoryElement l = history.getLast();
- int sDelta = (l.sensorTime - f.sensorTime) & 0xFFFF;
- int cDelta = (data - f.data) & 0xFFFF;
+ // the sensor has resent an old (counter,timestamp) pair,
+ // but the history is not empty
- // the saved actValue is not overwritten by this
- // the returned value is computed from the history
- int v = (int) (cDelta * 60 * 1000 / (now - l.sysTime + (sDelta / 1024) * 1000));
- Log.d(TAG, "getValue returns (2):" + v);
- return v < actValue ? v : actValue;
- }
- } else {
- // the current actValue is still valid, nothing to do here
- }
+ HistoryElement h = history.getLast();
+ if (ONE_MINUTE_MILLIS < (now - h.systemTime) * displayedValue) {
+ // Too much time has passed since the last counter change.
+ // This means that a smaller value than displayedValue must be
+ // returned. So the history is looked up, and a calculated
+ // average number is returned.
+ // Note, that displayedValue is NOT updated unless the history
+ // is empty or contains outdated entries. In that case it is zeroed.
+ return getValueFromHistory(data, now);
+ } // else the current displayedValue is still valid, nothing to do here
} else {
- actValue = 0;
+ // no new data from the sensor & the history is empty -> return 0
+ displayedValue = 0;
}
- Log.d(TAG, "getValue returns:" + actValue);
- return actValue;
+ Log.d(TAG, "getValue returns:" + displayedValue);
+ return displayedValue;
}
}