Skip to content

Commit

Permalink
Bluetooth: use composition instead of inheritance.
Browse files Browse the repository at this point in the history
Part of #1424.
  • Loading branch information
dennisguse committed Nov 5, 2023
1 parent dcc1e8c commit deeb447
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class BluetoothUtilsTest {
@Test
public void parseHeartRate_uint8() {
// given
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x02, 0x3C});

// when
Expand All @@ -34,7 +34,7 @@ public void parseHeartRate_uint8() {
@Test
public void parseHeartRate_uint16() {
// given
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});

// when
Expand All @@ -59,7 +59,7 @@ public void parseEnvironmentalSensing_Pa() {

@Test
public void parseCyclingSpeedCadence_crankOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});

// when
Expand All @@ -72,7 +72,7 @@ public void parseCyclingSpeedCadence_crankOnly() {

@Test
public void parseCyclingSpeedCadence_wheelOnly() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});

// when
Expand All @@ -85,7 +85,7 @@ public void parseCyclingSpeedCadence_wheelOnly() {

@Test
public void parseCyclingSpeedCadence_crankWheel() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});

// when
Expand All @@ -98,7 +98,7 @@ public void parseCyclingSpeedCadence_crankWheel() {

@Test
public void parseCyclingPower_power() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0, 0, 40, 0});

// when
Expand All @@ -110,7 +110,7 @@ public void parseCyclingPower_power() {

@Test
public void parseCyclingPower_power_with_cadence() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionManagerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});

// when
Expand All @@ -125,7 +125,7 @@ public void parseCyclingPower_power_with_cadence() {

@Test
public void parseRunningSpeedAndCadence_with_distance() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothConnectionRunningSpeedAndCadence.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});

// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@

import androidx.annotation.NonNull;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

