Ble updates

- stop Thread.sleep()ing and use CountDownLatches for connection
    and service discovery state
   - prepare BleIO for reading/writing
   - define CharacteristicType for the CMD/DATA characteristics
   - prepare BlePacket for DATA packets(the equivalent of BleCommand)
This commit is contained in:
Andrei Vereha 2021-02-24 11:49:24 +01:00
parent d9cebbbad7
commit 1ca69ec414
11 changed files with 276 additions and 87 deletions

View file

@ -1,7 +1,55 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothProfile;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
public class BleCommCallbacks extends BluetoothGattCallback { public class BleCommCallbacks extends BluetoothGattCallback {
private final CountDownLatch serviceDiscoveryComplete;
private final CountDownLatch connected;
private final AAPSLogger aapsLogger;
private final Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets;
public BleCommCallbacks(AAPSLogger aapsLogger, Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets) {
this.serviceDiscoveryComplete = new CountDownLatch(1);
this.connected = new CountDownLatch(1);
this.aapsLogger = aapsLogger;
this.incomingPackets = incomingPackets;
}
@Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
this.aapsLogger.debug(LTag.PUMPBTCOMM,"OnConnectionStateChange discovered with status/state"+status+"/"+newState);
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
this.connected.countDown();
}
}
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
this.aapsLogger.debug(LTag.PUMPBTCOMM,"OnServicesDiscovered with status"+status);
if (status == gatt.GATT_SUCCESS) {
this.serviceDiscoveryComplete.countDown();
}
}
public void waitForConnection(int timeout_ms)
throws InterruptedException {
this.connected.await(timeout_ms, TimeUnit.MILLISECONDS);
}
public void waitForServiceDiscovery(int timeout_ms)
throws InterruptedException {
this.serviceDiscoveryComplete.await(timeout_ms, TimeUnit.MILLISECONDS);
}
} }

View file

@ -0,0 +1,76 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleException;
public class BleIO {
private static final int DEFAULT_IO_TIMEOUT_MS = 1000;
private final AAPSLogger aapsLogger;
private final Map<CharacteristicType, BluetoothGattCharacteristic> chars;
private final Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets;
private final BluetoothGatt gatt;
public BleIO(AAPSLogger aapsLogger, Map<CharacteristicType, BluetoothGattCharacteristic> chars, Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets, BluetoothGatt gatt) {
this.aapsLogger = aapsLogger;
this.chars = chars;
this.incomingPackets = incomingPackets;
this.gatt = gatt;
}
/***
*
* @param characteristic where to read from(CMD or DATA)
* @return a byte array with the received data
*/
public byte[] receiveData(CharacteristicType characteristic) {
return null;
}
/***
*
* @param characteristic where to write to(CMD or DATA)
* @param packet the data to send
* @throws CouldNotSendBleException
*/
public void sendAndConfirmData(CharacteristicType characteristic, byte[] packet)
throws CouldNotSendBleException {
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on " + characteristic.name() + " :: " +packet.toString());
BluetoothGattCharacteristic ch = chars.get(characteristic);
boolean set = ch.setValue(packet);
if (!set) {
throw new CouldNotSendBleException("setValue");
}
boolean sent = this.gatt.writeCharacteristic(ch);
if (!sent) {
throw new CouldNotSendBleException("writeCharacteristic");
}
// TODO: wait for confirmation callback
}
/**
* Called before sending a new message.
* The incoming queues should be empty, so we log when they are not.
*/
public void flushIncomingQueues() {
}
/**
* Enable intentions on the characteristics.
* This will signal the pod it can start sending back data
*/
public void readyToRead()
throws CouldNotSendBleException {
}
}

View file

