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:
parent
05fa8e846b
commit
6577ad69b0
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user