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:
parent
d9cebbbad7
commit
1ca69ec414
11 changed files with 276 additions and 87 deletions
|
@ -1,7 +1,55 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -3,40 +3,34 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
|
|||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.UUID;
|
||||
import java.util.Collections;
|
||||
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.Singleton;
|
||||
|
||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||
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.driver.comm.blecommand.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.BuildConfig;
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello;
|
||||
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.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;
|
||||
|
||||
@Singleton
|
||||
public class BleManager implements OmnipodDashCommunicationManager {
|
||||
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 BleManager instance = null;
|
||||
private final Context context;
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
|
@ -44,9 +38,7 @@ public class BleManager implements OmnipodDashCommunicationManager {
|
|||
@Inject AAPSLogger aapsLogger;
|
||||
private String podAddress;
|
||||
private BluetoothGatt gatt;
|
||||
|
||||
private BluetoothGattCharacteristic cmdCharacteristic;
|
||||
private BluetoothGattCharacteristic dataCharacteristic;
|
||||
private BleIO bleio;
|
||||
|
||||
@Inject
|
||||
public BleManager(Context context) {
|
||||
|
@ -66,13 +58,6 @@ public class BleManager implements OmnipodDashCommunicationManager {
|
|||
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()
|
||||
throws InterruptedException,
|
||||
ScanFailException,
|
||||
|
@ -82,78 +67,46 @@ public class BleManager implements OmnipodDashCommunicationManager {
|
|||
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();
|
||||
// For tests: this.podAddress = "B8:27:EB:1D:7E:BB";
|
||||
this.connect();
|
||||
// do the dance: send SP0, SP1, etc
|
||||
// get and save LTK
|
||||
this.connect_();
|
||||
}
|
||||
|
||||
public void connect()
|
||||
public void connect_()
|
||||
throws FailedToConnectException,
|
||||
CouldNotSendBleException {
|
||||
CouldNotSendBleException,
|
||||
InterruptedException {
|
||||
// TODO: locking?
|
||||
|
||||
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);
|
||||
gatt = podDevice.connectGatt(this.context, true, bleCommCallback, BluetoothDevice.TRANSPORT_LE);
|
||||
|
||||
try {
|
||||
Thread.sleep(CONNECT_TIMEOUT_MS);
|
||||
} catch (InterruptedException e) {
|
||||
// we get interrupted on successful connection
|
||||
// TODO: interrupt this thread onConnect()
|
||||
boolean autoConnect = true;
|
||||
if (BuildConfig.DEBUG) {
|
||||
autoConnect = false;
|
||||
// TODO: remove this in the future
|
||||
// it's easier to start testing from scratch on each run.
|
||||
}
|
||||
gatt = podDevice.connectGatt(this.context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE);
|
||||
|
||||
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS);
|
||||
|
||||
int connectionState = this.bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT);
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: " + connectionState);
|
||||
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
||||
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)
|
||||
throws FailedToConnectException,
|
||||
CouldNotSendBleException {
|
||||
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;
|
||||
this.bleio = new BleIO(aapsLogger, chars, incomingPackets, gatt);
|
||||
this.aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod");
|
||||
this.bleio.sendAndConfirmData(CharacteristicType.CMD, new BleCommandHello(CONTROLLER_ID).asByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 {
|
||||
RTS((byte) 0x00),
|
|
@ -1,4 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
||||
|
||||
public class CouldNotSendBleCmdException extends CouldNotSendBleException {
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
||||
|
||||
public class CouldNotSendBleException extends Exception {
|
||||
public CouldNotSendBleException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet;
|
||||
|
||||
public class BlePacket {
|
||||
}
|
Loading…
Reference in a new issue