Sensor: adding Running Speed and Cadence.

Fixes #891.
This commit is contained in:
Dennis Guse
2021-07-21 22:26:57 +02:00
parent eba7a7fd89
commit 242147e491
16 changed files with 295 additions and 17 deletions
+2
View File
@@ -79,6 +79,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
* cycling: speed and distance
* cycling: cadence
* cycling: power meter
* running: speed and cadence
An overview of tested sensors: [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md)
### Gadgetbridge integration
+5
View File
@@ -50,3 +50,8 @@ Also the distance is not computed.
* Wahoo Kickr v4.0
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
## RSC: running cadence and speed
We do not support type of movement (i.e., walking vs running).
* Polar Stride
@@ -4,7 +4,10 @@ import android.bluetooth.BluetoothGattCharacteristic;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -87,4 +90,18 @@ public class BluetoothUtilsTest {
// then
assertEquals(40, power_w);
}
@Test
public void parseRunningSpeedAndCadence_with_distance() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, 12, 1});
// when
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
// then
assertEquals(Speed.of(5), sensor.getSpeed());
assertEquals(80, sensor.getCadence(), 0.01);
assertEquals(Distance.of(268), sensor.getTotalDistance());
}
}
@@ -25,7 +25,15 @@ public class Distance {
}
public static Distance ofMM(double distance_mm) {
return of(distance_mm * UnitConversions.MM_TO_M);
return of(0.001 * distance_mm);
}
public static Distance ofCM(double distance_cm) {
return of(0.01 * distance_cm);
}
public static Distance ofDM(double distance_dm) {
return of(0.1 * distance_dm);
}
public static Distance one(boolean metricUnit) {
@@ -0,0 +1,109 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
/**
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
*/
public final class SensorDataRunning extends SensorData<SensorDataRunning.Data> {
private static final String TAG = SensorDataRunning.class.getSimpleName();
private final Speed speed;
private final Float cadence;
private final Distance totalDistance;
public SensorDataRunning(String sensorAddress) {
super(sensorAddress);
this.speed = null;
this.cadence = null;
this.totalDistance = null;
}
public SensorDataRunning(String sensorAddress, String sensorName, Speed speed, Float cadence, Distance totalDistance) {
super(sensorAddress, sensorName);
this.speed = speed;
this.cadence = cadence;
this.totalDistance = totalDistance;
}
private boolean hasTotalDistance() {
return totalDistance != null;
}
public Float getCadence() {
return cadence;
}
@VisibleForTesting
public Speed getSpeed() {
return speed;
}
@VisibleForTesting
public Distance getTotalDistance() {
return totalDistance;
}
public void compute(SensorDataRunning previous) {
Distance overallDistance = null;
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().getDistance() != null) {
overallDistance = overallDistance.plus(previous.getValue().getDistance());
}
}
value = new Data(speed, cadence, overallDistance);
}
@Override
public void reset() {
if (value != null) {
value = new Data(value.speed, value.cadence, Distance.of(0));
}
}
public static class Data {
private final Speed speed;
private final Float cadence;
@Nullable
private final Distance distance;
public Data(Speed speed, Float cadence, @Nullable Distance distance) {
this.speed = speed;
this.cadence = cadence;
this.distance = distance;
}
public Speed getSpeed() {
return speed;
}
public Float getCadence() {
return cadence;
}
public Distance getDistance() {
return distance;
}
@Override
public String toString() {
return "Data{" +
"speed=" + speed +
", cadence=" + cadence +
", distance=" + distance +
'}';
}
}
}
@@ -14,6 +14,8 @@ public final class SensorDataSet {
private SensorDataCyclingPower cyclingPower;
private SensorDataRunning runningDistanceSpeedCadence;
public SensorDataSet() {
}
@@ -22,6 +24,7 @@ public final class SensorDataSet {
this.cyclingCadence = toCopy.cyclingCadence;
this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed;
this.cyclingPower = toCopy.cyclingPower;
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
}
public SensorDataHeartRate getHeartRate() {
@@ -40,6 +43,10 @@ public final class SensorDataSet {
return cyclingPower;
}
public SensorDataRunning getRunningDistanceSpeedCadence() {
return runningDistanceSpeedCadence;
}
public void set(SensorData<?> data) {
set(data, data);
}
@@ -53,6 +60,7 @@ public final class SensorDataSet {
this.cyclingCadence = null;
this.cyclingDistanceSpeed = null;
this.cyclingPower = null;
this.runningDistanceSpeedCadence = null;
}
public void fillTrackPoint(TrackPoint trackPoint) {
@@ -72,6 +80,12 @@ public final class SensorDataSet {
if (cyclingPower != null && cyclingPower.hasValue()) {
trackPoint.setPower(cyclingPower.getValue());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
trackPoint.setSpeed(runningDistanceSpeedCadence.getValue().getSpeed());
trackPoint.setCyclingCadence_rpm(runningDistanceSpeedCadence.getValue().getCadence());
}
}
public void reset() {
@@ -79,6 +93,8 @@ public final class SensorDataSet {
if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
}
@NonNull
@@ -87,7 +103,8 @@ public final class SensorDataSet {
return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : "");
+ (getCyclingPower() != null ? " " + getCyclingPower() : "")
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
}
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
@@ -110,6 +127,11 @@ public final class SensorDataSet {
return;
}
if (type instanceof SensorDataRunning) {
this.runningDistanceSpeedCadence = (SensorDataRunning) data;
return;
}
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
}
}
@@ -34,13 +34,14 @@ import de.dennisguse.opentracks.content.sensor.SensorData;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.util.BluetoothUtils;
/**
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
* Also parses the transferred data into {@link SensorDataObserver}.
*/
public abstract class BluetoothConnectionManager {
public abstract class BluetoothConnectionManager<DataType> {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
@@ -103,7 +104,7 @@ public abstract class BluetoothConnectionManager {
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
SensorData<?> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
SensorData<DataType> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
observer.onChanged(sensorData);
@@ -148,14 +149,14 @@ public abstract class BluetoothConnectionManager {
return address.equals(bluetoothGatt.getDevice().getAddress());
}
protected abstract SensorData<?> createEmptySensorData(String address);
protected abstract SensorData<DataType> createEmptySensorData(String address);
/**
* @return null if data could not be parsed.
*/
protected abstract SensorData<?> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
protected abstract SensorData<DataType> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
public static class HeartRate extends BluetoothConnectionManager {
public static class HeartRate extends BluetoothConnectionManager<Float> {
HeartRate(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEART_RATE_SERVICE_UUID, BluetoothUtils.HEART_RATE_MEASUREMENT_CHAR_UUID, observer);
@@ -174,7 +175,7 @@ public abstract class BluetoothConnectionManager {
}
}
public static class CyclingCadence extends BluetoothConnectionManager {
public static class CyclingCadence extends BluetoothConnectionManager<Float> {
CyclingCadence(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
@@ -205,7 +206,7 @@ public abstract class BluetoothConnectionManager {
}
}
public static class CyclingDistanceSpeed extends BluetoothConnectionManager {
public static class CyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCycling.DistanceSpeed.Data> {
CyclingDistanceSpeed(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
@@ -232,7 +233,7 @@ public abstract class BluetoothConnectionManager {
}
}
public static class CyclingPower extends BluetoothConnectionManager {
public static class CyclingPower extends BluetoothConnectionManager<Float> {
CyclingPower(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_POWER_UUID, BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, observer);
@@ -251,6 +252,23 @@ public abstract class BluetoothConnectionManager {
}
}
public static class RunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
RunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID, BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, observer);
}
@Override
protected SensorDataRunning createEmptySensorData(String address) {
return new SensorDataRunning(address);
}
@Override
protected SensorDataRunning parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
return BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic);
}
}
interface SensorDataObserver {
void onChanged(SensorData<?> sensorData);
@@ -31,6 +31,7 @@ import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorData;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -67,6 +68,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this);
private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this);
private final BluetoothConnectionManager.RunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionManager.RunningSpeedAndCadence(this);
private final SensorDataSet sensorDataSet = new SensorDataSet();
@@ -85,11 +87,14 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
connect(cyclingCadence, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
connect(cyclingSpeed, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
@@ -97,8 +102,11 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
connect(cyclingPower, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress(sharedPreferences, context);
connect(runningSpeedAndCadence, address);
}
}
};
@@ -122,6 +130,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
cyclingCadence.disconnect();
cyclingSpeed.disconnect();
cyclingPower.disconnect();
runningSpeedAndCadence.disconnect();
sensorDataSet.clear();
@@ -133,7 +142,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled.");
return;
@@ -186,11 +195,20 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: speed data repeated.");
Log.d(TAG, "onChanged: cycling speed data repeated.");
return;
}
((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, preferenceWheelCircumference);
}
if (sensorData instanceof SensorDataRunning) {
SensorDataRunning previous = sensorDataSet.getRunningDistanceSpeedCadence();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: running speed data repeated.");
return;
}
((SensorDataRunning) sensorData).compute(previous);
}
sensorDataSet.set(sensorData);
}
@@ -31,6 +31,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.BluetoothUtils;
@@ -252,6 +253,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID);
} else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID);
}
if (dialogFragment != null) {
@@ -0,0 +1,23 @@
package de.dennisguse.opentracks.settings.bluetooth;
import android.content.Context;
import android.util.AttributeSet;
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
public BluetoothLeRunningSpeedAndCadencePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BluetoothLeRunningSpeedAndCadencePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BluetoothLeRunningSpeedAndCadencePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BluetoothLeRunningSpeedAndCadencePreference(Context context) {
super(context);
}
}
@@ -28,7 +28,10 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
/**
* Utilities for dealing with bluetooth devices.
@@ -54,6 +57,9 @@ public class BluetoothUtils {
public static final UUID CYCLING_SPEED_CADENCE_SERVICE_UUID = new UUID(0x181600001000L, 0x800000805f9b34fbL);
public static final UUID CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID = new UUID(0x2A5B00001000L, 0x800000805f9b34fbL);
public static final UUID RUNNING_RUNNING_SPEED_CADENCE_UUID = new UUID(0x181400001000L, 0x800000805f9b34fbL);
public static final UUID RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID = new UUID(0x2A5300001000L, 0x800000805f9b34fbL);
private static final String TAG = BluetoothUtils.class.getSimpleName();
private BluetoothUtils() {
@@ -136,4 +142,43 @@ public class BluetoothUtils {
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
}
public static SensorDataRunning parseRunningSpeedAndCadence(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.rsc_measurement.xml
int valueLength = characteristic.getValue().length;
if (valueLength == 0) {
return null;
}
int flags = characteristic.getValue()[0];
boolean hasStrideLength = (flags & 0x01) > 0;
boolean hasTotalDistance = (flags & 0x02) > 0;
boolean hasStatus = (flags & 0x03) > 0; // walking vs running
Speed speed = null;
Float cadence = null;
Distance totalDistance = null;
int index = 1;
if (valueLength - index >= 2) {
speed = Speed.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index) / 256f);
}
index = 3;
if (valueLength - index >= 1) {
cadence = Float.valueOf(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, index));
}
index = 4;
if (hasStrideLength && valueLength - index >= 2) {
Distance strideDistance = Distance.ofCM(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index));
index += 2;
}
if (hasTotalDistance && valueLength - index >= 2) {
totalDistance = Distance.ofDM(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index));
}
return new SensorDataRunning(address, sensorName, speed, cadence, totalDistance);
}
}
@@ -177,6 +177,10 @@ public class PreferencesUtils {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothRunningSpeedAndCadenceAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, getBluetoothSensorAddressNone(context));
}
public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(sharedPreferences, context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
@@ -42,8 +42,6 @@ public class UnitConversions {
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
public static final double MM_TO_M = 0.001;
// Distance //TODO Make private to Distance class!
// multiplication factor to convert miles to feet
private static final double MI_TO_FT = 5280.0;
+1
View File
@@ -22,6 +22,7 @@
<string name="settings_sensor_bluetooth_cycling_cadence_key" translatable="false">bluetoothCyclingCadenceSensor</string>
<string name="settings_sensor_bluetooth_cycling_speed_key" translatable="false">bluetoothCyclingSpeedSensor</string>
<string name="settings_sensor_bluetooth_cycling_power_key" translatable="false">bluetoothCyclingPowerSensor</string>
<string name="settings_sensor_bluetooth_running_speed_and_cadence_key" translatable="false">bluetoothRunningSpeedAndCadenceSensor</string>
<string name="sensor_type_value_none" translatable="false">NONE</string>
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_key" translatable="false">bluetoothCyclingSpeedWheelCircumference</string>
+1
View File
@@ -369,6 +369,7 @@ limitations under the License.
<string name="settings_sensor_cycling_distance_speed">Cycling: distance and speed</string>
<string name="settings_sensor_cycling_cadence">Cycling: cadence</string>
<string name="settings_sensor_running_speed_and_cadence">Running: distance, speed, and cadence</string>
<string name="settings_sensor_wheel_circumference">Wheel circumference (mm)</string>
<!-- Settings Stats -->
+4
View File
@@ -138,6 +138,10 @@ limitations under the License.
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_power_key"
android:title="@string/sensor_state_power" />
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_running_speed_and_cadence_key"
android:title="@string/settings_sensor_running_speed_and_cadence" />
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_speed_key"