From 634a7a4321202705aefe569e1a273abab6f4e935 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Thu, 1 Oct 2020 17:55:33 +0200 Subject: [PATCH] Bluetooth Sensor: add cycling powermeter. --- README_TESTED_SENSORS.md | 10 +++++ .../opentracks/util/BluetoothUtilsTest.java | 12 ++++++ .../sensor/SensorDataCyclingPower.java | 32 ++++++++++++++ .../content/sensor/SensorDataSet.java | 23 ++++++++-- .../StatisticsRecordingFragment.java | 40 ++++++++++++++---- .../sensors/BluetoothConnectionManager.java | 30 ++++++++++++- .../sensors/BluetoothRemoteSensorManager.java | 9 ++++ .../opentracks/settings/SettingsActivity.java | 3 ++ .../BluetoothLeCyclingPowerPreference.java | 23 ++++++++++ .../opentracks/util/BluetoothUtils.java | 18 +++++++- .../opentracks/util/PreferencesUtils.java | 9 ++++ src/main/res/layout/statistics_recording.xml | 42 +++++++++++++++++++ src/main/res/values/colors.xml | 4 +- src/main/res/values/do_not_translate.xml | 1 + src/main/res/values/settings.xml | 1 + src/main/res/xml/settings.xml | 4 ++ 16 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java create mode 100644 src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingPowerPreference.java diff --git a/README_TESTED_SENSORS.md b/README_TESTED_SENSORS.md index 58f8dc3d0..3e6437c08 100644 --- a/README_TESTED_SENSORS.md +++ b/README_TESTED_SENSORS.md @@ -26,3 +26,13 @@ However, often only one value is provided. * Wahoo Cadence (Model: WFPODCAD2) This sensor reports cadence data as speed. A workaround is in place. + +## Cycling Power Measurement + +We only support _Instantaneous Power_. + +* QUARQ Red DZero Powermeter +* Wahoo Kickr v4.0 + WARNING: we only support physically movements with valid GPS coordinates. + Using a device indoors (no GPS available) will only show the current power values, but won't store anything. + Also the distance is not computed. \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java index 30cf4dcc1..5137e9f6d 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java @@ -75,4 +75,16 @@ public class BluetoothUtilsTest { assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); } + + @Test + public void parseCyclingPower_power() { + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, 0, 0); + characteristic.setValue(new byte[]{0, 0, 40, 0}); + + // when + int power_w = BluetoothUtils.parseCyclingPower(characteristic); + + // then + assertEquals(40, power_w); + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java new file mode 100644 index 000000000..705f58695 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java @@ -0,0 +1,32 @@ +package de.dennisguse.opentracks.content.sensor; + +import androidx.annotation.NonNull; + +public class SensorDataCyclingPower extends SensorData { + + private final Float power_w; + + public SensorDataCyclingPower(String address) { + super(address); + this.power_w = null; + } + + public SensorDataCyclingPower(String name, String address, float power_w) { + super(name, address); + this.power_w = power_w; + } + + public boolean hasPower_w() { + return power_w != null; + } + + public float getPower_w() { + return power_w; + } + + @NonNull + @Override + public String toString() { + return "power=" + power_w; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java index 9610f970a..7d9bd402c 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java @@ -12,6 +12,8 @@ public final class SensorDataSet { private SensorDataCycling.Speed cyclingSpeed; + private SensorDataCyclingPower cyclingPower; + public SensorDataSet() { } @@ -27,6 +29,10 @@ public final class SensorDataSet { return cyclingSpeed; } + public SensorDataCyclingPower getCyclingPower() { + return cyclingPower; + } + public void set(SensorData data) { set(data, data); } @@ -39,6 +45,7 @@ public final class SensorDataSet { this.heartRate = null; this.cyclingCadence = null; this.cyclingSpeed = null; + this.cyclingPower = null; } public void fillTrackPoint(TrackPoint trackPoint) { @@ -53,6 +60,10 @@ public final class SensorDataSet { if (cyclingSpeed != null && cyclingSpeed.hasSpeed_mps()) { trackPoint.setSpeed(cyclingSpeed.getSpeed_mps()); } + + if (cyclingPower != null && cyclingPower.hasPower_w()) { + trackPoint.setPower(cyclingPower.getPower_w()); + } } @NonNull @@ -60,10 +71,11 @@ public final class SensorDataSet { public String toString() { return (getHeartRate() != null ? "" + getHeartRate() : "") + (getCyclingCadence() != null ? " " + getCyclingCadence() : "") - + (getCyclingSpeed() != null ? " " + getCyclingSpeed() : ""); + + (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "") + + (getCyclingPower() != null ? " " + getCyclingPower() : ""); } - private void set(SensorData type, SensorData data) { + private void set(@NonNull SensorData type, SensorData data) { if (type instanceof SensorDataHeartRate) { this.heartRate = (SensorDataHeartRate) data; return; @@ -78,6 +90,11 @@ public final class SensorDataSet { return; } - throw new UnsupportedOperationException(); + if (type instanceof SensorDataCyclingPower) { + this.cyclingPower = (SensorDataCyclingPower) data; + return; + } + + throw new UnsupportedOperationException(type.getClass().getCanonicalName()); } } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index 52d236ca0..c801de5bc 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -22,6 +22,7 @@ import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; 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.SensorDataSet; import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding; @@ -273,9 +274,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi if (sensorDataSet != null) { setHeartRateSensorData(sensorDataSet); setCadenceSensorData(sensorDataSet); - setSpeedSensorData(sensorDataSet, isSelectedTrackRecording()); + setPowerSensorData(sensorDataSet); + setSpeedSensorData(sensorDataSet); } - setTotalElevationGain(trackRecordingService.getElevationGain_m()); } } @@ -320,6 +321,29 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi viewBinding.statsSensorCadenceValue.setText(sensorValue); } + private void setPowerSensorData(SensorDataSet sensorDataSet) { + int isVisible = View.VISIBLE; + if (PreferencesUtils.isBluetoothCyclingPowerSensorAddressNone(getContext())) { + isVisible = View.GONE; + } + viewBinding.statsSensorPowerGroup.setVisibility(isVisible); + setVisibilitySensorHorizontalLine(); + + String sensorValue = getContext().getString(R.string.value_unknown); + String sensorName = getContext().getString(R.string.value_unknown); + if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) { + SensorDataCyclingPower data = sensorDataSet.getCyclingPower(); + sensorName = data.getSensorName(); + + if (data.hasPower_w() && data.isRecent()) { + sensorValue = StringUtils.formatDecimal(data.getPower_w(), 0); + } + } + + viewBinding.statsSensorPowerSensorValue.setText(sensorName); + viewBinding.statsSensorPowerValue.setText(sensorValue); + } + // Set elevation gain private void setTotalElevationGain(Float elevationGain_m) { //TODO Check if we can distribute the total elevation gain via trackStatistics instead of doing some computation in the UI layer. @@ -349,14 +373,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi } } - private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) { - if (isRecording) { - if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) { - SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed(); + private void setSpeedSensorData(SensorDataSet sensorDataSet) { + if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) { + SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed(); - if (data.hasSpeed_mps() && data.isRecent()) { - setSpeed(data.getSpeed_mps()); - } + if (data.hasSpeed_mps() && data.isRecent()) { + setSpeed(data.getSpeed_mps()); } } } diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java index df2a92e48..671ec09ad 100644 --- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java @@ -32,6 +32,7 @@ import java.util.UUID; 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.util.BluetoothUtils; @@ -88,8 +89,14 @@ public abstract class BluetoothConnectionManager { // Register for updates. BluetoothGattDescriptor descriptor = characteristic.getDescriptor(BluetoothUtils.CLIENT_CHARACTERISTIC_CONFIG_UUID); + if (descriptor == null) { + Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID characteristic not available; cannot request notifications for changed data."); + return; + } + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); + } @Override @@ -170,7 +177,7 @@ public abstract class BluetoothConnectionManager { public static class CyclingCadence extends BluetoothConnectionManager { CyclingCadence(SensorDataObserver observer) { - super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPPED_CADENCE_MEASUREMENT_CHAR_UUID, observer); + super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer); } @Override @@ -201,7 +208,7 @@ public abstract class BluetoothConnectionManager { public static class CyclingSpeed extends BluetoothConnectionManager { CyclingSpeed(SensorDataObserver observer) { - super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPPED_CADENCE_MEASUREMENT_CHAR_UUID, observer); + super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer); } @Override @@ -219,6 +226,25 @@ public abstract class BluetoothConnectionManager { } } + public static class CyclingPower extends BluetoothConnectionManager { + + CyclingPower(@NonNull SensorDataObserver observer) { + super(BluetoothUtils.CYCLING_POWER_UUID, BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, observer); + } + + @Override + protected SensorData createEmptySensorData(String address) { + return new SensorDataCyclingPower(address); + } + + @Override + protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) { + Integer cyclingPower = BluetoothUtils.parseCyclingPower(characteristic); + + return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, cyclingPower) : null; + } + } + interface SensorDataObserver { void onChanged(SensorData sensorData); diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java index 46547b7b1..9caba26ff 100644 --- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java @@ -59,6 +59,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this); private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this); private final BluetoothConnectionManager.CyclingSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingSpeed(this); + private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this); private final SensorDataSet sensorDataSet = new SensorDataSet(); @@ -82,6 +83,12 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. connect(cyclingSpeed, address); } + + if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) { + String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(context); + + connect(cyclingPower, address); + } } }; @@ -102,6 +109,8 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. public synchronized void stop() { heartRate.disconnect(); cyclingCadence.disconnect(); + cyclingSpeed.disconnect(); + cyclingPower.disconnect(); sensorDataSet.clear(); diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index e22e19f8a..81652c04f 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -20,6 +20,7 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.io.file.TrackFileFormat; 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.BluetoothLeSensorPreference; import de.dennisguse.opentracks.util.ActivityUtils; @@ -132,6 +133,8 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SERVICE_UUID); } else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) { 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); } if (dialogFragment != null) { diff --git a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingPowerPreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingPowerPreference.java new file mode 100644 index 000000000..486136bea --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingPowerPreference.java @@ -0,0 +1,23 @@ +package de.dennisguse.opentracks.settings.bluetooth; + +import android.content.Context; +import android.util.AttributeSet; + +public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference { + + public BluetoothLeCyclingPowerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public BluetoothLeCyclingPowerPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public BluetoothLeCyclingPowerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BluetoothLeCyclingPowerPreference(Context context) { + super(context); + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java b/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java index 9c2dd5c22..37a5aab09 100644 --- a/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java @@ -39,8 +39,11 @@ public class BluetoothUtils { public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL); public static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL); + public static final UUID CYCLING_POWER_UUID = new UUID(0x181800001000L, 0x800000805f9b34fbL); + public static final UUID CYCLING_POWER_MEASUREMENT_CHAR_UUID = new UUID(0x2A6300001000L, 0x800000805f9b34fbL); + public static final UUID CYCLING_SPEED_CADENCE_SERVICE_UUID = new UUID(0x181600001000L, 0x800000805f9b34fbL); - public static final UUID CYCLING_SPPED_CADENCE_MEASUREMENT_CHAR_UUID = new UUID(0x2A5B00001000L, 0x800000805f9b34fbL); + public static final UUID CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID = new UUID(0x2A5B00001000L, 0x800000805f9b34fbL); private static final String TAG = BluetoothUtils.class.getSimpleName(); @@ -62,7 +65,7 @@ public class BluetoothUtils { } public static Integer parseHeartRate(BluetoothGattCharacteristic characteristic) { - //DOCUMENTATION https://www.bluetooth.com/specifications/gatt/characteristics/ + //DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml byte[] raw = characteristic.getValue(); if (raw.length == 0) { return null; @@ -79,10 +82,21 @@ public class BluetoothUtils { return null; } + public static Integer parseCyclingPower(BluetoothGattCharacteristic characteristic) { + // DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.cycling_power_measurement.xml + int valueLength = characteristic.getValue().length; + if (valueLength < 4) { + return null; + } + + return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2); + } + /** * Documentation: https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=261449 */ public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) { + // DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml int valueLength = characteristic.getValue().length; if (valueLength == 0) { return null; diff --git a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java index 1fa437942..9b00dcece 100644 --- a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java @@ -262,6 +262,15 @@ public class PreferencesUtils { return getInt(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT); } + public static boolean isBluetoothCyclingPowerSensorAddressNone(Context context) { + return isBluetoothSensorAddressNone(context, getBluetoothCyclingPowerSensorAddress(context)); + } + + public static String getBluetoothCyclingPowerSensorAddress(Context context) { + return getString(context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context)); + } + + public static boolean shouldShowStatsOnLockscreen(Context context) { final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default); return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT); diff --git a/src/main/res/layout/statistics_recording.xml b/src/main/res/layout/statistics_recording.xml index ebc79b33e..94d8b1b48 100644 --- a/src/main/res/layout/statistics_recording.xml +++ b/src/main/res/layout/statistics_recording.xml @@ -487,5 +487,47 @@ app:layout_constraintRight_toLeftOf="@+id/guideline3" app:layout_constraintTop_toBottomOf="@+id/stats_sensor_cadence_value" /> + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 0bdbefe06..e3403afa0 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -22,10 +22,10 @@ limitations under the License. #40E88A70 #FF0066FF #40C8E0FF - #FF401465 - #40401465 #FF0066FF #40C8E0FF + #FF7A26C0 + #407A26C0 @android:color/holo_orange_dark #55FF8800 diff --git a/src/main/res/values/do_not_translate.xml b/src/main/res/values/do_not_translate.xml index 4cd700015..e29c1cbd8 100644 --- a/src/main/res/values/do_not_translate.xml +++ b/src/main/res/values/do_not_translate.xml @@ -26,6 +26,7 @@ limitations under the License. BPM RPM + W - diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 515154264..ad77abd22 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -21,6 +21,7 @@ bluetoothSensor bluetoothCyclingCadenceSensor bluetoothCyclingSpeedSensor + bluetoothCyclingPowerSensor NONE bluetoothCyclingSpeedWheelCircumference diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml index 8e23c55ab..e792e4d08 100644 --- a/src/main/res/xml/settings.xml +++ b/src/main/res/xml/settings.xml @@ -131,6 +131,10 @@ limitations under the License. android:defaultValue="@string/sensor_type_value_none" android:key="@string/settings_sensor_bluetooth_cycling_cadence_key" android:title="@string/sensor_state_cadence" /> +