forked from upstream-mirrors/OpenTracks
d7022a07a2
Fixes #1526.
203 lines
8.3 KiB
Java
203 lines
8.3 KiB
Java
/*
|
|
* 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.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.os.Build;
|
|
import android.os.Handler;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
|
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
|
|
|
/**
|
|
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
|
* Also parses the transferred data into {@link SensorDataObserver}.
|
|
*/
|
|
public abstract class AbstractBluetoothConnectionManager<DataType> {
|
|
|
|
private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
|
|
|
|
private final SensorDataObserver observer;
|
|
|
|
private final List<ServiceMeasurementUUID> serviceMeasurementUUIDs;
|
|
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.i(TAG, "Connecting to sensor: " + gatt.getDevice());
|
|
case BluetoothProfile.STATE_CONNECTED -> {
|
|
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
|
|
gatt.discoverServices();
|
|
}
|
|
case BluetoothProfile.STATE_DISCONNECTING ->
|
|
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
|
case BluetoothProfile.STATE_DISCONNECTED -> {
|
|
//This is also triggered, if no connection was established (ca. 30s)
|
|
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
|
|
if (gatt.connect()) {
|
|
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
|
|
}
|
|
clearData();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
|
BluetoothGattService gattService = null;
|
|
ServiceMeasurementUUID serviceMeasurement = null;
|
|
for (ServiceMeasurementUUID s : serviceMeasurementUUIDs) {
|
|
gattService = gatt.getService(s.serviceUUID());
|
|
if (gattService != null) {
|
|
serviceMeasurement = s;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (gattService == null) {
|
|
Log.e(TAG, "Could not get gattService for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement);
|
|
return;
|
|
}
|
|
|
|
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
|
if (characteristic == null) {
|
|
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.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) {
|
|
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();
|
|
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);
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
observer.onChanged(sensorData);
|
|
} else {
|
|
//TODO This might lead to NPEs in case of race conditions due to shutdown.
|
|
observer.getHandler().post(() -> observer.onChanged(sensorData));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
AbstractBluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
|
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
|
|
this.observer = observer;
|
|
}
|
|
|
|
AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
|
this.serviceMeasurementUUIDs = serviceUUUID;
|
|
this.observer = observer;
|
|
}
|
|
|
|
synchronized void connect(Context context, @NonNull BluetoothDevice device) {
|
|
if (bluetoothGatt != null) {
|
|
Log.w(TAG, "Already connected; ignoring.");
|
|
return;
|
|
}
|
|
|
|
Log.d(TAG, "Connecting to: " + device);
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, this.observer.getHandler());
|
|
} else {
|
|
bluetoothGatt = device.connectGatt(context, false, 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(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, @NonNull BluetoothGattCharacteristic characteristic);
|
|
|
|
@Deprecated //TODO REMOVE
|
|
interface SensorDataObserver {
|
|
|
|
void onChanged(SensorData<?> sensorData);
|
|
|
|
void onDisconnecting(SensorData<?> sensorData);
|
|
|
|
@NonNull
|
|
Handler getHandler();
|
|
}
|
|
}
|