ble: standardize server actions (#5157)

This commit is contained in:
Thomas Kuehne
2025-09-26 22:39:53 +02:00
committed by José Rebelo
parent 5526e9528e
commit f03ac358fa
6 changed files with 126 additions and 73 deletions
@@ -1,4 +1,4 @@
/* Copyright (C) 2019-2024 Andreas Böhler /* Copyright (C) 2019-2025 Andreas Böhler, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServer;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
/** /**
@@ -64,7 +66,9 @@ public abstract class BtLEServerAction {
return DateTimeUtils.formatLocalTime(creationTimestamp); return DateTimeUtils.formatLocalTime(creationTimestamp);
} }
@NonNull
@Override
public String toString() { public String toString() {
return getCreationTime() + ":" + getClass().getSimpleName() + " on device: " + getDevice().getAddress(); return getCreationTime() + " " + getClass().getSimpleName() + " on " + getDevice().getAddress();
} }
} }
@@ -1,4 +1,4 @@
/* Copyright (C) 2019-2024 Andreas Böhler, Damien Gaignon /* Copyright (C) 2019-2025 Andreas Böhler, Damien Gaignon, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -17,11 +17,15 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle; package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyCharacteristicChangedAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ServerResponseAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ServerResponseAction;
public class ServerTransactionBuilder { public class ServerTransactionBuilder {
@@ -34,6 +38,7 @@ public class ServerTransactionBuilder {
mTransaction = new ServerTransaction(taskName); mTransaction = new ServerTransaction(taskName);
} }
@NonNull
public ServerTransactionBuilder writeServerResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] data) { public ServerTransactionBuilder writeServerResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] data) {
if(device == null) { if(device == null) {
LOG.warn("Unable to write to device: null"); LOG.warn("Unable to write to device: null");
@@ -43,7 +48,20 @@ public class ServerTransactionBuilder {
return add(action); return add(action);
} }
public ServerTransactionBuilder add(BtLEServerAction action) { @NonNull
public ServerTransactionBuilder notifyCharacteristicChanged(@NonNull BluetoothDevice device,
@NonNull BluetoothGattCharacteristic characteristic,
@NonNull byte[] value) {
if (device == null) {
LOG.warn("Unable to write to device: null");
return this;
}
BtLEServerAction action = new NotifyCharacteristicChangedAction(device, characteristic, value);
return add(action);
}
@NonNull
public ServerTransactionBuilder add(@NonNull BtLEServerAction action) {
mTransaction.add(action); mTransaction.add(action);
return this; return this;
} }
@@ -66,10 +84,8 @@ public class ServerTransactionBuilder {
/** /**
* To be used as the final step to execute the transaction by the given queue. * To be used as the final step to execute the transaction by the given queue.
*
* @param queue
*/ */
public void queue(BtLEQueue queue) { public void queue(@NonNull BtLEQueue queue) {
if (mQueued) { if (mQueued) {
throw new IllegalStateException("This builder had already been queued. You must not reuse it."); throw new IllegalStateException("This builder had already been queued. You must not reuse it.");
} }
@@ -0,0 +1,84 @@
/* Copyright (C) 2023-2025 Frank Ertl, Thomas Kuehne
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothStatusCodes;
import android.os.Build;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class NotifyCharacteristicChangedAction extends BtLEServerAction {
private static final Logger LOG = LoggerFactory.getLogger(NotifyCharacteristicChangedAction.class);
private final BluetoothGattCharacteristic characteristic;
private final byte[] value;
public NotifyCharacteristicChangedAction(@NonNull BluetoothDevice device,
@NonNull BluetoothGattCharacteristic characteristic,
byte[] value) {
super(device);
this.characteristic = characteristic;
this.value = value;
}
@Override
public boolean expectsResult() {
return false;
}
@SuppressLint("MissingPermission")
@Override
public boolean run(BluetoothGattServer server) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
int status = server.notifyCharacteristicChanged(getDevice(), characteristic, false, value);
if (status == BluetoothStatusCodes.SUCCESS) {
return true;
}
LOG.error("notifyCharacteristicChanged {} failed: {}",
characteristic.getUuid(), BleNamesResolver.getBluetoothStatusString(status));
return false;
}
if (characteristic.setValue(value)) {
if (server.notifyCharacteristicChanged(getDevice(), characteristic, false)) {
return true;
}
LOG.error("notifyCharacteristicChanged {} failed", characteristic.getUuid());
} else {
LOG.error("setting value of characteristic {} failed", characteristic.getUuid());
}
return false;
}
@NonNull
@Override
public String toString() {
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
return super.toString() + " " + uuid + " - " + GB.hexdump(value);
}
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2019-2024 Andreas Böhler /* Copyright (C) 2019-2025 Andreas Böhler, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -16,24 +16,19 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServer;
import org.slf4j.Logger; import androidx.annotation.NonNull;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/** /**
* Invokes a response on a given GATT characteristic read. * 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 { public class ServerResponseAction extends BtLEServerAction {
private static final Logger LOG = LoggerFactory.getLogger(ServerResponseAction.class);
private final byte[] value; private final byte[] value;
private final int requestId; private final int requestId;
private final int status; private final int status;
@@ -47,17 +42,10 @@ public class ServerResponseAction extends BtLEServerAction {
this.offset = offset; this.offset = offset;
} }
@SuppressLint("MissingPermission")
@Override @Override
public boolean run(BluetoothGattServer server) { public boolean run(BluetoothGattServer server) {
return writeValue(server, getDevice(), requestId, status, offset, value); return server.sendResponse(getDevice(), requestId, status, offset, getValue());
}
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, status, offset, value);
} }
protected final byte[] getValue() { protected final byte[] getValue() {
@@ -68,4 +56,10 @@ public class ServerResponseAction extends BtLEServerAction {
public boolean expectsResult() { public boolean expectsResult() {
return false; return false;
} }
@NonNull
@Override
public String toString() {
return super.toString() + " #" + requestId + " - " + GB.hexdump(getValue());
}
} }
@@ -58,7 +58,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType; import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsServerAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID; import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ActivitySampleHandler; import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ActivitySampleHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.BatteryStateHandler; import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.BatteryStateHandler;
@@ -441,8 +440,8 @@ public class WithingsSteelHRDeviceSupport extends AbstractBTLESingleDeviceSuppor
public void sendAncsNotificationSourceNotification(NotificationSource notificationSource) { public void sendAncsNotificationSourceNotification(NotificationSource notificationSource) {
try { try {
ServerTransactionBuilder builder = performServer("notificationSourceNotification"); ServerTransactionBuilder builder = performServer("notificationSourceNotification");
notificationSourceCharacteristic.setValue(notificationSource.serialize()); byte[] data = notificationSource.serialize();
builder.add(new WithingsServerAction(device, notificationSourceCharacteristic)); builder.notifyCharacteristicChanged(device, notificationSourceCharacteristic, data);
builder.queue(getQueue()); builder.queue(getQueue());
} catch (IOException e) { } catch (IOException e) {
logger.error("Could not send notification.", e); logger.error("Could not send notification.", e);
@@ -454,8 +453,7 @@ public class WithingsSteelHRDeviceSupport extends AbstractBTLESingleDeviceSuppor
try { try {
ServerTransactionBuilder builder = performServer("dataSourceNotification"); ServerTransactionBuilder builder = performServer("dataSourceNotification");
byte[] data = response.serialize(); byte[] data = response.serialize();
dataSourceCharacteristic.setValue(response.serialize()); builder.notifyCharacteristicChanged(device, dataSourceCharacteristic, data);
builder.add(new WithingsServerAction(device, dataSourceCharacteristic));
builder.queue(getQueue()); builder.queue(getQueue());
} catch (IOException e) { } catch (IOException e) {
logger.error("Could not send notification.", e); logger.error("Could not send notification.", e);
@@ -1,43 +0,0 @@
/* Copyright (C) 2023-2024 Frank Ertl
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
public class WithingsServerAction extends BtLEServerAction
{
private BluetoothGattCharacteristic characteristic;
public WithingsServerAction(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
super(device);
this.characteristic = characteristic;
}
@Override
public boolean expectsResult() {
return false;
}
@Override
public boolean run(BluetoothGattServer server) {
return server.notifyCharacteristicChanged(getDevice(), characteristic, false);
}
}