@ -3,40 +3,34 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import java.math.BigInteger; import java.util.Collections;
import java.util.UUID; import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand.BleCommand; import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand.BleCommandHello; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand.BleCommandType;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CharacteristicNotFoundException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleCmdException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleException; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ServiceNotFoundException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner;
@Singleton @Singleton
public class BleManager implements OmnipodDashCommunicationManager { public class BleManager implements OmnipodDashCommunicationManager {
private static final int CONNECT_TIMEOUT_MS = 5000; private static final int CONNECT_TIMEOUT_MS = 5000;
private static final int DISCOVER_SERVICES_TIMEOUT_MS = 5000;
private static final String SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f";
private static final String CMD_CHARACTERISTIC_UUID = "1a7e-2441-e3ed-4464-8b7e-751e03d0dc5f";
private static final String DATA_CHARACTERISTIC_UUID = "1a7e-2442-e3ed-4464-8b7e-751e03d0dc5f";
private static final int CONTROLLER_ID = 4242; // TODO read from preferences or somewhere else. private static final int CONTROLLER_ID = 4242; // TODO read from preferences or somewhere else.
private static BleManager instance = null; private static BleManager instance = null;
private final Context context; private final Context context;
private final BluetoothAdapter bluetoothAdapter; private final BluetoothAdapter bluetoothAdapter;
@ -44,9 +38,7 @@ public class BleManager implements OmnipodDashCommunicationManager {
@Inject AAPSLogger aapsLogger; @Inject AAPSLogger aapsLogger;
private String podAddress; private String podAddress;
private BluetoothGatt gatt; private BluetoothGatt gatt;
private BleIO bleio;
private BluetoothGattCharacteristic cmdCharacteristic;
private BluetoothGattCharacteristic dataCharacteristic;
@Inject @Inject
public BleManager(Context context) { public BleManager(Context context) {
@ -66,13 +58,6 @@ public class BleManager implements OmnipodDashCommunicationManager {
return ret; return ret;
} }
private static UUID uuidFromString(String s) {
return new UUID(
new BigInteger(s.replace("-", "").substring(0, 16), 16).longValue(),
new BigInteger(s.replace("-", "").substring(16), 16).longValue()
);
}
public void activateNewPod() public void activateNewPod()
throws InterruptedException, throws InterruptedException,
ScanFailException, ScanFailException,
@ -82,78 +67,46 @@ public class BleManager implements OmnipodDashCommunicationManager {
PodScanner podScanner = new PodScanner(this.aapsLogger, this.bluetoothAdapter); PodScanner podScanner = new PodScanner(this.aapsLogger, this.bluetoothAdapter);
this.podAddress = podScanner.scanForPod(PodScanner.SCAN_FOR_SERVICE_UUID, PodScanner.POD_ID_NOT_ACTIVATED).getScanResult().getDevice().getAddress(); this.podAddress = podScanner.scanForPod(PodScanner.SCAN_FOR_SERVICE_UUID, PodScanner.POD_ID_NOT_ACTIVATED).getScanResult().getDevice().getAddress();
// For tests: this.podAddress = "B8:27:EB:1D:7E:BB"; // For tests: this.podAddress = "B8:27:EB:1D:7E:BB";
this.connect(); this.connect_();
// do the dance: send SP0, SP1, etc
// get and save LTK
} }
public void connect() public void connect_()
throws FailedToConnectException, throws FailedToConnectException,
CouldNotSendBleException { CouldNotSendBleException,
InterruptedException {
// TODO: locking? // TODO: locking?
BluetoothDevice podDevice = this.bluetoothAdapter.getRemoteDevice(this.podAddress); BluetoothDevice podDevice = this.bluetoothAdapter.getRemoteDevice(this.podAddress);
BluetoothGattCallback bleCommCallback = new BleCommCallbacks();
Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets = new EnumMap<CharacteristicType, BlockingQueue<byte[]>>(CharacteristicType.class);
incomingPackets.put(CharacteristicType.CMD, new LinkedBlockingDeque<>());
incomingPackets.put(CharacteristicType.DATA, new LinkedBlockingDeque<>());
incomingPackets = Collections.unmodifiableMap(incomingPackets);
BleCommCallbacks bleCommCallbacks = new BleCommCallbacks(aapsLogger, incomingPackets);
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to " + this.podAddress); aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to " + this.podAddress);
gatt = podDevice.connectGatt(this.context, true, bleCommCallback, BluetoothDevice.TRANSPORT_LE); boolean autoConnect = true;
if (BuildConfig.DEBUG) {
try { autoConnect = false;
Thread.sleep(CONNECT_TIMEOUT_MS); // TODO: remove this in the future
} catch (InterruptedException e) { // it's easier to start testing from scratch on each run.
// we get interrupted on successful connection
// TODO: interrupt this thread onConnect()
} }
gatt = podDevice.connectGatt(this.context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE);
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS);
int connectionState = this.bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT); int connectionState = this.bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT);
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: " + connectionState); aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: " + connectionState);
if (connectionState != BluetoothProfile.STATE_CONNECTED) { if (connectionState != BluetoothProfile.STATE_CONNECTED) {
throw new FailedToConnectException(this.podAddress); throw new FailedToConnectException(this.podAddress);
} }
this.discoverServicesAndSayHello(gatt);
} ServiceDiscoverer discoverer = new ServiceDiscoverer(this.aapsLogger, gatt, bleCommCallbacks);
Map<CharacteristicType, BluetoothGattCharacteristic> chars = discoverer.discoverServices();
private void discoverServicesAndSayHello(BluetoothGatt gatt) this.bleio = new BleIO(aapsLogger, chars, incomingPackets, gatt);
throws FailedToConnectException, this.aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod");
CouldNotSendBleException { this.bleio.sendAndConfirmData(CharacteristicType.CMD, new BleCommandHello(CONTROLLER_ID).asByteArray());
gatt.discoverServices();
try {
Thread.sleep(CONNECT_TIMEOUT_MS);
} catch (InterruptedException e) {
// we get interrupted on successfull connection
// TODO: interrupt this thread onConnect()
}
BluetoothGattService service = gatt.getService(uuidFromString(SERVICE_UUID));
if (service == null) {
throw new ServiceNotFoundException(SERVICE_UUID);
}
BluetoothGattCharacteristic cmdChar = service.getCharacteristic(uuidFromString(CMD_CHARACTERISTIC_UUID));
if (cmdChar == null) {
throw new CharacteristicNotFoundException(CMD_CHARACTERISTIC_UUID);
}
BluetoothGattCharacteristic dataChar = service.getCharacteristic(uuidFromString(DATA_CHARACTERISTIC_UUID));
if (dataChar == null) {
throw new CharacteristicNotFoundException(DATA_CHARACTERISTIC_UUID);
}
this.cmdCharacteristic = cmdChar;
this.dataCharacteristic = dataChar;
BleCommand hello = new BleCommandHello(CONTROLLER_ID);
if (!this.sendCmd(hello.asByteArray())) {
throw new CouldNotSendBleCmdException();
}
aapsLogger.debug(LTag.PUMPBTCOMM, "saying hello to the pod" + hello.asByteArray());
}
private boolean sendCmd(byte[] payload) {
// TODO move out of here
this.cmdCharacteristic.setValue(payload);
boolean ret = this.gatt.writeCharacteristic(cmdCharacteristic);
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command status. data:" + payload.toString() + "status: " + ret);
return ret;
} }
} }

