1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-09 11:47:04 +01:00

Add initial support for Gatt server in BtLEQueue

This commit is contained in:
Andreas Böhler 2019-01-29 11:00:58 +01:00
parent 05fa8e846b
commit 6577ad69b0
7 changed files with 671 additions and 40 deletions

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle; package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattDescriptor;
@ -44,16 +45,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBlePro
* Bluetooth Smart. * Bluetooth Smart.
* <p/> * <p/>
* The connection to the device and all communication is made with a generic {@link BtLEQueue}. * The connection to the device and all communication is made with a generic {@link BtLEQueue}.
* Messages to the device are encoded as {@link BtLEAction actions} that are grouped with a * Messages to the device are encoded as {@link BtLEAction actions} or {@link BtLEServerAction actions}
* {@link Transaction} and sent via {@link BtLEQueue}. * that are grouped with a {@link Transaction} or {@link ServerTransaction} and sent via {@link BtLEQueue}.
* *
* @see TransactionBuilder * @see TransactionBuilder
* @see BtLEQueue * @see BtLEQueue
*/ */
public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback { public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback, GattServerCallback {
private BtLEQueue mQueue; private BtLEQueue mQueue;
private Map<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics; private Map<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics;
private final Set<UUID> mSupportedServices = new HashSet<>(4); private final Set<UUID> mSupportedServices = new HashSet<>(4);
private final Set<BluetoothGattService> mSupportedServerServices = new HashSet<>(4);
private Logger logger; private Logger logger;
private final List<AbstractBleProfile<?>> mSupportedProfiles = new ArrayList<>(); private final List<AbstractBleProfile<?>> mSupportedProfiles = new ArrayList<>();
@ -70,7 +72,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
@Override @Override
public boolean connect() { public boolean connect() {
if (mQueue == null) { if (mQueue == null) {
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext()); mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices);
mQueue.setAutoReconnect(getAutoReconnect()); mQueue.setAutoReconnect(getAutoReconnect());
} }
return mQueue.connect(); return mQueue.connect();
@ -136,6 +138,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
return createTransactionBuilder(taskName); return createTransactionBuilder(taskName);
} }
public ServerTransactionBuilder createServerTransactionBuilder(String taskName) {
return new ServerTransactionBuilder(taskName);
}
public ServerTransactionBuilder performServer(String taskName) throws IOException {
if (!isConnected()) {
if(!connect()) {
throw new IOException("1: Unable to connect to device: " + getDevice());
}
}
return createServerTransactionBuilder(taskName);
}
/** /**
* Ensures that the device is connected and (only then) performs the actions of the given * Ensures that the device is connected and (only then) performs the actions of the given
* transaction builder. * transaction builder.
@ -187,6 +202,14 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
mSupportedProfiles.add(profile); mSupportedProfiles.add(profile);
} }
/**
* Subclasses should call this method to add server services they support.
* @param service
*/
protected void addSupportedServerService(BluetoothGattService service) {
mSupportedServerServices.add(service);
}
/** /**
* Returns the characteristic matching the given UUID. Only characteristics * Returns the characteristic matching the given UUID. Only characteristics
* are returned whose service is marked as supported. * are returned whose service is marked as supported.
@ -337,4 +360,29 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
public void onSetLedColor(int color) { public void onSetLedColor(int color) {
} }
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
}
@Override
public boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
return false;
}
@Override
public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
return false;
}
@Override
public boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
return false;
}
@Override
public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
return false;
}
} }

View File

@ -23,7 +23,10 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
@ -35,9 +38,10 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -53,20 +57,27 @@ public final class BtLEQueue {
private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class); private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
private final Object mGattMonitor = new Object(); private final Object mGattMonitor = new Object();
private final Object mTransactionMonitor = new Object();
private final GBDevice mGbDevice; private final GBDevice mGbDevice;
private final BluetoothAdapter mBluetoothAdapter; private final BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt; private BluetoothGatt mBluetoothGatt;
private BluetoothGattServer mBluetoothGattServer;
private final Set<BluetoothGattService> mSupportedServerServices;
private final BlockingQueue<Transaction> mTransactions = new LinkedBlockingQueue<>(); private final Queue<Transaction> mTransactions = new ConcurrentLinkedQueue<>();
private final Queue<ServerTransaction> mServerTransactions = new ConcurrentLinkedQueue<>();
private volatile boolean mDisposed; private volatile boolean mDisposed;
private volatile boolean mCrashed; private volatile boolean mCrashed;
private volatile boolean mAbortTransaction; private volatile boolean mAbortTransaction;
private volatile boolean mAbortServerTransaction;
private final Context mContext; private final Context mContext;
private CountDownLatch mWaitForActionResultLatch; private CountDownLatch mWaitForActionResultLatch;
private CountDownLatch mWaitForServerActionResultLatch;
private CountDownLatch mConnectionLatch; private CountDownLatch mConnectionLatch;
private BluetoothGattCharacteristic mWaitCharacteristic; private BluetoothGattCharacteristic mWaitCharacteristic;
private final InternalGattCallback internalGattCallback; private final InternalGattCallback internalGattCallback;
private final InternalGattServerCallback internalGattServerCallback;
private boolean mAutoReconnect; private boolean mAutoReconnect;
private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") { private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
@ -77,7 +88,16 @@ public final class BtLEQueue {
while (!mDisposed && !mCrashed) { while (!mDisposed && !mCrashed) {
try { try {
Transaction transaction = mTransactions.take(); LOG.info("waiting...");
synchronized (mTransactionMonitor) {
try {
mTransactionMonitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Transaction transaction = mTransactions.poll();
ServerTransaction serverTransaction = mServerTransactions.poll();
if (!isConnected()) { if (!isConnected()) {
LOG.debug("not connected, waiting for connection..."); LOG.debug("not connected, waiting for connection...");
@ -94,37 +114,68 @@ public final class BtLEQueue {
mConnectionLatch = null; mConnectionLatch = null;
} }
internalGattCallback.setTransactionGattCallback(transaction.getGattCallback()); if(serverTransaction != null) {
mAbortTransaction = false; internalGattServerCallback.setTransactionGattCallback(serverTransaction.getGattCallback());
// Run all actions of the transaction until one doesn't succeed mAbortServerTransaction = false;
for (BtLEAction action : transaction.getActions()) {
if (mAbortTransaction) { // got disconnected for (BtLEServerAction action : serverTransaction.getActions()) {
LOG.info("Aborting running transaction"); if (mAbortServerTransaction) { // got disconnected
break; LOG.info("Aborting running transaction");
} break;
mWaitCharacteristic = action.getCharacteristic(); }
mWaitForActionResultLatch = new CountDownLatch(1); if (LOG.isDebugEnabled()) {
if (LOG.isDebugEnabled()) { LOG.debug("About to run action: " + action);
LOG.debug("About to run action: " + action); }
} if (action.run(mBluetoothGattServer)) {
if (action instanceof GattListenerAction) { // check again, maybe due to some condition, action did not need to write, so we can't wait
// this special action overwrites the transaction gatt listener (if any), it must boolean waitForResult = action.expectsResult();
// always be the last action in the transaction if (waitForResult) {
internalGattCallback.setTransactionGattCallback(((GattListenerAction)action).getGattCallback()); mWaitForServerActionResultLatch.await();
} mWaitForServerActionResultLatch = null;
if (action.run(mBluetoothGatt)) { if (mAbortServerTransaction) {
// check again, maybe due to some condition, action did not need to write, so we can't wait break;
boolean waitForResult = action.expectsResult(); }
if (waitForResult) { }
mWaitForActionResultLatch.await(); } else {
mWaitForActionResultLatch = null; LOG.error("Action returned false: " + action);
if (mAbortTransaction) { break; // abort the transaction
break; }
} }
}
if(transaction != null) {
internalGattCallback.setTransactionGattCallback(transaction.getGattCallback());
mAbortTransaction = false;
// Run all actions of the transaction until one doesn't succeed
for (BtLEAction action : transaction.getActions()) {
if (mAbortTransaction) { // got disconnected
LOG.info("Aborting running transaction");
break;
}
mWaitCharacteristic = action.getCharacteristic();
mWaitForActionResultLatch = new CountDownLatch(1);
if (LOG.isDebugEnabled()) {
LOG.debug("About to run action: " + action);
}
if (action instanceof GattListenerAction) {
// this special action overwrites the transaction gatt listener (if any), it must
// always be the last action in the transaction
internalGattCallback.setTransactionGattCallback(((GattListenerAction) action).getGattCallback());
}
if (action.run(mBluetoothGatt)) {
// check again, maybe due to some condition, action did not need to write, so we can't wait
boolean waitForResult = action.expectsResult();
if (waitForResult) {
mWaitForActionResultLatch.await();
mWaitForActionResultLatch = null;
if (mAbortTransaction) {
break;
}
}
} else {
LOG.error("Action returned false: " + action);
break; // abort the transaction
} }
} else {
LOG.error("Action returned false: " + action);
break; // abort the transaction
} }
} }
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
@ -143,11 +194,13 @@ public final class BtLEQueue {
} }
}; };
public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, Context context) { public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, GattServerCallback externalGattServerCallback, Context context, Set<BluetoothGattService> supportedServerServices) {
mBluetoothAdapter = bluetoothAdapter; mBluetoothAdapter = bluetoothAdapter;
mGbDevice = gbDevice; mGbDevice = gbDevice;
internalGattCallback = new InternalGattCallback(externalGattCallback); internalGattCallback = new InternalGattCallback(externalGattCallback);
internalGattServerCallback = new InternalGattServerCallback(externalGattServerCallback);
mContext = context; mContext = context;
mSupportedServerServices = supportedServerServices;
dispatchThread.start(); dispatchThread.start();
} }
@ -183,6 +236,21 @@ public final class BtLEQueue {
LOG.info("Attempting to connect to " + mGbDevice.getName()); LOG.info("Attempting to connect to " + mGbDevice.getName());
mBluetoothAdapter.cancelDiscovery(); mBluetoothAdapter.cancelDiscovery();
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress()); BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
if(!mSupportedServerServices.isEmpty()) {
BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
LOG.error("Error getting bluetoothManager");
return false;
}
mBluetoothGattServer = bluetoothManager.openGattServer(mContext, internalGattServerCallback);
if (mBluetoothGattServer == null) {
LOG.error("Error opening Gatt Server");
return false;
}
for(BluetoothGattService service : mSupportedServerServices) {
mBluetoothGattServer.addService(service);
}
}
synchronized (mGattMonitor) { synchronized (mGattMonitor) {
// connectGatt with true doesn't really work ;( too often connection problems // connectGatt with true doesn't really work ;( too often connection problems
if (GBApplication.isRunningMarshmallowOrLater()) { if (GBApplication.isRunningMarshmallowOrLater()) {
@ -218,6 +286,12 @@ public final class BtLEQueue {
gatt.close(); gatt.close();
setDeviceConnectionState(State.NOT_CONNECTED); setDeviceConnectionState(State.NOT_CONNECTED);
} }
BluetoothGattServer gattServer = mBluetoothGattServer;
if (gattServer != null) {
mBluetoothGattServer = null;
gattServer.clearServices();
gattServer.close();
}
} }
} }
@ -226,9 +300,16 @@ public final class BtLEQueue {
internalGattCallback.reset(); internalGattCallback.reset();
mTransactions.clear(); mTransactions.clear();
mAbortTransaction = true; mAbortTransaction = true;
mAbortServerTransaction = true;
if (mWaitForActionResultLatch != null) { if (mWaitForActionResultLatch != null) {
mWaitForActionResultLatch.countDown(); mWaitForActionResultLatch.countDown();
} }
if (mWaitForServerActionResultLatch != null) {
mWaitForServerActionResultLatch.countDown();
}
synchronized(mTransactionMonitor) {
mTransactionMonitor.notify();
}
boolean wasInitialized = mGbDevice.isInitialized(); boolean wasInitialized = mGbDevice.isInitialized();
setDeviceConnectionState(State.NOT_CONNECTED); setDeviceConnectionState(State.NOT_CONNECTED);
@ -286,6 +367,24 @@ public final class BtLEQueue {
LOG.debug("about to add: " + transaction); LOG.debug("about to add: " + transaction);
if (!transaction.isEmpty()) { if (!transaction.isEmpty()) {
mTransactions.add(transaction); mTransactions.add(transaction);
synchronized(mTransactionMonitor) {
mTransactionMonitor.notify();
}
}
}
/**
* Adds a serverTransaction to the end of the queue
*
* @param transaction
*/
public void add(ServerTransaction transaction) {
LOG.debug("about to add: " + transaction);
if(!transaction.isEmpty()) {
mServerTransactions.add(transaction);
synchronized(mTransactionMonitor) {
mTransactionMonitor.notify();
}
} }
} }
@ -300,14 +399,25 @@ public final class BtLEQueue {
LOG.debug("about to insert: " + transaction); LOG.debug("about to insert: " + transaction);
if (!transaction.isEmpty()) { if (!transaction.isEmpty()) {
List<Transaction> tail = new ArrayList<>(mTransactions.size() + 2); List<Transaction> tail = new ArrayList<>(mTransactions.size() + 2);
mTransactions.drainTo(tail); //mTransactions.drainTo(tail);
for( Transaction t : mTransactions) {
tail.add(t);
}
mTransactions.clear();
mTransactions.add(transaction); mTransactions.add(transaction);
mTransactions.addAll(tail); mTransactions.addAll(tail);
synchronized(mTransactionMonitor) {
mTransactionMonitor.notify();
}
} }
} }
public void clear() { public void clear() {
mTransactions.clear(); mTransactions.clear();
mServerTransactions.clear();
synchronized(mTransactionMonitor) {
mTransactionMonitor.notify();
}
} }
/** /**
@ -332,6 +442,16 @@ public final class BtLEQueue {
return true; return true;
} }
private boolean checkCorrectBluetoothDevice(BluetoothDevice device) {
//BluetoothDevice clientDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
if(!device.getAddress().equals(mGbDevice.getAddress())) { // != clientDevice && clientDevice != null) {
LOG.info("Ignoring request from wrong Bluetooth device: " + device.getAddress());
return false;
}
return true;
}
// Implements callback methods for GATT events that the app cares about. For example, // Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered. // connection change and services discovered.
private final class InternalGattCallback extends BluetoothGattCallback { private final class InternalGattCallback extends BluetoothGattCallback {
@ -549,4 +669,90 @@ public final class BtLEQueue {
mTransactionGattCallback = null; mTransactionGattCallback = null;
} }
} }
// Implements callback methods for GATT server events that the app cares about. For example,
// connection change and read/write requests.
private final class InternalGattServerCallback extends BluetoothGattServerCallback {
private
@Nullable
GattServerCallback mTransactionGattCallback;
private final GattServerCallback mExternalGattServerCallback;
public InternalGattServerCallback(GattServerCallback externalGattServerCallback) {
mExternalGattServerCallback = externalGattServerCallback;
}
public void setTransactionGattCallback(@Nullable GattServerCallback callback) {
mTransactionGattCallback = callback;
}
private GattServerCallback getCallbackToUse() {
if (mTransactionGattCallback != null) {
return mTransactionGattCallback;
}
return mExternalGattServerCallback;
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
LOG.debug("gatt server connection state change, newState: " + newState + getStatusString(status));
if(!checkCorrectBluetoothDevice(device)) {
return;
}
if (status != BluetoothGatt.GATT_SUCCESS) {
LOG.warn("connection state event with error status " + status);
}
}
private String getStatusString(int status) {
return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
if(!checkCorrectBluetoothDevice(device)) {
return;
}
LOG.debug("characterstic read request: " + device.getAddress() + " characteristic: " + characteristic.getUuid());
if (getCallbackToUse() != null) {
getCallbackToUse().onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if(!checkCorrectBluetoothDevice(device)) {
return;
}
LOG.debug("characteristic write request: " + device.getAddress() + " characteristic: " + characteristic.getUuid());
if (getCallbackToUse() != null) {
getCallbackToUse().onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
}
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
if(!checkCorrectBluetoothDevice(device)) {
return;
}
LOG.debug("onDescriptorReadRequest: " + device.getAddress());
if(getCallbackToUse() != null) {
getCallbackToUse().onDescriptorReadRequest(device, requestId, offset, descriptor);
}
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if(!checkCorrectBluetoothDevice(device)) {
return;
}
LOG.debug("onDescriptorWriteRequest: " + device.getAddress());
if(getCallbackToUse() != null) {
getCallbackToUse().onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
}
}
} }

View File

@ -0,0 +1,75 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattService;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
/**
* The Bluedroid implementation only allows performing one GATT request at a time.
* As they are asynchronous anyway, we encapsulate every GATT request (read and write)
* inside a runnable action.
* <p/>
* These actions are then executed one after another, ensuring that every action's result
* has been posted before invoking the next action.
*/
public abstract class BtLEServerAction {
private final BluetoothDevice device;
private final long creationTimestamp;
public BtLEServerAction(BluetoothDevice device) {
this.device = device;
creationTimestamp = System.currentTimeMillis();
}
public BluetoothDevice getDevice() {
return this.device;
}
/**
* Returns true if this action expects an (async) result which must
* be waited for, before continuing with other actions.
* <p/>
* This is needed because the current Bluedroid stack can only deal
* with one single bluetooth operation at a time.
*/
public abstract boolean expectsResult();
/**
* Executes this action, e.g. reads or write a GATT characteristic.
*
* @return true if the action was successful, false otherwise
*/
public abstract boolean run(BluetoothGattServer server);
protected String getCreationTime() {
return DateTimeUtils.formatDateTime(new Date(creationTimestamp));
}
public String toString() {
return getCreationTime() + ":" + getClass().getSimpleName() + " on device: " + getDevice().getAddress();
}
}

