forked from upstream-mirrors/OpenTracks
Merge pull request #1767 from OpenTracksApp/barometer2#1424
Support for BLE Environmental Sensing (Pressure/Barometer).
This commit is contained in:
+1
-1
@@ -586,7 +586,7 @@ public class ExportImportTest {
|
||||
Mockito.when(barometer.getValue()).thenReturn(new AltitudeGainLoss(altitudeGain, altitudeGain));
|
||||
sensorDataSet.add(barometer);
|
||||
} else {
|
||||
sensorDataSet.add(new AggregatorBarometer(""));
|
||||
sensorDataSet.add(new AggregatorBarometer("test", null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class AggregatorBarometerTest {
|
||||
@Test
|
||||
public void getAltitudeGainLoss_downhill() {
|
||||
// given
|
||||
AggregatorBarometer subject = new AggregatorBarometer("");
|
||||
AggregatorBarometer subject = new AggregatorBarometer("test", null);
|
||||
|
||||
// then
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
@@ -33,19 +34,22 @@ import androidx.annotation.NonNull;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
/**
|
||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public class BluetoothConnectionManager {
|
||||
public class BluetoothConnectionManager implements Driver {
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver observer;
|
||||
|
||||
private final SensorHandlerInterface sensorHandler;
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
|
||||
@Override
|
||||
@@ -101,9 +105,12 @@ public class BluetoothConnectionManager {
|
||||
return;
|
||||
}
|
||||
|
||||
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
gatt.writeDescriptor(descriptor);
|
||||
|
||||
if (!descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
|
||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID could not be set to ENABLE_NOTIFICATION_VALUE");
|
||||
}
|
||||
if (!gatt.writeDescriptor(descriptor)) {
|
||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,17 +131,46 @@ public class BluetoothConnectionManager {
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager(SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
||||
BluetoothConnectionManager(BluetoothAdapter bluetoothAdapter, SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
||||
this.bluetoothAdapter = bluetoothAdapter;
|
||||
this.observer = observer;
|
||||
this.sensorHandler = sensorHandler;
|
||||
}
|
||||
|
||||
synchronized void connect(Context context, Handler handler, @NonNull BluetoothDevice device) {
|
||||
if (bluetoothGatt != null) {
|
||||
@Override
|
||||
public synchronized void connect(Context context, Handler handler, @NonNull String address) {
|
||||
if (!isBluetoothEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SensorType.NONE.getPreferenceValue().equals(address)) {
|
||||
Log.w(TAG, "NONE: going to disconnect");
|
||||
if (isConnected()) {
|
||||
disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConnected()) {
|
||||
Log.w(TAG, "Already connected; ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
BluetoothDevice device;
|
||||
try {
|
||||
device = bluetoothAdapter.getRemoteDevice(address);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, address + ": Unable to get remote device for", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, device + ": trying to connect");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
|
||||
@@ -146,8 +182,10 @@ public class BluetoothConnectionManager {
|
||||
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName()));
|
||||
}
|
||||
|
||||
synchronized void disconnect() {
|
||||
if (bluetoothGatt == null) {
|
||||
@Override
|
||||
public synchronized void disconnect() {
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "Not connected; no need to re-connect.");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
|
||||
@@ -158,11 +196,21 @@ public class BluetoothConnectionManager {
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
synchronized boolean isSameBluetoothDevice(String address) {
|
||||
private synchronized boolean isSameBluetoothDevice(String address) {
|
||||
if (bluetoothGatt == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address.equals(bluetoothGatt.getDevice().getAddress());
|
||||
}
|
||||
|
||||
|
||||
private boolean isBluetoothEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return bluetoothGatt != null;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -1,12 +1,15 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
||||
|
||||
public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface {
|
||||
@@ -23,14 +26,22 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
|
||||
|
||||
@Override
|
||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
||||
return null; //TODO
|
||||
return new AggregatorBarometer(address, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
//TODO
|
||||
AtmosphericPressure value = parseEnvironmentalSensing(characteristic);
|
||||
if (value == null) return;
|
||||
Log.i("ZXCV", "VALUE: " + value);
|
||||
observer.onChange(new Raw<>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoding:
|
||||
* org.bluetooth.service.environmental_sensing.xml
|
||||
* org.bluetooth.characteristic.pressure.xml
|
||||
*/
|
||||
public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -30,29 +28,20 @@ import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
/**
|
||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
||||
* <p>
|
||||
* Note: should only be instantiated once.
|
||||
* <p>
|
||||
* TODO: listen for Bluetooth enabled/disabled events.
|
||||
* <p>
|
||||
* TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
|
||||
* We establish two GATT separate GATT connections (as if two different sensors were used).
|
||||
* However, it is not clear if this is allowed.
|
||||
* Even if this works, it is not clear what happens if a user (while recording) changes one of the sensors in the settings as this will trigger a disconnect of one GATT.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothRemoteSensorManager implements SensorConnector, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(50); //TODO
|
||||
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
||||
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private final Context context;
|
||||
private final Handler handler;
|
||||
private boolean started = false;
|
||||
@@ -66,14 +55,13 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
|
||||
this.heartRate = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerHeartRate());
|
||||
this.cyclingCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingCadence());
|
||||
this.cyclingSpeed = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingPower = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerCyclingPower());
|
||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerRunningSpeedAndCadence());
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerHeartRate());
|
||||
this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence());
|
||||
this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed());
|
||||
this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerCyclingPower());
|
||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerRunningSpeedAndCadence());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,39 +83,8 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
|
||||
started = false;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
|
||||
if (!isEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isBluetoothSensorAddressNone(address)) {
|
||||
Log.w(TAG, "No Bluetooth address.");
|
||||
connectionManager.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is an ongoing connection; if yes, check if the address changed.
|
||||
if (connectionManager.isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
} else {
|
||||
connectionManager.disconnect();
|
||||
}
|
||||
if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
|
||||
Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
|
||||
}
|
||||
|
||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||
try {
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
|
||||
connectionManager.connect(context, handler, device);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to get remote device for: " + address, e);
|
||||
}
|
||||
connectionManager.connect(context, handler, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
@@ -16,31 +17,61 @@ public class GainManager implements SensorConnector {
|
||||
|
||||
private static final String TAG = GainManager.class.getSimpleName();
|
||||
|
||||
private final BarometerInternal driver;
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
connect();
|
||||
};
|
||||
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
private Driver driver;
|
||||
|
||||
public GainManager(SensorManager.SensorDataChangedObserver listener) {
|
||||
this.listener = listener;
|
||||
driver = new BarometerInternal();
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
driver.connect(context, handler, this);
|
||||
|
||||
if (driver.isConnected()) {
|
||||
listener.onConnect(new AggregatorBarometer("internal"));
|
||||
}
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
Log.d(TAG, "Stop");
|
||||
this.context = null;
|
||||
this.handler = null;
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
driver.disconnect(context);
|
||||
listener.onDisconnect(new AggregatorBarometer("internal"));
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
public void onSensorValueChanged(AtmosphericPressure currentSensorValue) {
|
||||
listener.onChange(new Raw<>(currentSensorValue));
|
||||
private void connect() {
|
||||
onDisconnect();
|
||||
|
||||
String address = PreferencesUtils.getBarometerSensorAddress();
|
||||
switch (PreferencesUtils.getSensorType(address)) {
|
||||
case NONE -> driver = null;
|
||||
case INTERNAL -> driver = new BarometerInternal(listener);
|
||||
case REMOTE -> driver =
|
||||
new BluetoothConnectionManager(
|
||||
BluetoothUtils.getAdapter(context),
|
||||
listener,
|
||||
new BluetoothHandlerBarometricPressure()
|
||||
);
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
if (driver != null) {
|
||||
driver.connect(context, handler, address);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
if (driver == null) return;
|
||||
|
||||
driver.disconnect();
|
||||
listener.onDisconnect(new AggregatorBarometer("GainManager", null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
public enum SensorType {
|
||||
NONE("NONE"),
|
||||
INTERNAL("INTERNAL"),
|
||||
REMOTE("*");
|
||||
|
||||
private final String preferenceValue;
|
||||
|
||||
SensorType(String preferenceValue) {
|
||||
this.preferenceValue = preferenceValue;
|
||||
}
|
||||
|
||||
public String getPreferenceValue() {
|
||||
return preferenceValue;
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,29 @@ import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.sensors.GainManager;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
|
||||
public class BarometerInternal {
|
||||
public class BarometerInternal implements Driver {
|
||||
|
||||
private static final String TAG = BarometerInternal.class.getSimpleName();
|
||||
|
||||
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
||||
|
||||
private GainManager observer;
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
private final SensorEventListener listener = new SensorEventListener() {
|
||||
private Context context;
|
||||
|
||||
private final SensorEventListener sensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (!isConnected()) {
|
||||
@@ -29,7 +34,7 @@ public class BarometerInternal {
|
||||
return;
|
||||
}
|
||||
|
||||
observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
|
||||
listener.onChange(new Raw<>(AtmosphericPressure.ofHPA(event.values[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,30 +43,40 @@ public class BarometerInternal {
|
||||
}
|
||||
};
|
||||
|
||||
public void connect(Context context, Handler handler, GainManager observer) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
public BarometerInternal(@NonNull SensorManager.SensorDataChangedObserver listener) {
|
||||
this.listener = listener;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(Context context, Handler handler, String addressIgnored) {
|
||||
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
if (pressureSensor == null) {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
this.observer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sensorManager.registerListener(listener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.observer = observer;
|
||||
if (sensorManager.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||
this.context = context;
|
||||
listener.onConnect(new AggregatorBarometer("internal", null));
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(context);
|
||||
}
|
||||
|
||||
public void disconnect(Context context) {
|
||||
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(listener);
|
||||
observer = null;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return observer != null;
|
||||
return context != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (!isConnected()) return;
|
||||
|
||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sensorManager.unregisterListener(sensorEventListener);
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
public interface Driver {
|
||||
void connect(Context context, Handler handler, String address);
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
void disconnect();
|
||||
}
|
||||
@@ -11,8 +11,8 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
||||
|
||||
private AtmosphericPressure lastAcceptedSensorValue;
|
||||
|
||||
public AggregatorBarometer(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
public AggregatorBarometer(String sensorAddress, String sensorName) {
|
||||
super(sensorAddress, sensorName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -120,10 +120,6 @@ public final class SensorDataSet {
|
||||
return;
|
||||
}
|
||||
if (value instanceof AtmosphericPressure) {
|
||||
if (this.barometer == null) {
|
||||
//TODO Quick fix. This shouldn't happen, but it does. May be a race condition?
|
||||
return;
|
||||
}
|
||||
this.barometer.add((Raw<AtmosphericPressure>) data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
@@ -249,11 +250,18 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
private static String getBluetoothSensorAddressNone() {
|
||||
return resources.getString(R.string.sensor_type_value_none);
|
||||
return SensorType.NONE.getPreferenceValue();
|
||||
}
|
||||
|
||||
public static boolean isBluetoothSensorAddressNone(String currentValue) {
|
||||
return getBluetoothSensorAddressNone().equals(currentValue);
|
||||
public static SensorType getSensorType(String address) {
|
||||
return Arrays.stream(SensorType.values())
|
||||
.filter(it -> it.getPreferenceValue().equals(address))
|
||||
.findFirst()
|
||||
.orElse(SensorType.REMOTE);
|
||||
}
|
||||
|
||||
public static String getBarometerSensorAddress() {
|
||||
return getString(R.string.settings_sensor_bluetooth_pressure_key, getBluetoothSensorAddressNone());
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddress() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -7,10 +8,13 @@ import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
public class BluetoothLeAdapter extends BaseAdapter {
|
||||
|
||||
/**
|
||||
@@ -50,22 +54,30 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
return currentView;
|
||||
}
|
||||
|
||||
public void add(String name, String address) {
|
||||
Device device = new Device(name, address);
|
||||
/**
|
||||
* @return Data changed?
|
||||
*/
|
||||
public boolean add(String address, String name) {
|
||||
Device device = new Device(address, name);
|
||||
if (!devices.contains(device)) {
|
||||
devices.add(new Device(name, address));
|
||||
devices.add(device);
|
||||
return true;
|
||||
} else {
|
||||
for (Device currentDevice : devices) {
|
||||
if (currentDevice.getAddress().equals(address)) {
|
||||
currentDevice.setName(name);
|
||||
if (currentDevice.address.equals(address)) {
|
||||
currentDevice.name = name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void add(BluetoothDevice bluetoothDevice) {
|
||||
add(bluetoothDevice.getName(), bluetoothDevice.getAddress());
|
||||
public void addAll(List<BluetoothDevice> bluetoothDevices) {
|
||||
boolean dataSetChanged = bluetoothDevices.stream()
|
||||
.anyMatch(bluetoothDevice -> add(bluetoothDevice.getAddress(), bluetoothDevice.getName()));
|
||||
|
||||
if (dataSetChanged) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Device get(int index) {
|
||||
@@ -73,19 +85,14 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
public static class Device {
|
||||
private String name;
|
||||
|
||||
@NonNull
|
||||
private final String address;
|
||||
private String name;
|
||||
|
||||
public Device(String name, String address) {
|
||||
this.name = name;
|
||||
Device(@NonNull String address, String name) {
|
||||
Objects.requireNonNull(address);
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getNameOrAddress() {
|
||||
return name != null ? name : getAddress();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -93,16 +100,21 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getNameOrAddress() {
|
||||
return name != null ? name : address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Device device)) return false;
|
||||
|
||||
return address.equals(device.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, address);
|
||||
return Objects.hash(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
|
||||
|
||||
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerManagerCyclingPower.CYCLING_POWER);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerManagerCyclingPower.CYCLING_POWER));
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
|
||||
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreferen
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.settings.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorManager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure;
|
||||
|
||||
public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BluetoothLePressurePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
SensorManager sensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
boolean hasInternalSensor = pressureSensor != null;
|
||||
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog
|
||||
.newInstance(getKey(), List.of(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE), hasInternalSensor);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -5,6 +5,8 @@ import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
|
||||
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
||||
@@ -27,6 +29,6 @@ public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSens
|
||||
|
||||
@Override
|
||||
public PreferenceDialogFragmentCompat createInstance() {
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE);
|
||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE));
|
||||
}
|
||||
}
|
||||
|
||||
+34
-25
@@ -23,12 +23,12 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.sensors.SensorType;
|
||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
@@ -43,9 +43,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
private static final String TAG = BluetoothLeSensorPreference.class.getSimpleName();
|
||||
|
||||
private static final String ARG_BLE_SERVICE_UUIDS = "bluetoothUUID";
|
||||
private static final String ARG_INCLUDE_INTERNAL = "supportsInternal";
|
||||
|
||||
private static final int DEVICE_NONE_RESOURCEID = R.string.value_none;
|
||||
|
||||
private static final int SENSOR_INTERNAL_RESOURCEID = R.string.value_internal_sensor;
|
||||
|
||||
public BluetoothLeSensorPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@@ -65,11 +68,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
private String value;
|
||||
private boolean valueSet = false;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
private void setValue(String value) {
|
||||
final boolean changed = !TextUtils.equals(this.value, value);
|
||||
if (changed || !valueSet) {
|
||||
this.value = value;
|
||||
@@ -88,21 +87,24 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
if (getValue() == null || PreferencesUtils.isBluetoothSensorAddressNone(getValue())) {
|
||||
if (value == null || SensorType.NONE.getPreferenceValue().equals(value)) {
|
||||
return getContext().getString(DEVICE_NONE_RESOURCEID);
|
||||
}
|
||||
if (SensorType.INTERNAL.getPreferenceValue().equals(value)) {
|
||||
return getContext().getString(SENSOR_INTERNAL_RESOURCEID);
|
||||
}
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
||||
if (bluetoothAdapter == null) {
|
||||
Log.w(TAG, "No Bluetooth adapter present");
|
||||
return getValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(getValue());
|
||||
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(value);
|
||||
if (device != null && device.getName() != null) {
|
||||
return getContext().getString(R.string.bluetooth_sensor_summary, device.getAddress(), device.getName());
|
||||
return getContext().getString(R.string.bluetooth_sensor_summary, device.getAddress(), device.getName());
|
||||
}
|
||||
return getValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
public abstract PreferenceDialogFragmentCompat createInstance();
|
||||
@@ -119,14 +121,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
@Override
|
||||
public void onScanResult(int callbackType, ScanResult result) {
|
||||
Log.d(TAG, "Found device " + result.getDevice().getName() + " " + result);
|
||||
listAdapter.add(result.getDevice());
|
||||
onBatchScanResults(List.of(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(List<ScanResult> results) {
|
||||
for (ScanResult result : results) {
|
||||
onScanResult(-1, result);
|
||||
}
|
||||
listAdapter.addAll(results.stream().map(ScanResult::getDevice).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,18 +138,20 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
}
|
||||
};
|
||||
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, ServiceMeasurementUUID sensorUUID) {
|
||||
return newInstance(preferenceKey, Collections.singletonList(sensorUUID));
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs) {
|
||||
return newInstance(preferenceKey, sensorUUIDs, false);
|
||||
}
|
||||
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs) {
|
||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs, boolean includeInternalSensor) {
|
||||
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
||||
final Bundle b = new Bundle(1);
|
||||
final Bundle b = new Bundle(3);
|
||||
b.putString(ARG_KEY, preferenceKey);
|
||||
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream()
|
||||
.map(ServiceMeasurementUUID::serviceUUID)
|
||||
.map(ParcelUuid::new)
|
||||
.collect(Collectors.toList())));
|
||||
b.putBoolean(ARG_INCLUDE_INTERNAL, includeInternalSensor);
|
||||
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
@@ -179,6 +181,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
|
||||
private void startBluetoothScan() {
|
||||
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
|
||||
boolean includeInternalSensor = getArguments().getBoolean(ARG_INCLUDE_INTERNAL);
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
||||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
||||
@@ -200,15 +203,21 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
|
||||
listAdapter.add(getContext().getString(DEVICE_NONE_RESOURCEID), deviceNone);
|
||||
listAdapter.add(SensorType.NONE.getPreferenceValue(), getContext().getString(DEVICE_NONE_RESOURCEID));
|
||||
selectedEntryIndex = 0;
|
||||
|
||||
BluetoothLeSensorPreference preference = (BluetoothLeSensorPreference) getPreference();
|
||||
String deviceSelected = preference.getValue();
|
||||
if (deviceSelected != null && !deviceNone.equals(deviceSelected)) {
|
||||
listAdapter.add(preference.getValue(), preference.getValue());
|
||||
selectedEntryIndex = 1;
|
||||
String deviceSelected = preference.value;
|
||||
if (includeInternalSensor) {
|
||||
listAdapter.add(SensorType.INTERNAL.getPreferenceValue(), getString(SENSOR_INTERNAL_RESOURCEID));
|
||||
if (SensorType.INTERNAL.getPreferenceValue().equals(deviceSelected)) {
|
||||
selectedEntryIndex = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceSelected != null && SensorType.REMOTE.equals(PreferencesUtils.getSensorType(deviceSelected))) {
|
||||
listAdapter.add(preference.value, preference.value);
|
||||
selectedEntryIndex = !includeInternalSensor ? 1 : 2;
|
||||
}
|
||||
|
||||
List<ScanFilter> scanFilter = null;
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
<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="settings_sensor_bluetooth_pressure_key" translatable="false">bluetoothPressureSensor</string>
|
||||
<string name="sensor_type_value_none" translatable="false">NONE</string>
|
||||
<string name="sensor_type_value_internal" translatable="false">INTERNAL</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_key" translatable="false">bluetoothCyclingSpeedWheelCircumference</string>
|
||||
<string name="settings_sensor_bluetooth_cycling_speed_wheel_circumference_default" translatable="false">2135</string>
|
||||
|
||||
@@ -331,6 +331,7 @@ limitations under the License.
|
||||
<string name="sensor_state_heart_rate_unit">bpm</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||
<string name="sensor_state_power">Power</string>
|
||||
<string name="sensor_state_pressure">Barometer</string>
|
||||
<string name="sensor_state_power_avg">Avg Power</string>
|
||||
<string name="sensor_state_power_max">Max Power</string>
|
||||
<string name="sensor_state_power_unit">W</string>
|
||||
@@ -559,6 +560,7 @@ limitations under the License.
|
||||
<string name="value_integer_second">%1$d s</string>
|
||||
<string name="value_none">None</string>
|
||||
<string name="value_off">Off</string>
|
||||
<string name="value_internal_sensor">Internal sensor</string>
|
||||
<string name="value_smallest_recommended">Smallest (recommended)</string>
|
||||
<string name="value_int_seconds">%1$d s (recommended)</string>
|
||||
<string name="voiceIdle">Becoming idle.</string>
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
android:key="@string/settings_sensor_heart_rate_max_key"
|
||||
android:title="@string/settings_sensor_heart_rate_max"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLePressurePreference
|
||||
android:defaultValue="@string/sensor_type_value_none"
|
||||
android:key="@string/settings_sensor_bluetooth_pressure_key"
|
||||
android:title="@string/sensor_state_pressure" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_cycling_sensor">
|
||||
|
||||
Reference in New Issue
Block a user