/**
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
*/
@SuppressLint("MissingPermission")
public abstract class AbstractBluetoothConnectionManager<DataType> {
public class BluetoothConnectionManager {

private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();

private final SensorManager.SensorDataChangedObserver observer;

private final List<ServiceMeasurementUUID> serviceMeasurementUUIDs;
private final SensorHandlerInterface sensorHandler;
private BluetoothGatt bluetoothGatt;

private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
Expand Down Expand Up @@ -76,7 +76,7 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
BluetoothGattService gattService = null;
ServiceMeasurementUUID serviceMeasurement = null;
for (ServiceMeasurementUUID s : serviceMeasurementUUIDs) {
for (ServiceMeasurementUUID s : sensorHandler.getServices()) {
gattService = gatt.getService(s.serviceUUID());
if (gattService != null) {
serviceMeasurement = s;
Expand Down Expand Up @@ -112,24 +112,22 @@ public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
UUID serviceUUID = characteristic.getService().getUuid();
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress() + " with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = serviceMeasurementUUIDs.stream()
.filter(s -> s.serviceUUID().equals(characteristic.getService().getUuid())).findFirst();
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = sensorHandler.getServices()
.stream()
.filter(s -> s.serviceUUID().equals(characteristic.getService().getUuid()))
.findFirst();
if (serviceMeasurementUUID.isEmpty()) {
Log.e(TAG, "Unknown service UUID; not supported?");
return;
}

SensorData<DataType> sensorData = parsePayload(serviceMeasurementUUID.get(), gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
observer.onChange(sensorData);
}
sensorHandler.handlePayload(observer, serviceMeasurementUUID.get(), gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
}
};

AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorManager.SensorDataChangedObserver observer) {
this.serviceMeasurementUUIDs = serviceUUUID;
BluetoothConnectionManager(SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
this.observer = observer;
this.sensorHandler = sensorHandler;
}

synchronized void connect(Context context, Handler handler, @NonNull BluetoothDevice device) {
Expand All @@ -142,12 +140,12 @@ synchronized void connect(Context context, Handler handler, @NonNull BluetoothDe

bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);

SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
SensorData<?> sensorData = sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress());
observer.onChange(sensorData);
}

private synchronized void clearData() {
observer.onDisconnect(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
}

synchronized void disconnect() {
Expand All @@ -167,11 +165,4 @@ synchronized boolean isSameBluetoothDevice(String address) {

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(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, @NonNull BluetoothGattCharacteristic characteristic);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,38 @@
import android.bluetooth.BluetoothGattCharacteristic;

import java.util.List;
import java.util.UUID;

import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

public class BluetoothConnectionManagerCyclingDistanceSpeed extends AbstractBluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
public class BluetoothConnectionManagerCyclingDistanceSpeed implements SensorHandlerInterface {

BluetoothConnectionManagerCyclingDistanceSpeed(SensorManager.SensorDataChangedObserver observer) {
super(List.of(BluetoothUtils.CYCLING_SPEED_CADENCE), observer);
public static final ServiceMeasurementUUID CYCLING_SPEED_CADENCE = new ServiceMeasurementUUID(
new UUID(0x181600001000L, 0x800000805f9b34fbL),
new UUID(0x2A5B00001000L, 0x800000805f9b34fbL)
);

@Override
public List<ServiceMeasurementUUID> getServices() {
return List.of(CYCLING_SPEED_CADENCE);
}

@Override
protected SensorDataCyclingDistanceSpeed createEmptySensorData(String address) {
public SensorDataCyclingDistanceSpeed createEmptySensorData(String address) {
return new SensorDataCyclingDistanceSpeed(address);
}

@Override
protected SensorDataCyclingDistanceSpeed parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
return;
}

if (cadenceAndSpeed.getDistanceSpeed() != null) {
return cadenceAndSpeed.getDistanceSpeed();
observer.onChange(cadenceAndSpeed.getDistanceSpeed());
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,34 @@
import androidx.annotation.NonNull;

import java.util.List;
import java.util.UUID;

import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothConnectionManager<Power> {
public class BluetoothConnectionManagerCyclingPower implements SensorHandlerInterface {

BluetoothConnectionManagerCyclingPower(@NonNull SensorManager.SensorDataChangedObserver observer) {
super(List.of(BluetoothUtils.CYCLING_POWER), observer);
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
new UUID(0x181800001000L, 0x800000805f9b34fbL),
new UUID(0x2A6300001000L, 0x800000805f9b34fbL)
);

@Override
public List<ServiceMeasurementUUID> getServices() {
return List.of(CYCLING_POWER);
}

@Override
protected SensorDataCyclingPower createEmptySensorData(String address) {
public SensorDataCyclingPower createEmptySensorData(String address) {
return new SensorDataCyclingPower(address);
}

@Override
protected SensorDataCyclingPower parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);

return cyclingPower != null ? cyclingPower.power() : null;
if (cyclingPower != null) {
observer.onChange(cyclingPower.power());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,45 @@
import androidx.annotation.NonNull;

import java.util.List;
import java.util.UUID;

import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

public class BluetoothConnectionManagerHeartRate extends AbstractBluetoothConnectionManager<HeartRate> {
public class BluetoothConnectionManagerHeartRate implements SensorHandlerInterface {

BluetoothConnectionManagerHeartRate(@NonNull SensorManager.SensorDataChangedObserver observer) {
super(List.of(BluetoothUtils.HEARTRATE), observer);
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
);

// Used for device discovery in preferences
public static final List<ServiceMeasurementUUID> HEART_RATE_SUPPORTING_DEVICES = List.of(
HEARTRATE,
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
new ServiceMeasurementUUID(
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"), //Miband3
HEARTRATE.measurementUUID()
)
);

@Override
public List<ServiceMeasurementUUID> getServices() {
return HEART_RATE_SUPPORTING_DEVICES;
}

@Override
protected SensorDataHeartRate createEmptySensorData(String address) {
public SensorDataHeartRate createEmptySensorData(String address) {
return new SensorDataHeartRate(address);
}

@Override
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);

return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
if (heartRate != null) {
observer.onChange(new SensorDataHeartRate(address, sensorName, heartRate));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,31 @@
import androidx.annotation.NonNull;

import java.util.List;
import java.util.UUID;

import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

public class BluetoothConnectionRunningSpeedAndCadence extends AbstractBluetoothConnectionManager<SensorDataRunning.Data> {
public class BluetoothConnectionRunningSpeedAndCadence implements SensorHandlerInterface {

BluetoothConnectionRunningSpeedAndCadence(@NonNull SensorManager.SensorDataChangedObserver observer) {
super(List.of(BluetoothUtils.RUNNING_SPEED_CADENCE), observer);

public static final ServiceMeasurementUUID RUNNING_SPEED_CADENCE = new ServiceMeasurementUUID(
new UUID(0x181400001000L, 0x800000805f9b34fbL),
new UUID(0x2A5300001000L, 0x800000805f9b34fbL)
);

@Override
public List<ServiceMeasurementUUID> getServices() {
return List.of(RUNNING_SPEED_CADENCE);
}

@Override
protected SensorDataRunning createEmptySensorData(String address) {
public SensorDataRunning createEmptySensorData(String address) {
return new SensorDataRunning(address);
}

@Override
protected SensorDataRunning parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
return BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic);
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
observer.onChange(BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic));
}
}
Loading

0 comments on commit deeb447

Please sign in to comment.