diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleCommCallbacks.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleCommCallbacks.java deleted file mode 100644 index aa8044266f..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleCommCallbacks.java +++ /dev/null @@ -1,55 +0,0 @@ -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> incomingPackets; - - public BleCommCallbacks(AAPSLogger aapsLogger, Map> 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); - } - -} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleIO.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleIO.java deleted file mode 100644 index 40c76d5de2..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleIO.java +++ /dev/null @@ -1,76 +0,0 @@ -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 chars; - private final Map> incomingPackets; - private final BluetoothGatt gatt; - - - public BleIO(AAPSLogger aapsLogger, Map chars, Map> 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 { - - } -} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleManager.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleManager.java index fdacd4e395..35164c4cc1 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleManager.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleManager.java @@ -13,6 +13,7 @@ import java.util.EnumMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeoutException; import javax.inject.Inject; import javax.inject.Singleton; @@ -20,7 +21,14 @@ import javax.inject.Singleton; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.BleIOBusyException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWrite; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotEnableNotifications; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DescriptorNotFoundException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO; 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; @@ -62,18 +70,22 @@ public class BleManager implements OmnipodDashCommunicationManager { throws InterruptedException, ScanFailException, FailedToConnectException, - CouldNotSendBleException { + CouldNotSendBleException, + BleIOBusyException, + TimeoutException, + CouldNotConfirmWrite, CouldNotEnableNotifications, DescriptorNotFoundException, CouldNotConfirmDescriptorWriteException { this.aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation"); 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_(); + this.connect(); } - public void connect_() + public void connect() throws FailedToConnectException, CouldNotSendBleException, - InterruptedException { + InterruptedException, + BleIOBusyException, TimeoutException, CouldNotConfirmWrite, CouldNotEnableNotifications, DescriptorNotFoundException, CouldNotConfirmDescriptorWriteException { // TODO: locking? BluetoothDevice podDevice = this.bluetoothAdapter.getRemoteDevice(this.podAddress); @@ -105,8 +117,9 @@ public class BleManager implements OmnipodDashCommunicationManager { ServiceDiscoverer discoverer = new ServiceDiscoverer(this.aapsLogger, gatt, bleCommCallbacks); Map chars = discoverer.discoverServices(); - this.bleio = new BleIO(aapsLogger, chars, incomingPackets, gatt); + this.bleio = new BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks); this.aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod"); - this.bleio.sendAndConfirmData(CharacteristicType.CMD, new BleCommandHello(CONTROLLER_ID).asByteArray()); + this.bleio.sendAndConfirmPacket(CharacteristicType.CMD, new BleCommandHello(CONTROLLER_ID).asByteArray()); + this.bleio.readyToRead(); } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/CharacteristicType.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/CharacteristicType.java index cdc4bdb46b..73bcc9de61 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/CharacteristicType.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/CharacteristicType.java @@ -4,8 +4,8 @@ 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"); + CMD("1a7e2441-e3ed-4464-8b7e-751e03d0dc5f"), + DATA("1a7e2442-e3ed-4464-8b7e-751e03d0dc5f"); public final String value; @@ -13,7 +13,7 @@ public enum CharacteristicType { this.value = value; } - public static CharacteristicType byValue(byte value) { + public static CharacteristicType byValue(String value) { for (CharacteristicType type : values()) { if (type.value.equals(value)) { return type; diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ServiceDiscoverer.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ServiceDiscoverer.java index c30cfce8e9..13828db4d0 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ServiceDiscoverer.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/ServiceDiscoverer.java @@ -12,6 +12,7 @@ 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.callbacks.BleCommCallbacks; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CharacteristicNotFoundException; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ServiceNotFoundException; diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.java new file mode 100644 index 0000000000..28a202606e --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.java @@ -0,0 +1,147 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothProfile; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.CharacteristicType; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWrite; + +public class BleCommCallbacks extends BluetoothGattCallback { + private final static int WRITE_CONFIRM_TIMEOUT_MS = 10; // the other thread should be waiting for the exchange + + private final CountDownLatch serviceDiscoveryComplete; + private final CountDownLatch connected; + private final AAPSLogger aapsLogger; + private final Map> incomingPackets; + private final BlockingQueue writeQueue; + private final BlockingQueue descriptorWriteQueue; + + public BleCommCallbacks(AAPSLogger aapsLogger, Map> incomingPackets) { + this.serviceDiscoveryComplete = new CountDownLatch(1); + this.connected = new CountDownLatch(1); + this.aapsLogger = aapsLogger; + this.incomingPackets = incomingPackets; + this.writeQueue = new LinkedBlockingQueue<>(1); + this.descriptorWriteQueue = new LinkedBlockingQueue<>(1); + } + + @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 == BluetoothGatt.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); + } + + public void confirmWrite(CharacteristicType characteristicType, byte[] expectedPayload, int timeout_ms) throws InterruptedException, TimeoutException, CouldNotConfirmWrite { + CharacteristicWriteConfirmation received = this.writeQueue.poll(timeout_ms, TimeUnit.MILLISECONDS); + if (received == null ) { + throw new TimeoutException(); + } + if (!Arrays.equals(expectedPayload, received.payload)) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm write. Got " + received.payload + ".Excepted: " + expectedPayload + ". Status: "+received.status); + throw new CouldNotConfirmWrite(expectedPayload, received.payload); + } + this.aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed write with value: " + received.payload); + } + + @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicWrite(gatt, characteristic, status); + byte[] received = null; + + if (status == BluetoothGatt.GATT_SUCCESS) { + received = characteristic.getValue(); + this.aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicWrite value " + characteristic.getStringValue(0)); + } + + this.aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicWrite with status/char/value " + + status + "/" + + CharacteristicType.byValue(characteristic.getUuid().toString()) + "/" + + received); + try { + if (this.writeQueue.size() > 0) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Write confirm queue should be empty. found: "+ this.writeQueue.size()); + this.writeQueue.clear(); + } + boolean offered = this.writeQueue.offer(new CharacteristicWriteConfirmation(received, status), WRITE_CONFIRM_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (!offered) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation"); + } + } catch (InterruptedException e) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending write confirmation"); + } + } + + @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + super.onCharacteristicChanged(gatt, characteristic); + + byte[] payload = characteristic.getValue(); + CharacteristicType characteristicType = CharacteristicType.byValue(characteristic.getUuid().toString()); + this.aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicChanged with char/value " + + characteristicType + "/" + + payload); + this.incomingPackets.get(characteristicType).add(payload); + } + + public void confirmWriteDescriptor(String descriptorUUID, int timeout_ms) throws InterruptedException, CouldNotConfirmDescriptorWriteException { + DescriptorWriteConfirmation confirmed = this.descriptorWriteQueue.poll(timeout_ms, TimeUnit.MILLISECONDS); + if (!descriptorUUID.equals(confirmed.uuid)) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm descriptor write. Got " + confirmed.uuid + ".Expected: " + descriptorUUID + ". Status: " + confirmed.status); + throw new CouldNotConfirmDescriptorWriteException(confirmed.uuid, descriptorUUID); + } + } + + @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorWrite(gatt, descriptor, status); + String uuid = null; + if (status == BluetoothGatt.GATT_SUCCESS) { + uuid = descriptor.getUuid().toString(); + } + DescriptorWriteConfirmation confirmation = new DescriptorWriteConfirmation(status, uuid); + try { + if (this.descriptorWriteQueue.size() > 0) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Descriptor write queue should be empty, found: "+ this.descriptorWriteQueue.size()); + this.descriptorWriteQueue.clear(); + } + + boolean offered = this.descriptorWriteQueue.offer(confirmation, WRITE_CONFIRM_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (!offered) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed descriptor write confirmation"); + } + } catch (InterruptedException e) { + this.aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending descriptor write confirmation"); + } + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.java new file mode 100644 index 0000000000..cf2e8bff77 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks; + +public class CharacteristicWriteConfirmation { + public byte[] payload; + public int status; + + public CharacteristicWriteConfirmation(byte[] payload, int status) { + this.payload = payload; + this.status = status; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.java new file mode 100644 index 0000000000..88ae897b1b --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks; + +public class DescriptorWriteConfirmation { + public int status; + public String uuid; + + public DescriptorWriteConfirmation(int status, String uuid) { + this.status = status; + this.uuid = uuid; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.java new file mode 100644 index 0000000000..53f85c5a6f --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.java @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class BleIOBusyException extends Exception{ +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.java new file mode 100644 index 0000000000..b2034fd087 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.java @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class CouldNotConfirmDescriptorWriteException extends Exception{ + private final String received; + private final String expected; + + public CouldNotConfirmDescriptorWriteException(String received, String expected) { + super(); + this.received = received; + this.expected = expected; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWrite.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWrite.java new file mode 100644 index 0000000000..f422746180 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWrite.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class CouldNotConfirmWrite extends Exception { + + private final byte[] sent; + private final Object confirmed; + + public CouldNotConfirmWrite(byte[] sent, byte[] confirmed) { + super(); + this.sent = sent; + this.confirmed = confirmed; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.java new file mode 100644 index 0000000000..261490dee5 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.CharacteristicType; + +public class CouldNotEnableNotifications extends Exception { + public CouldNotEnableNotifications(CharacteristicType cmd) { + super(cmd.getValue()); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DescriptorNotFoundException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DescriptorNotFoundException.java new file mode 100644 index 0000000000..d7dc3473eb --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DescriptorNotFoundException.java @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class DescriptorNotFoundException extends Exception { +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.java new file mode 100644 index 0000000000..75490b33e3 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.java @@ -0,0 +1,140 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.CharacteristicType; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.BleIOBusyException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWrite; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotEnableNotifications; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DescriptorNotFoundException; + + +public class BleIO { + private static final int DEFAULT_IO_TIMEOUT_MS = 1000; + + private final AAPSLogger aapsLogger; + private final Map chars; + private final Map> incomingPackets; + private final BluetoothGatt gatt; + private final BleCommCallbacks bleCommCallbacks; + + private IOState state; + + public BleIO(AAPSLogger aapsLogger, Map chars, Map> incomingPackets, BluetoothGatt gatt, BleCommCallbacks bleCommCallbacks) { + this.aapsLogger = aapsLogger; + this.chars = chars; + this.incomingPackets = incomingPackets; + this.gatt = gatt; + this.bleCommCallbacks = bleCommCallbacks; + this.state = IOState.IDLE; + } + + /*** + * + * @param characteristic where to read from(CMD or DATA) + * @return a byte array with the received data + */ + public byte[] receivePacket(CharacteristicType characteristic) throws + BleIOBusyException, + InterruptedException, + TimeoutException { + synchronized (this.state) { + if (this.state != IOState.IDLE) { + throw new BleIOBusyException(); + } + this.state = IOState.READING; + } + byte[] ret = this.incomingPackets.get(characteristic).poll(DEFAULT_IO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (ret == null) { + throw new TimeoutException(); + } + synchronized (this.state) { + this.state = IOState.IDLE; + } + return ret; + } + + /*** + * + * @param characteristic where to write to(CMD or DATA) + * @param payload the data to send + * @throws CouldNotSendBleException + */ + public void sendAndConfirmPacket(CharacteristicType characteristic, byte[] payload) + throws CouldNotSendBleException, + BleIOBusyException, + InterruptedException, + CouldNotConfirmWrite, + TimeoutException { + synchronized (this.state) { + if (this.state != IOState.IDLE) { + throw new BleIOBusyException(); + } + this.state = IOState.WRITING; + } + + aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on" + characteristic.name() + "/" + payload.toString()); + BluetoothGattCharacteristic ch = chars.get(characteristic); + boolean set = ch.setValue(payload); + if (!set) { + throw new CouldNotSendBleException("setValue"); + } + boolean sent = this.gatt.writeCharacteristic(ch); + if (!sent) { + throw new CouldNotSendBleException("writeCharacteristic"); + } + this.bleCommCallbacks.confirmWrite(CharacteristicType.CMD, payload, DEFAULT_IO_TIMEOUT_MS); + synchronized (this.state) { + this.state = IOState.IDLE; + } + } + + /** + * 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 + * @return + */ + public void readyToRead() + throws CouldNotSendBleException, + CouldNotEnableNotifications, + DescriptorNotFoundException, + InterruptedException, CouldNotConfirmDescriptorWriteException { + + for (CharacteristicType type : CharacteristicType.values()) { + BluetoothGattCharacteristic ch = this.chars.get(type); + boolean notificationSet = this.gatt.setCharacteristicNotification(ch, true); + if (!notificationSet) { + throw new CouldNotEnableNotifications(type); + } + List descriptors = ch.getDescriptors(); + if (descriptors.size() != 1) { + throw new DescriptorNotFoundException(); + } + BluetoothGattDescriptor descriptor = descriptors.get(0); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + gatt.writeDescriptor(descriptor); + bleCommCallbacks.confirmWriteDescriptor(descriptor.getUuid().toString(), DEFAULT_IO_TIMEOUT_MS); + } + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IOState.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IOState.java new file mode 100644 index 0000000000..cd9ae12297 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IOState.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io; + +public enum IOState { + IDLE, + WRITING, + READING; +}