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: speed and distance
* cycling: cadence * cycling: cadence
* cycling: power meter * cycling: power meter
* running: speed and cadence
An overview of tested sensors: [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md)
### Gadgetbridge integration ### Gadgetbridge integration
+5
View File
@@ -50,3 +50,8 @@ Also the distance is not computed.
* Wahoo Kickr v4.0 * Wahoo Kickr v4.0
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061) * 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 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.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@@ -87,4 +90,18 @@ public class BluetoothUtilsTest {
// then // then
assertEquals(40, power_w); 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) { 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) { 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 SensorDataCyclingPower cyclingPower;
private SensorDataRunning runningDistanceSpeedCadence;
public SensorDataSet() { public SensorDataSet() {
} }
@@ -22,6 +24,7 @@ public final class SensorDataSet {
this.cyclingCadence = toCopy.cyclingCadence; this.cyclingCadence = toCopy.cyclingCadence;
this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed; this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed;
this.cyclingPower = toCopy.cyclingPower; this.cyclingPower = toCopy.cyclingPower;
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
} }
public SensorDataHeartRate getHeartRate() { public SensorDataHeartRate getHeartRate() {
@@ -40,6 +43,10 @@ public final class SensorDataSet {
return cyclingPower; return cyclingPower;
} }
public SensorDataRunning getRunningDistanceSpeedCadence() {
return runningDistanceSpeedCadence;
}
public void set(SensorData<?> data) { public void set(SensorData<?> data) {
set(data, data); set(data, data);
} }
@@ -53,6 +60,7 @@ public final class SensorDataSet {
this.cyclingCadence = null; this.cyclingCadence = null;
this.cyclingDistanceSpeed = null; this.cyclingDistanceSpeed = null;
this.cyclingPower = null; this.cyclingPower = null;
this.runningDistanceSpeedCadence = null;
} }
public void fillTrackPoint(TrackPoint trackPoint) { public void fillTrackPoint(TrackPoint trackPoint) {
@@ -72,6 +80,12 @@ public final class SensorDataSet {
if (cyclingPower != null && cyclingPower.hasValue()) { if (cyclingPower != null && cyclingPower.hasValue()) {
trackPoint.setPower(cyclingPower.getValue()); 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() { public void reset() {
@@ -79,6 +93,8 @@ public final class SensorDataSet {
if (cyclingCadence != null) cyclingCadence.reset(); if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset(); if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset(); if (cyclingPower != null) cyclingPower.reset();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
} }
@NonNull @NonNull
@@ -87,7 +103,8 @@ public final class SensorDataSet {
return (getHeartRate() != null ? "" + getHeartRate() : "") return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "") + (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "") + (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : ""); + (getCyclingPower() != null ? " " + getCyclingPower() : "")
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
} }
private void set(@NonNull SensorData<?> type, SensorData<?> data) { private void set(@NonNull SensorData<?> type, SensorData<?> data) {
@@ -110,6 +127,11 @@ public final class SensorDataSet {
return; return;
} }
if (type instanceof SensorDataRunning) {
this.runningDistanceSpeedCadence = (SensorDataRunning) data;
return;
}
throw new UnsupportedOperationException(type.getClass().getCanonicalName()); 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.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower; import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate; import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.util.BluetoothUtils; import de.dennisguse.opentracks.util.BluetoothUtils;
/** /**
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications. * Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
* Also parses the transferred data into {@link SensorDataObserver}. * 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(); private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
@@ -103,7 +104,7 @@ public abstract class BluetoothConnectionManager {
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) { public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress()); 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) { if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData); Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
observer.onChanged(sensorData); observer.onChanged(sensorData);
@@ -148,14 +149,14 @@ public abstract class BluetoothConnectionManager {
return address.equals(bluetoothGatt.getDevice().getAddress()); 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. * @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) { HeartRate(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEART_RATE_SERVICE_UUID, BluetoothUtils.HEART_RATE_MEASUREMENT_CHAR_UUID, 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) { CyclingCadence(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 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) { CyclingDistanceSpeed(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 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) { CyclingPower(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_POWER_UUID, BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, 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 { interface SensorDataObserver {
void onChanged(SensorData<?> sensorData); 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.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorData; import de.dennisguse.opentracks.content.sensor.SensorData;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling; 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.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils; import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; 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.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this); private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this);
private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(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(); private final SensorDataSet sensorDataSet = new SensorDataSet();
@@ -85,11 +87,14 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
connect(cyclingCadence, address); 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); String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
connect(cyclingSpeed, address); 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)) { if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context); String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
@@ -97,8 +102,11 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
connect(cyclingPower, address); 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(); cyclingCadence.disconnect();
cyclingSpeed.disconnect(); cyclingSpeed.disconnect();
cyclingPower.disconnect(); cyclingPower.disconnect();
runningSpeedAndCadence.disconnect();
sensorDataSet.clear(); sensorDataSet.clear();
@@ -133,7 +142,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
} }
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) { private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
if (!isEnabled()) { if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled."); Log.w(TAG, "Bluetooth not enabled.");
return; return;
@@ -186,11 +195,20 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed(); SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData); Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) { if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: speed data repeated."); Log.d(TAG, "onChanged: cycling speed data repeated.");
return; return;
} }
((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, preferenceWheelCircumference); ((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); 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.BluetoothLeCyclingCadenceAndSpeedPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference; import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference; 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.settings.bluetooth.BluetoothLeSensorPreference;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.BluetoothUtils; 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); dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
} else if (preference instanceof BluetoothLeCyclingPowerPreference) { } else if (preference instanceof BluetoothLeCyclingPowerPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID); 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) { 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.List;
import java.util.UUID; 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.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
/** /**
* Utilities for dealing with bluetooth devices. * 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_SERVICE_UUID = new UUID(0x181600001000L, 0x800000805f9b34fbL);
public static final UUID CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID = new UUID(0x2A5B00001000L, 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 static final String TAG = BluetoothUtils.class.getSimpleName();
private BluetoothUtils() { private BluetoothUtils() {
@@ -136,4 +142,43 @@ public class BluetoothUtils {
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed); 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)); 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) { 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); 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); 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 // multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192; 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! // Distance //TODO Make private to Distance class!
// multiplication factor to convert miles to feet // multiplication factor to convert miles to feet
private static final double MI_TO_FT = 5280.0; 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_cadence_key" translatable="false">bluetoothCyclingCadenceSensor</string>
<string name="settings_sensor_bluetooth_cycling_speed_key" translatable="false">bluetoothCyclingSpeedSensor</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_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="sensor_type_value_none" translatable="false">NONE</string>
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_key" translatable="false">bluetoothCyclingSpeedWheelCircumference</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_distance_speed">Cycling: distance and speed</string>
<string name="settings_sensor_cycling_cadence">Cycling: cadence</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> <string name="settings_sensor_wheel_circumference">Wheel circumference (mm)</string>
<!-- Settings Stats --> <!-- Settings Stats -->
+4
View File
@@ -138,6 +138,10 @@ limitations under the License.
android:defaultValue="@string/sensor_type_value_none" android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_power_key" android:key="@string/settings_sensor_bluetooth_cycling_power_key"
android:title="@string/sensor_state_power" /> 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 <de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference
android:defaultValue="@string/sensor_type_value_none" android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_speed_key" android:key="@string/settings_sensor_bluetooth_cycling_speed_key"