View File

@ -0,0 +1,60 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServerCallback;
public interface GattServerCallback {
/**
* @param device
* @param status
* @param newState
* @see BluetoothGattServerCallback#onConnectionStateChange(BluetoothDevice, int, int)
*/
void onConnectionStateChange(BluetoothDevice device, int status, int newState);
/**
* @param device
* @param requestId
* @param offset
* @param characteristic
* @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int, BluetoothGattCharacteristic)
*/
boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic);
/**
* @param device
* @param requestId
* @param characteristic
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param value
* @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int, BluetoothGattCharacteristic, boolean, boolean, int, byte[])
*/
boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value);
/**
* @param device
* @param requestId
* @param offset
* @param descriptor
* @see BluetoothGattServerCallback#onDescriptorReadRequest(BluetoothDevice, int, int, BluetoothGattDescriptor)
*/
boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor);
/**
* @param device
* @param requestId
* @param descriptor
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param value
* @see BluetoothGattServerCallback#onDescriptorWriteRequest(BluetoothDevice, int, BluetoothGattDescriptor, boolean, boolean, int, byte[])
*/
boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value);
}

View File

@ -0,0 +1,83 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import androidx.annotation.Nullable;
/**
* Groups a bunch of {@link BtLEServerAction actions} together, making sure
* that upon failure of one action, all subsequent actions are discarded.
*
* @author TREND
*/
public class ServerTransaction {
private final String mName;
private final List<BtLEServerAction> mActions = new ArrayList<>(4);
private final long creationTimestamp = System.currentTimeMillis();
private
@Nullable
GattServerCallback gattCallback;
public ServerTransaction(String taskName) {
this.mName = taskName;
}
public String getTaskName() {
return mName;
}
public void add(BtLEServerAction action) {
mActions.add(action);
}
public List<BtLEServerAction> getActions() {
return Collections.unmodifiableList(mActions);
}
public boolean isEmpty() {
return mActions.isEmpty();
}
protected String getCreationTime() {
return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp));
}
@Override
public String toString() {
return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size());
}
public void setGattCallback(@Nullable GattServerCallback callback) {
gattCallback = callback;
}
/**
* Returns the GattServerCallback for this transaction, or null if none.
*/
public
@Nullable
GattServerCallback getGattCallback() {
return gattCallback;
}
}

