mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-09-30 16:52:21 +02:00
@@ -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
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+26
-8
@@ -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);
|
||||
|
||||
+23
-5
@@ -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) {
|
||||
|
||||
+23
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user