Package structure: moved sensors.

Part of #963.
This commit is contained in:
Dennis Guse
2022-01-15 22:02:29 +01:00
parent 0645628d7e
commit 88c4e1a109
26 changed files with 51 additions and 51 deletions
@@ -12,12 +12,12 @@ import androidx.annotation.NonNull;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -40,13 +40,13 @@ import java.util.Objects;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.data.CustomContentProvider;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncementManager;
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -12,11 +12,11 @@ import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
/**
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
@@ -1,143 +0,0 @@
package de.dennisguse.opentracks.services.sensors;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
*/
public class AltitudeSumManager implements SensorEventListener {
private static final String TAG = AltitudeSumManager.class.getSimpleName();
private boolean isConnected = false;
private float lastAcceptedPressureValue_hPa;
private float lastSeenSensorValue_hPa;
private Float altitudeGain_m;
private Float altitudeLoss_m;
public void start(Context context) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
if (pressureSensor == null) {
Log.w(TAG, "No pressure sensor available.");
isConnected = false;
} else {
isConnected = sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
lastAcceptedPressureValue_hPa = Float.NaN;
reset();
}
public void stop(Context context) {
Log.d(TAG, "Stop");
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensorManager.unregisterListener(this);
isConnected = false;
reset();
}
@VisibleForTesting
public void setConnected(boolean isConnected) {
this.isConnected = isConnected;
}
public void fill(@NonNull TrackPoint trackPoint) {
trackPoint.setAltitudeGain(altitudeGain_m);
trackPoint.setAltitudeLoss(altitudeLoss_m);
}
@Nullable
public Float getAltitudeGain_m() {
return isConnected ? altitudeGain_m : null;
}
@VisibleForTesting
public void setAltitudeGain_m(float altitudeGain_m) {
this.altitudeGain_m = altitudeGain_m;
}
@VisibleForTesting
public void addAltitudeGain_m(float altitudeGain_m) {
this.altitudeGain_m = this.altitudeGain_m == null ? 0f : this.altitudeGain_m;
this.altitudeGain_m += altitudeGain_m;
}
@VisibleForTesting
public void addAltitudeLoss_m(Float altitudeLoss_m) {
this.altitudeLoss_m = this.altitudeLoss_m == null ? 0f : this.altitudeLoss_m;
this.altitudeLoss_m += altitudeLoss_m;
}
@Nullable
public Float getAltitudeLoss_m() {
return isConnected ? altitudeLoss_m : null;
}
@VisibleForTesting
public void setAltitudeLoss_m(float altitudeLoss_m) {
this.altitudeLoss_m = altitudeLoss_m;
}
public void reset() {
Log.d(TAG, "Reset");
altitudeGain_m = null;
altitudeLoss_m = null;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
}
@Override
public void onSensorChanged(SensorEvent event) {
if (!isConnected) {
Log.w(TAG, "Not connected to sensor, cannot process data.");
return;
}
onSensorValueChanged(event.values[0]);
}
@VisibleForTesting
void onSensorValueChanged(float value_hPa) {
if (Float.isNaN(lastAcceptedPressureValue_hPa)) {
lastAcceptedPressureValue_hPa = value_hPa;
lastSeenSensorValue_hPa = value_hPa;
return;
}
altitudeGain_m = altitudeGain_m != null ? altitudeGain_m : 0;
altitudeLoss_m = altitudeLoss_m != null ? altitudeLoss_m : 0;
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedPressureValue_hPa, lastSeenSensorValue_hPa, value_hPa);
if (altitudeChange != null) {
altitudeGain_m += altitudeChange.getAltitudeGain_m();
altitudeLoss_m += altitudeChange.getAltitudeLoss_m();
lastAcceptedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa();
}
lastSeenSensorValue_hPa = value_hPa;
Log.v(TAG, "altitude gain: " + altitudeGain_m + ", altitude loss: " + altitudeLoss_m);
}
}
@@ -1,277 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.sensors;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.UUID;
import de.dennisguse.opentracks.content.sensor.SensorData;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
/**
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
* Also parses the transferred data into {@link SensorDataObserver}.
*/
public abstract class BluetoothConnectionManager<DataType> {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private final SensorDataObserver observer;
private final UUID serviceUUUID;
private final UUID measurementUUID;
private BluetoothGatt bluetoothGatt;
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTING:
Log.d(TAG, "Connecting to sensor: " + gatt.getDevice());
break;
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTING:
Log.d(TAG, "Disconnecting from sensor: " + gatt.getDevice());
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
clearData();
break;
}
}
@Override
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
BluetoothGattService service = gatt.getService(serviceUUUID);
if (service == null) {
Log.e(TAG, "Could not get service for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID);
return;
}
BluetoothGattCharacteristic characteristic = service.getCharacteristic(measurementUUID);
if (characteristic == null) {
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID + " characteristicUUID=" + measurementUUID);
return;
}
gatt.setCharacteristicNotification(characteristic, true);
// Register for updates.
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(BluetoothUtils.CLIENT_CHARACTERISTIC_CONFIG_UUID);
if (descriptor == null) {
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID characteristic not available; cannot request notifications for changed data.");
return;
}
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
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);
}
}
};
BluetoothConnectionManager(UUID serviceUUUID, UUID measurementUUID, SensorDataObserver observer) {
this.serviceUUUID = serviceUUUID;
this.measurementUUID = measurementUUID;
this.observer = observer;
}
synchronized void connect(Context context, @NonNull BluetoothDevice device) {
if (bluetoothGatt != null) {
Log.w(TAG, "Already connected; ignoring.");
}
Log.d(TAG, "Connecting to: " + device);
bluetoothGatt = device.connectGatt(context, true, connectCallback);
SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
observer.onChanged(sensorData);
}
private synchronized void clearData() {
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
}
synchronized void disconnect() {
if (bluetoothGatt == null) {
Log.w(TAG, "Cannot disconnect if not connected.");
return;
}
bluetoothGatt.close();
clearData();
bluetoothGatt = null;
}
synchronized boolean isSameBluetoothDevice(String address) {
if (bluetoothGatt == null) {
return false;
}
return address.equals(bluetoothGatt.getDevice().getAddress());
}
protected abstract SensorData<DataType> createEmptySensorData(String address);
/**
* @return null if data could not be parsed.
*/
protected abstract SensorData<DataType> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
public static class HeartRate extends BluetoothConnectionManager<Float> {
HeartRate(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEART_RATE_SERVICE_UUID, BluetoothUtils.HEART_RATE_MEASUREMENT_CHAR_UUID, observer);
}
@Override
protected SensorDataHeartRate createEmptySensorData(String address) {
return new SensorDataHeartRate(address);
}
@Override
protected SensorDataHeartRate parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
}
}
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);
}
@Override
protected SensorDataCycling.Cadence createEmptySensorData(String address) {
return new SensorDataCycling.Cadence(address);
}
@Override
protected SensorDataCycling.Cadence parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
}
if (cadenceAndSpeed.getCadence() != null) {
return cadenceAndSpeed.getCadence();
}
return null;
}
}
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);
}
@Override
protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) {
return new SensorDataCycling.DistanceSpeed(address);
}
@Override
protected SensorDataCycling.DistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
}
if (cadenceAndSpeed.getDistanceSpeed() != null) {
return cadenceAndSpeed.getDistanceSpeed();
}
return null;
}
}
public static class CyclingPower extends BluetoothConnectionManager<Float> {
CyclingPower(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_POWER_UUID, BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, observer);
}
@Override
protected SensorDataCyclingPower createEmptySensorData(String address) {
return new SensorDataCyclingPower(address);
}
@Override
protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
Integer cyclingPower = BluetoothUtils.parseCyclingPower(characteristic);
return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, cyclingPower) : null;
}
}
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);
void onDisconnecting(SensorData<?> sensorData);
}
}
@@ -1,224 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.sensors;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import java.time.Duration;
import de.dennisguse.opentracks.R;
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.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* 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 BluetoothConnectionManager.SensorDataObserver {
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
public static final Duration MAX_SENSOR_DATE_SET_AGE_MS = Duration.ofSeconds(5);
private final BluetoothAdapter bluetoothAdapter;
private final Context context;
private boolean started = false;
private Distance preferenceWheelCircumference;
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
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();
private final SensorDataSetChangeObserver observer;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!started) return;
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress();
connect(heartRate, address);
}
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress();
connect(cyclingCadence, address);
}
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress();
connect(cyclingSpeed, address);
}
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference();
}
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
connect(cyclingPower, address);
}
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress();
connect(runningSpeedAndCadence, address);
}
}
};
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull SensorDataSetChangeObserver observer) {
this.context = context;
this.observer = observer;
bluetoothAdapter = BluetoothUtils.getAdapter(context);
}
public void start() {
started = true;
//Registering triggers connection startup
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public synchronized void stop() {
heartRate.disconnect();
cyclingCadence.disconnect();
cyclingSpeed.disconnect();
cyclingPower.disconnect();
runningSpeedAndCadence.disconnect();
sensorDataSet.clear();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
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();
}
Log.i(TAG, "Connecting to bluetooth address: " + address);
try {
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
connectionManager.connect(context, device);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get remote device for: " + address, e);
}
}
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
sensorDataSet.fillTrackPoint(trackPoint);
return new SensorDataSet(sensorDataSet);
}
public void reset() {
sensorDataSet.reset();
}
@Override
public synchronized void onChanged(SensorData<?> sensorData) {
if (sensorData instanceof SensorDataCycling.Cadence) {
SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence();
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
if (sensorData.equals(previous)) {
Log.d(TAG, "onChanged: cadence data repeated.");
return;
}
((SensorDataCycling.Cadence) sensorData).compute(previous);
}
if (sensorData instanceof SensorDataCycling.DistanceSpeed) {
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData.equals(previous)) {
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);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@Override
public void onDisconnecting(SensorData<?> sensorData) {
sensorDataSet.remove(sensorData);
}
public interface SensorDataSetChangeObserver {
void onChange(SensorDataSet sensorDataSet);
}
}
@@ -1,181 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services.sensors;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Utilities for dealing with bluetooth devices.
*
* @author Rodrigo Damazio
*/
public class BluetoothUtils {
public static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
public static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
public static final List<UUID> HEART_RATE_SUPPORTING_DEVICES = Collections.unmodifiableList(Arrays.asList(
BluetoothUtils.HEART_RATE_SERVICE_UUID,
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb") //Miband3
));
public static final UUID CYCLING_POWER_UUID = new UUID(0x181800001000L, 0x800000805f9b34fbL);
public static final UUID CYCLING_POWER_MEASUREMENT_CHAR_UUID = new UUID(0x2A6300001000L, 0x800000805f9b34fbL);
public static final UUID CYCLING_SPEED_CADENCE_SERVICE_UUID = new UUID(0x181600001000L, 0x800000805f9b34fbL);
public static final UUID CYCLING_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() {
}
public static BluetoothAdapter getAdapter(Context context) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
Log.i(TAG, "BluetoothManager not available.");
return null;
} else {
return bluetoothManager.getAdapter();
}
}
public static boolean hasBluetooth(Context context) {
return BluetoothUtils.getAdapter(context) != null;
}
public static Integer parseHeartRate(BluetoothGattCharacteristic characteristic) {
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
byte[] raw = characteristic.getValue();
if (raw.length == 0) {
return null;
}
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
if (formatUINT16 && raw.length >= 3) {
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
}
if (!formatUINT16 && raw.length >= 2) {
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
}
return null;
}
public static Integer parseCyclingPower(BluetoothGattCharacteristic characteristic) {
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.cycling_power_measurement.xml
int valueLength = characteristic.getValue().length;
if (valueLength < 4) {
return null;
}
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2);
}
public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
int valueLength = characteristic.getValue().length;
if (valueLength == 0) {
return null;
}
int flags = characteristic.getValue()[0];
boolean hasWheel = (flags & 0x01) > 0;
boolean hasCrank = (flags & 0x02) > 0;
int index = 1;
SensorDataCycling.DistanceSpeed speed = null;
if (hasWheel && valueLength - index >= 6) {
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
index += 4;
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
index += 2;
}
SensorDataCycling.Cadence cadence = null;
if (hasCrank && valueLength - index >= 4) {
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
index += 2;
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
}
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 >= 4) {
totalDistance = Distance.ofDM(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index));
}
return new SensorDataRunning(address, sensorName, speed, cadence, totalDistance);
}
}
@@ -1,84 +0,0 @@
package de.dennisguse.opentracks.services.sensors;
import android.hardware.SensorManager;
import androidx.annotation.VisibleForTesting;
public class PressureSensorUtils {
//Everything above is considered a meaningful change in altitude.
private static final float ALTITUDE_CHANGE_DIFF_M = 3.0f;
private static final float EXPONENTIAL_SMOOTHING = 0.3f;
private static final float p0 = SensorManager.PRESSURE_STANDARD_ATMOSPHERE;
private PressureSensorUtils() {
}
public static class AltitudeChange {
private final float currentSensorValue_hPa;
private final float altitudeChange_m;
public AltitudeChange(float currentSensorValue_hPa, float altitudeChange_m) {
this.currentSensorValue_hPa = currentSensorValue_hPa;
this.altitudeChange_m = altitudeChange_m;
}
public float getCurrentSensorValue_hPa() {
return currentSensorValue_hPa;
}
public float getAltitudeChange_m() {
return altitudeChange_m;
}
public float getAltitudeGain_m() {
return altitudeChange_m > 0 ? altitudeChange_m : 0;
}
public float getAltitudeLoss_m() {
return altitudeChange_m < 0 ? -1 * altitudeChange_m : 0;
}
}
/**
* Applies exponential smoothing to sensor value before computation.
*/
public static AltitudeChange computeChangesWithSmoothing_m(float lastAcceptedSensorValue_hPa, float lastSeenSensorValue_hPa, float currentSensorValue_hPa) {
float nextSensorValue_hPa = EXPONENTIAL_SMOOTHING * currentSensorValue_hPa + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue_hPa;
return computeChanges(lastAcceptedSensorValue_hPa, nextSensorValue_hPa);
}
@VisibleForTesting
public static AltitudeChange computeChanges(float lastAcceptedSensorValue_hPa, float currentSensorValue_hPa) {
float lastSensorValue_m = SensorManager.getAltitude(p0, lastAcceptedSensorValue_hPa);
float currentSensorValue_m = SensorManager.getAltitude(p0, currentSensorValue_hPa);
float altitudeChange_m = currentSensorValue_m - lastSensorValue_m;
if (Math.abs(altitudeChange_m) < ALTITUDE_CHANGE_DIFF_M) {
return null;
}
// Limit altitudeC change by ALTITUDE_CHANGE_DIFF and computes pressure value accordingly.
AltitudeChange altitudeChange = new AltitudeChange(currentSensorValue_hPa, altitudeChange_m);
if (altitudeChange.getAltitudeChange_m() > 0) {
return new AltitudeChange(getBarometricPressure(lastSensorValue_m + ALTITUDE_CHANGE_DIFF_M), ALTITUDE_CHANGE_DIFF_M);
} else {
return new AltitudeChange(getBarometricPressure(lastSensorValue_m - ALTITUDE_CHANGE_DIFF_M), -1 * ALTITUDE_CHANGE_DIFF_M);
}
}
/*
* Barometeric pressure to altitude estimation; inverts of SensorManager.getAltitude(float, float)
* https://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Internationale_H%C3%B6henformel
* {\color{White} p(h)} = p_0 \cdot \left( 1 - \frac{0{,}0065 \frac{\mathrm K}{\mathrm m} \cdot h}{T_0\ } \right)^{5{,}255}
*/
@VisibleForTesting
public static float getBarometricPressure(float altitude_m) {
return (float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f));
}
}
@@ -1,30 +0,0 @@
package de.dennisguse.opentracks.services.sensors;
public class UintUtils {
public static final int UINT16_MAX = 0xFFFF;
public static final long UINT32_MAX = 0xFFFFFFFFL;
private UintUtils() {
}
/**
* Computes a - b for UINT with overflow (b < a).
*
* @return diff
*/
public static long diff(long a, long b, final long UINT_MAX) {
if (a < 0 || b < 0) {
throw new RuntimeException("a or b cannot be less than zero.");
}
if (a > UINT_MAX || b > UINT_MAX) {
throw new RuntimeException("a or b are outside of the allowed range.");
}
if (a >= b) {
return a - b;
}
return (UINT_MAX - b) + a;
}
}