View File

@ -0,0 +1,87 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ServerResponseAction;
public class ServerTransactionBuilder {
private static final Logger LOG = LoggerFactory.getLogger(ServerTransactionBuilder.class);
private final ServerTransaction mTransaction;
private boolean mQueued;
public ServerTransactionBuilder(String taskName) {
mTransaction = new ServerTransaction(taskName);
}
public ServerTransactionBuilder writeServerResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] data) {
if(device == null) {
LOG.warn("Unable to write to device: null");
return this;
}
ServerResponseAction action = new ServerResponseAction(device, requestId, status, offset, data);
return add(action);
}
public ServerTransactionBuilder add(BtLEServerAction action) {
mTransaction.add(action);
return this;
}
/**
* Sets a GattServerCallback instance that will be called when the transaction is executed,
* resulting in GattServerCallback events.
*
* @param callback the callback to set, may be null
*/
public void setGattCallback(@Nullable GattServerCallback callback) {
mTransaction.setGattCallback(callback);
}
public
@Nullable
GattServerCallback getGattCallback() {
return mTransaction.getGattCallback();
}
/**
* To be used as the final step to execute the transaction by the given queue.
*
* @param queue
*/
public void queue(BtLEQueue queue) {
if (mQueued) {
throw new IllegalStateException("This builder had already been queued. You must not reuse it.");
}
mQueued = true;
queue.add(mTransaction);
}
public ServerTransaction getTransaction() {
return mTransaction;
}
public String getTaskName() {
return mTransaction.getTaskName();
}
}

View File

@ -0,0 +1,72 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Uwe Hermann
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
/**
* Invokes a response on a given GATT characteristic read.
* The result status will be made available asynchronously through the
* {@link BluetoothGattCallback}
*/
public class ServerResponseAction extends BtLEServerAction {
private static final Logger LOG = LoggerFactory.getLogger(ServerResponseAction.class);
private final byte[] value;
private final int requestId;
private final int status;
private final int offset;
public ServerResponseAction(BluetoothDevice device, int requestId, int status, int offset, byte[] data) {
super(device);
this.value = data;
this.requestId = requestId;
this.status = status;
this.offset = offset;
}
@Override
public boolean run(BluetoothGattServer server) {
return writeValue(server, getDevice(), requestId, status, offset, value);
}
protected boolean writeValue(BluetoothGattServer gattServer, BluetoothDevice device, int requestId, int status, int offset, byte[] value) {
if (LOG.isDebugEnabled()) {
LOG.debug("writing to server: " + device.getAddress() + ": " + Logging.formatBytes(value));
}
return gattServer.sendResponse(device, requestId, 0, offset, value);
}
protected final byte[] getValue() {
return value;
}
@Override
public boolean expectsResult() {
return false;
}
}