diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt index 9a3e241e52..0d9b275dd6 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/dagger/OmnipodDashModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.pump.omnipod.common.dagger.ActivityScope import info.nightscout.androidaps.plugins.pump.omnipod.common.dagger.OmnipodWizardModule +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.BleManager import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.DashPodManagementActivity import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.OmnipodDashOverviewFragment import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.DashPodActivationWizardActivity @@ -30,4 +31,6 @@ abstract class OmnipodDashModule { @ContributesAndroidInjector abstract fun contributesOmnipodDashOverviewFragment(): OmnipodDashOverviewFragment + @ContributesAndroidInjector + abstract fun contributesBleManager(): BleManager } 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 new file mode 100644 index 0000000000..235cd5434d --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleCommCallbacks.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm; + +import android.bluetooth.BluetoothGattCallback; + +public class BleCommCallbacks extends BluetoothGattCallback { + +} 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 new file mode 100644 index 0000000000..c8a14ed336 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/BleManager.java @@ -0,0 +1,159 @@ +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 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.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; + private final BluetoothManager bluetoothManager; + @Inject AAPSLogger aapsLogger; + private String podAddress; + private BluetoothGatt gatt; + + private BluetoothGattCharacteristic cmdCharacteristic; + private BluetoothGattCharacteristic dataCharacteristic; + + @Inject + public BleManager(Context context) { + this.context = context; + this.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); + this.bluetoothAdapter = bluetoothManager.getAdapter(); + } + + public static BleManager getInstance(Context context) { + BleManager ret; + synchronized (BleManager.class) { + if (instance == null) { + instance = new BleManager(context); + } + ret = instance; + } + 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, + FailedToConnectException, + CouldNotSendBleException { + 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(); + // do the dance: send SP0, SP1, etc + // get and save LTK + } + + public void connect() + throws FailedToConnectException, + CouldNotSendBleException { + // TODO: locking? + + BluetoothDevice podDevice = this.bluetoothAdapter.getRemoteDevice(this.podAddress); + BluetoothGattCallback bleCommCallback = new BleCommCallbacks(); + + 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() + } + + 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); + + } + + 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; + } +} + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommand.java new file mode 100644 index 0000000000..fdcbde4e7c --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommand.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; + +import org.jetbrains.annotations.NotNull; + +public class BleCommand { + private final byte[] data; + + public BleCommand(@NotNull BleCommandType type) { + this.data = new byte[]{type.getValue()}; + } + + public BleCommand(@NotNull BleCommandType type, @NotNull byte[] payload) { + int n = payload.length + 1; + this.data = new byte[n]; + this.data[0] = type.getValue(); + System.arraycopy(payload, 0, data, 1, payload.length); + } + + public byte[] asByteArray() { + return this.data; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandHello.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandHello.java new file mode 100644 index 0000000000..3bcabacc28 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandHello.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; + +import java.nio.ByteBuffer; + +public class BleCommandHello extends BleCommand { + public BleCommandHello(int controllerId) { + super(BleCommandType.HELLO, + ByteBuffer.allocate(6) + .put((byte) 1) // TODO find the meaning of this constant + .put((byte) 4) // TODO find the meaning of this constant + .putInt(controllerId).array() + ); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandType.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandType.java new file mode 100644 index 0000000000..4bd71a357d --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/blecommand/BleCommandType.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand; + +public enum BleCommandType { + RTS((byte) 0x00), + CTS((byte) 0x01), + NACK((byte) 0x02), + ABORT((byte) 0x03), + SUCCESS((byte) 0x04), + FAIL((byte) 0x05), + HELLO((byte) 0x06); + + public final byte value; + + BleCommandType(byte value) { + this.value = value; + } + + public static BleCommandType byValue(byte value) { + for (BleCommandType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown BleCommandType: " + value); + } + + public byte getValue() { + return this.value; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CharacteristicNotFoundException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CharacteristicNotFoundException.java new file mode 100644 index 0000000000..1405d93f32 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CharacteristicNotFoundException.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class CharacteristicNotFoundException extends FailedToConnectException { + public CharacteristicNotFoundException(String cmdCharacteristicUuid) { + super("characteristic not found: " + cmdCharacteristicUuid); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleCmdException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleCmdException.java new file mode 100644 index 0000000000..ae2d1e4be4 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleCmdException.java @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class CouldNotSendBleCmdException extends CouldNotSendBleException { +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.java new file mode 100644 index 0000000000..8bca9c732d --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.java @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class CouldNotSendBleException extends Exception { +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DiscoveredInvalidPodException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DiscoveredInvalidPodException.java new file mode 100644 index 0000000000..9de20faf07 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/DiscoveredInvalidPodException.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +import android.os.ParcelUuid; + +import java.util.List; + +public class DiscoveredInvalidPodException extends Exception { + public DiscoveredInvalidPodException(String message, List serviceUUIds) { + super(message + " service UUIDs: " + serviceUUIds); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.java new file mode 100644 index 0000000000..dd4cf724ed --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class FailedToConnectException extends Exception { + public FailedToConnectException() { + super(); + } + + public FailedToConnectException(String message) { + super(message); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.java new file mode 100644 index 0000000000..f99fbe32d4 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.java @@ -0,0 +1,10 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class ScanFailException extends Exception { + public ScanFailException() { + } + + public ScanFailException(int errorCode) { + super("errorCode" + errorCode); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.java new file mode 100644 index 0000000000..7fc00ae2df --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.java @@ -0,0 +1,20 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.BleDiscoveredDevice; + +public class ScanFailFoundTooManyException extends ScanFailException { + private final List devices; + + public ScanFailFoundTooManyException(List devices) { + super(); + this.devices = new ArrayList<>(devices); + } + + public List getDiscoveredDevices() { + return Collections.unmodifiableList(this.devices); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.java new file mode 100644 index 0000000000..9bf2284db3 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.java @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class ScanFailNotFoundException extends ScanFailException { +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ServiceNotFoundException.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ServiceNotFoundException.java new file mode 100644 index 0000000000..51bfa5b8ef --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ServiceNotFoundException.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions; + +public class ServiceNotFoundException extends FailedToConnectException { + public ServiceNotFoundException(String serviceUuid) { + super("service not found: " + serviceUuid); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.java new file mode 100644 index 0000000000..d4c0a19ef8 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/BleDiscoveredDevice.java @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan; + +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.os.ParcelUuid; + +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DiscoveredInvalidPodException; + +public class BleDiscoveredDevice { + private final ScanResult scanResult; + private final long podID; + private final int sequenceNo; + private final long lotNo; + + public BleDiscoveredDevice(ScanResult scanResult, long searchPodID) + throws DiscoveredInvalidPodException { + + this.scanResult = scanResult; + this.podID = searchPodID; + + this.validateServiceUUIDs(); + this.validatePodID(); + this.lotNo = this.parseLotNo(); + this.sequenceNo = this.parseSeqNo(); + } + + private static String extractUUID16(ParcelUuid uuid) { + return uuid.toString().substring(4, 8); + } + + private void validateServiceUUIDs() + throws DiscoveredInvalidPodException { + ScanRecord scanRecord = scanResult.getScanRecord(); + List serviceUUIDs = scanRecord.getServiceUuids(); + + if (serviceUUIDs.size() != 9) { + throw new DiscoveredInvalidPodException("Expected 9 service UUIDs, got" + serviceUUIDs.size(), serviceUUIDs); + } + if (!extractUUID16(serviceUUIDs.get(0)).equals("4024")) { + // this is the service that we filtered for + throw new DiscoveredInvalidPodException("The first exposed service UUID should be 4024, got " + extractUUID16(serviceUUIDs.get(0)), serviceUUIDs); + } + // TODO understand what is serviceUUIDs[1]. 0x2470. Alarms? + if (!extractUUID16(serviceUUIDs.get(2)).equals("000a")) { + // constant? + throw new DiscoveredInvalidPodException("The third exposed service UUID should be 000a, got " + serviceUUIDs.get(2), serviceUUIDs); + } + } + + private void validatePodID() + throws DiscoveredInvalidPodException { + ScanRecord scanRecord = scanResult.getScanRecord(); + List serviceUUIDs = scanRecord.getServiceUuids(); + String hexPodID = extractUUID16(serviceUUIDs.get(3)) + extractUUID16(serviceUUIDs.get(4)); + Long podID = Long.parseLong(hexPodID, 16); + if (this.podID != podID) { + throw new DiscoveredInvalidPodException("This is not the POD we are looking for. " + this.podID + " found: " + podID, serviceUUIDs); + } + } + + private long parseLotNo() { + ScanRecord scanRecord = scanResult.getScanRecord(); + List serviceUUIDs = scanRecord.getServiceUuids(); + String lotSeq = extractUUID16(serviceUUIDs.get(5)) + + extractUUID16(serviceUUIDs.get(6)) + + extractUUID16(serviceUUIDs.get(7)); + + return Long.parseLong(lotSeq.substring(0, 10), 16); + } + + private int parseSeqNo() { + ScanRecord scanRecord = scanResult.getScanRecord(); + List serviceUUIDs = scanRecord.getServiceUuids(); + String lotSeq = extractUUID16(serviceUUIDs.get(7)) + + extractUUID16(serviceUUIDs.get(8)); + + return Integer.parseInt(lotSeq.substring(2), 16); + } + + public ScanResult getScanResult() { + return this.scanResult; + } + + @Override public String toString() { + return "BleDiscoveredDevice{" + + "scanResult=" + scanResult + + ", podID=" + podID + + ", sequenceNo=" + sequenceNo + + ", lotNo=" + lotNo + + '}'; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.java new file mode 100644 index 0000000000..c3953f75d1 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.java @@ -0,0 +1,62 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; +import android.os.ParcelUuid; + +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailFoundTooManyException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailNotFoundException; + +public class PodScanner { + public static final String SCAN_FOR_SERVICE_UUID = "00004024-0000-1000-8000-00805F9B34FB"; + public static final long POD_ID_NOT_ACTIVATED = 4294967294L; + private static final int SCAN_DURATION_MS = 5000; + + private final BluetoothAdapter bluetoothAdapter; + private final AAPSLogger logger; + + public PodScanner(AAPSLogger logger, BluetoothAdapter bluetoothAdapter) { + this.bluetoothAdapter = bluetoothAdapter; + this.logger = logger; + } + + public BleDiscoveredDevice scanForPod(String serviceUUID, long podID) + throws InterruptedException, ScanFailException { + BluetoothLeScanner scanner = this.bluetoothAdapter.getBluetoothLeScanner(); + + ScanFilter filter = new ScanFilter.Builder() + .setServiceUuid(ParcelUuid.fromString(serviceUUID)) + .build(); + + ScanSettings scanSettings = new ScanSettings.Builder() + .setLegacy(false) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build(); + + ScanCollector scanCollector = new ScanCollector(this.logger, podID); + this.logger.debug(LTag.PUMPBTCOMM, "Scanning with filters: "+ filter.toString() + " settings" + scanSettings.toString()); + scanner.startScan(Arrays.asList(filter), scanSettings, scanCollector); + + Thread.sleep(SCAN_DURATION_MS); + + scanner.flushPendingScanResults(scanCollector); + scanner.stopScan(scanCollector); + + List collected = scanCollector.collect(); + if (collected.size() == 0) { + throw new ScanFailNotFoundException(); + } else if (collected.size() > 1) { + throw new ScanFailFoundTooManyException(collected); + } + return collected.get(0); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.java new file mode 100644 index 0000000000..9121b0f5a4 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.java @@ -0,0 +1,66 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan; + +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanResult; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DiscoveredInvalidPodException; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException; + +public class ScanCollector extends ScanCallback { + private final AAPSLogger logger; + private final long podID; + // there could be different threads calling the onScanResult callback + private final ConcurrentHashMap found; + private int scanFailed; + + public ScanCollector(AAPSLogger logger, long podID) { + this.podID = podID; + this.logger = logger; + this.found = new ConcurrentHashMap(); + } + + @Override + public void onScanResult(int callbackType, ScanResult result) { + // callbackType will be ALL + this.logger.debug(LTag.PUMPBTCOMM, "Scan found: "+result.toString()); + this.found.put(result.getDevice().getAddress(), result); + } + + @Override + public void onScanFailed(int errorCode) { + this.scanFailed = errorCode; + this.logger.warn(LTag.PUMPBTCOMM, "Scan failed with errorCode: "+errorCode); + super.onScanFailed(errorCode); + } + + public List collect() + throws ScanFailException { + List ret = new ArrayList<>(); + + if (this.scanFailed != 0) { + throw new ScanFailException(this.scanFailed); + } + + logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: " + this.podID); + + for (ScanResult result : this.found.values()) { + try { + BleDiscoveredDevice device = new BleDiscoveredDevice(result, this.podID); + ret.add(device); + logger.debug(LTag.PUMPBTCOMM, "ScanCollector found: " + result.toString() + "Pod ID: " + this.podID); + } catch (DiscoveredInvalidPodException e) { + logger.debug(LTag.PUMPBTCOMM, "ScanCollector: pod not matching" + e.toString()); + // this is not the POD we are looking for + } + } + return Collections.unmodifiableList(ret); + } +} + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/activation/viewmodel/action/DashInitializePodViewModel.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/activation/viewmodel/action/DashInitializePodViewModel.kt index b6a3b8de7e..b97060a907 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/activation/viewmodel/action/DashInitializePodViewModel.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/wizard/activation/viewmodel/action/DashInitializePodViewModel.kt @@ -1,15 +1,21 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.viewmodel.action +import android.os.AsyncTask import androidx.annotation.StringRes +import androidx.lifecycle.viewModelScope import dagger.android.HasAndroidInjector import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activation.viewmodel.action.InitializePodViewModel import info.nightscout.androidaps.plugins.pump.omnipod.dash.R +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.BleManager +import kotlinx.coroutines.launch import javax.inject.Inject -class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAPSLogger, private val injector: HasAndroidInjector) : InitializePodViewModel() { +class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAPSLogger, + private val injector: HasAndroidInjector, + private val bleManager: BleManager) : InitializePodViewModel() { override fun isPodInAlarm(): Boolean = false // TODO @@ -19,7 +25,14 @@ class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAP override fun doExecuteAction(): PumpEnactResult { // TODO FIRST STEP OF ACTIVATION - aapsLogger.debug(LTag.PUMP, "started activation part 1") + AsyncTask.execute { + try { + bleManager.activateNewPod() + } catch (e: Exception) { + aapsLogger.error(LTag.PUMP, "TEST ACTIVATE Exception" + e.toString()) + } + } + return PumpEnactResult(injector).success(false).comment("not implemented") }