View file

@ -0,0 +1,35 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import java.math.BigInteger;
import java.util.UUID;
public enum CharacteristicType {
CMD("1a7e-2441-e3ed-4464-8b7e-751e03d0dc5f"),
DATA("1a7e-2442-e3ed-4464-8b7e-751e03d0dc5f");
public final String value;
CharacteristicType(String value) {
this.value = value;
}
public static CharacteristicType byValue(byte value) {
for (CharacteristicType type : values()) {
if (type.value.equals(value)) {
return type;
}
}
throw new IllegalArgumentException("Unknown Characteristic Type: " + value);
}
public String getValue() {
return this.value;
}
public UUID getUUID() {
return new UUID(
new BigInteger(this.value.replace("-", "").substring(0, 16), 16).longValue(),
new BigInteger(this.value.replace("-", "").substring(16), 16).longValue()
);
}
}

View file

@ -0,0 +1,74 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import java.math.BigInteger;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.UUID;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CharacteristicNotFoundException;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ServiceNotFoundException;
public class ServiceDiscoverer {
private static final String SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f";
private static final int DISCOVER_SERVICES_TIMEOUT_MS = 5000;
private final BluetoothGatt gatt;
private final BleCommCallbacks bleCallbacks;
private final AAPSLogger logger;
private Map<CharacteristicType, BluetoothGattCharacteristic> chars;
public ServiceDiscoverer(AAPSLogger logger, BluetoothGatt gatt, BleCommCallbacks bleCallbacks) {
this.gatt = gatt;
this.bleCallbacks = bleCallbacks;
this.logger = logger;
}
private static UUID uuidFromString(String s) {
return new UUID(
new BigInteger(s.replace("-", "").substring(0, 16), 16).longValue(),
new BigInteger(s.replace("-", "").substring(16), 16).longValue()
);
}
/***
* This is first step after connection establishment
*/
public Map<CharacteristicType, BluetoothGattCharacteristic> discoverServices()
throws InterruptedException,
ServiceNotFoundException,
CharacteristicNotFoundException {
logger.debug(LTag.PUMPBTCOMM, "Discovering services");
gatt.discoverServices();
this.bleCallbacks.waitForServiceDiscovery(DISCOVER_SERVICES_TIMEOUT_MS);
logger.debug(LTag.PUMPBTCOMM, "Services discovered");
BluetoothGattService service = gatt.getService(
uuidFromString(SERVICE_UUID));
if (service == null) {
throw new ServiceNotFoundException(SERVICE_UUID);
}
BluetoothGattCharacteristic cmdChar = service.getCharacteristic(CharacteristicType.CMD.getUUID());
if (cmdChar == null) {
throw new CharacteristicNotFoundException(CharacteristicType.CMD.getValue());
}
BluetoothGattCharacteristic dataChar = service.getCharacteristic(CharacteristicType.DATA.getUUID());
if (dataChar == null) {
throw new CharacteristicNotFoundException(CharacteristicType.DATA.getValue());
}
Map<CharacteristicType, BluetoothGattCharacteristic> chars = new EnumMap(CharacteristicType.class);
chars.put(CharacteristicType.CMD, cmdChar);
chars.put(CharacteristicType.DATA, dataChar);
this.chars = Collections.unmodifiableMap(chars);
return this.chars;
}
}

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
public enum BleCommandType { public enum BleCommandType {
RTS((byte) 0x00), RTS((byte) 0x00),

View file

@ -1,4 +0,0 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
public class CouldNotSendBleCmdException extends CouldNotSendBleException {
}

View file

@ -1,4 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
public class CouldNotSendBleException extends Exception { public class CouldNotSendBleException extends Exception {
public CouldNotSendBleException(String msg) {
super(msg);
}
} }

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet;
public class BlePacket {
}