Merge pull request #2 from 0pen-dash/ble-io

Ble updates
This commit is contained in:
bartsopers 2021-02-25 21:54:07 +01:00 committed by GitHub
commit 80788c481f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 690 additions and 535 deletions

View file

@ -1,7 +0,0 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
import android.bluetooth.BluetoothGattCallback;
public class BleCommCallbacks extends BluetoothGattCallback {
}

View file

@ -1,159 +0,0 @@
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;
}
}

View file

@ -0,0 +1,73 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.content.Context
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.*
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.TimeoutException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BleManager @Inject constructor(private val context: Context, private val aapsLogger: AAPSLogger) : OmnipodDashCommunicationManager {
private val bluetoothManager: BluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
@Throws(InterruptedException::class, ScanFailException::class, FailedToConnectException::class, CouldNotSendBleException::class, BleIOBusyException::class, TimeoutException::class, CouldNotConfirmWriteException::class, CouldNotEnableNotifications::class, DescriptorNotFoundException::class, CouldNotConfirmDescriptorWriteException::class)
fun activateNewPod() {
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
val podAddress = podScanner.scanForPod(PodScanner.SCAN_FOR_SERVICE_UUID, PodScanner.POD_ID_NOT_ACTIVATED).scanResult.device.address
// For tests: this.podAddress = "B8:27:EB:1D:7E:BB";
connect(podAddress)
}
@Throws(FailedToConnectException::class, CouldNotSendBleException::class, InterruptedException::class, BleIOBusyException::class, TimeoutException::class, CouldNotConfirmWriteException::class, CouldNotEnableNotifications::class, DescriptorNotFoundException::class, CouldNotConfirmDescriptorWriteException::class)
private fun connect(podAddress: String) {
// TODO: locking?
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>> =
mapOf(CharacteristicType.CMD to LinkedBlockingDeque(),
CharacteristicType.DATA to LinkedBlockingDeque());
val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets)
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to $podAddress")
var autoConnect = true
if (BuildConfig.DEBUG) {
autoConnect = false
// TODO: remove this in the future
// it's easier to start testing from scratch on each run.
}
val gatt = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS)
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
throw FailedToConnectException(podAddress)
}
val discoverer = ServiceDiscoverer(aapsLogger, gatt, bleCommCallbacks)
val chars = discoverer.discoverServices()
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks)
aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod")
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
bleIO.readyToRead()
}
companion object {
private const val CONNECT_TIMEOUT_MS = 5000
private const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else.
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
import java.math.BigInteger
import java.util.*
enum class CharacteristicType(val value: String) {
CMD("1a7e2441-e3ed-4464-8b7e-751e03d0dc5f"), DATA("1a7e2442-e3ed-4464-8b7e-751e03d0dc5f");
val uuid: UUID
get() = UUID(
BigInteger(value.replace("-", "").substring(0, 16), 16).toLong(),
BigInteger(value.replace("-", "").substring(16), 16).toLong()
)
companion object {
@JvmStatic
fun byValue(value: String): CharacteristicType =
values().firstOrNull { it.value == value }
?: throw IllegalArgumentException("Unknown Characteristic Type: $value")
}
}

View file

@ -1,4 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
public interface OmnipodDashCommunicationManager { interface OmnipodDashCommunicationManager
}

View file

@ -0,0 +1,45 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
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
import java.math.BigInteger
import java.util.*
class ServiceDiscoverer(private val logger: AAPSLogger, private val gatt: BluetoothGatt, private val bleCallbacks: BleCommCallbacks) {
/***
* This is first step after connection establishment
*/
@Throws(InterruptedException::class, ServiceNotFoundException::class, CharacteristicNotFoundException::class)
fun discoverServices(): Map<CharacteristicType, BluetoothGattCharacteristic> {
logger.debug(LTag.PUMPBTCOMM, "Discovering services")
gatt.discoverServices()
bleCallbacks.waitForServiceDiscovery(DISCOVER_SERVICES_TIMEOUT_MS)
logger.debug(LTag.PUMPBTCOMM, "Services discovered")
val service = gatt.getService(SERVICE_UUID.toUuid())
?: throw ServiceNotFoundException(SERVICE_UUID)
val cmdChar = service.getCharacteristic(CharacteristicType.CMD.uuid)
?: throw CharacteristicNotFoundException(CharacteristicType.CMD.value)
val dataChar = service.getCharacteristic(CharacteristicType.DATA.uuid) // TODO: this is never used
?: throw CharacteristicNotFoundException(CharacteristicType.DATA.value)
var chars = mapOf(CharacteristicType.CMD to cmdChar,
CharacteristicType.DATA to dataChar)
return chars
}
private fun String.toUuid(): UUID = UUID(
BigInteger(replace("-", "").substring(0, 16), 16).toLong(),
BigInteger(replace("-", "").substring(16), 16).toLong()
)
companion object {
private const val SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f"
private const val DISCOVER_SERVICES_TIMEOUT_MS = 5000
}
}

View file

@ -1,22 +0,0 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.blecommand;
import org.jetbrains.annotations.NotNull;
public abstract 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;
}
}

View file

@ -1,14 +0,0 @@
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()
);
}
}

View file

@ -1,30 +0,0 @@
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;
}
}

View file

@ -0,0 +1,145 @@
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 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.CharacteristicType.Companion.byValue
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWriteException
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
class BleCommCallbacks(private val aapsLogger: AAPSLogger, private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>) : BluetoothGattCallback() {
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
private val connected: CountDownLatch = CountDownLatch(1)
private val writeQueue: BlockingQueue<CharacteristicWriteConfirmation> = LinkedBlockingQueue(1)
private val descriptorWriteQueue: BlockingQueue<DescriptorWriteConfirmation> = LinkedBlockingQueue(1)
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange discovered with status/state$status/$newState")
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
connected.countDown()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
super.onServicesDiscovered(gatt, status)
aapsLogger.debug(LTag.PUMPBTCOMM, "OnServicesDiscovered with status$status")
if (status == BluetoothGatt.GATT_SUCCESS) {
serviceDiscoveryComplete.countDown()
}
}
@Throws(InterruptedException::class)
fun waitForConnection(timeout_ms: Int) {
connected.await(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
}
@Throws(InterruptedException::class)
fun waitForServiceDiscovery(timeout_ms: Int) {
serviceDiscoveryComplete.await(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
}
@Throws(InterruptedException::class, TimeoutException::class, CouldNotConfirmWriteException::class)
fun confirmWrite(expectedPayload: ByteArray, timeout_ms: Int) {
val received: CharacteristicWriteConfirmation = writeQueue.poll(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
?: throw TimeoutException()
when (received) {
is CharacteristicWriteConfirmationPayload -> confirmWritePayload(expectedPayload, received)
is CharacteristicWriteConfirmationError -> throw CouldNotConfirmWriteException(received.status)
}
}
private fun confirmWritePayload(expectedPayload: ByteArray, received: CharacteristicWriteConfirmationPayload) {
if (!expectedPayload.contentEquals(received.payload)) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm write. Got " + received.payload + ".Excepted: " + expectedPayload)
throw CouldNotConfirmWriteException(expectedPayload, received.payload)
}
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed write with value: " + received.payload)
}
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
CharacteristicWriteConfirmationPayload(characteristic.value)
} else {
CharacteristicWriteConfirmationError(status)
}
aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicWrite with status/char/value " +
status + "/" + byValue(characteristic.uuid.toString()) + "/" + characteristic.value)
try {
if (writeQueue.size > 0) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Write confirm queue should be empty. found: " + writeQueue.size)
writeQueue.clear()
}
val offered = writeQueue.offer(writeConfirmation, WRITE_CONFIRM_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
if (!offered) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation")
}
} catch (e: InterruptedException) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending write confirmation")
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicChanged(gatt, characteristic)
val payload = characteristic.value
val characteristicType = byValue(characteristic.uuid.toString())
aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicChanged with char/value " +
characteristicType + "/" +
payload)
incomingPackets[characteristicType]!!.add(payload)
}
@Throws(InterruptedException::class, CouldNotConfirmDescriptorWriteException::class)
fun confirmWriteDescriptor(descriptorUUID: String, timeout_ms: Int) {
val confirmed: DescriptorWriteConfirmation = descriptorWriteQueue.poll(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
?: throw TimeoutException()
when (confirmed) {
is DescriptorWriteConfirmationError -> throw CouldNotConfirmWriteException(confirmed.status)
is DescriptorWriteConfirmationUUID -> if (confirmed.uuid != descriptorUUID) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm descriptor write. Got ${confirmed.uuid}. Expected: ${descriptorUUID}")
throw CouldNotConfirmDescriptorWriteException(descriptorUUID, confirmed.uuid)
} else {
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed descriptor write : " + confirmed.uuid)
}
}
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
aapsLogger.debug(LTag.PUMPBTCOMM, "OnDescriptor value " + descriptor.value)
DescriptorWriteConfirmationUUID(descriptor.uuid.toString())
} else {
DescriptorWriteConfirmationError(status)
}
try {
if (descriptorWriteQueue.size > 0) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Descriptor write queue should be empty, found: " + descriptorWriteQueue.size)
descriptorWriteQueue.clear()
}
val offered = descriptorWriteQueue.offer(writeConfirmation, WRITE_CONFIRM_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
if (!offered) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed descriptor write confirmation")
}
} catch (e: InterruptedException) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending descriptor write confirmation")
}
}
companion object {
private const val WRITE_CONFIRM_TIMEOUT_MS = 10 // the confirmation queue should be empty anyway
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
sealed class CharacteristicWriteConfirmation
data class CharacteristicWriteConfirmationPayload(val payload: ByteArray) : CharacteristicWriteConfirmation()
data class CharacteristicWriteConfirmationError(val status: Int) : CharacteristicWriteConfirmation()

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
sealed class DescriptorWriteConfirmation
data class DescriptorWriteConfirmationUUID(val uuid: String): DescriptorWriteConfirmation()
data class DescriptorWriteConfirmationError(val status: Int): DescriptorWriteConfirmation()

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
abstract class BleCommand {
val data: ByteArray
constructor(type: BleCommandType) {
data = byteArrayOf(type.value)
}
constructor(type: BleCommandType, payload: ByteArray) {
val n = payload.size + 1
data = ByteArray(n)
data[0] = type.value
System.arraycopy(payload, 0, data, 1, payload.size)
}
}

View file

@ -0,0 +1,10 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
import java.nio.ByteBuffer
class BleCommandHello(controllerId: Int) : BleCommand(BleCommandType.HELLO,
ByteBuffer.allocate(6)
.put(1.toByte()) // TODO find the meaning of this constant
.put(4.toByte()) // TODO find the meaning of this constant
.putInt(controllerId).array()
)

View file

@ -0,0 +1,13 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
enum class BleCommandType(val value: Byte) {
RTS(0x00.toByte()), CTS(0x01.toByte()), NACK(0x02.toByte()), ABORT(0x03.toByte()), SUCCESS(0x04.toByte()), FAIL(0x05.toByte()), HELLO(0x06.toByte());
companion object {
@JvmStatic
fun byValue(value: Byte): BleCommandType =
BleCommandType.values().firstOrNull { it.value == value }
?: throw IllegalArgumentException("Unknown BleCommandType: $value")
}
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class BleIOBusyException : Exception()

View file

@ -1,7 +0,0 @@
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);
}
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CharacteristicNotFoundException(cmdCharacteristicUuid: String) : FailedToConnectException("characteristic not found: $cmdCharacteristicUuid")

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CouldNotConfirmDescriptorWriteException(override val message: String?) : Exception(message) {
constructor(sent: String, confirmed: String): this("Could not confirm write. Sent: {$sent} .Received: ${confirmed}")
constructor(status: Int): this("Could not confirm write. Write status: ${status}")
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CouldNotConfirmWriteException(override val message: String?) : Exception(message) {
constructor(sent: ByteArray, confirmed: ByteArray): this("Could not confirm write. Sent: {$sent} .Received: ${confirmed}")
constructor(status: Int): this("Could not confirm write. Write status: ${status}")
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.CharacteristicType
class CouldNotEnableNotifications(cmd: CharacteristicType) : Exception(cmd.value)

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 +0,0 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
public class CouldNotSendBleException extends Exception {
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CouldNotSendBleException(msg: String?) : Exception(msg)

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class DescriptorNotFoundException : Exception()

View file

@ -1,11 +0,0 @@
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<ParcelUuid> serviceUUIds) {
super(message + " service UUIDs: " + serviceUUIds);
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
import android.os.ParcelUuid
class DiscoveredInvalidPodException: Exception {
constructor(message: String) : super(message) {}
constructor(message: String, serviceUUIds: List<ParcelUuid?>) : super("$message service UUIDs: $serviceUUIds"){}
}

View file

@ -1,11 +0,0 @@
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);
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
open class FailedToConnectException : Exception {
constructor() : super() {}
constructor(message: String?) : super(message) {}
}

View file

@ -1,10 +0,0 @@
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);
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
open class ScanFailException : Exception {
constructor() {}
constructor(errorCode: Int) : super("errorCode$errorCode") {}
}

View file

@ -1,20 +0,0 @@
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<BleDiscoveredDevice> devices;
public ScanFailFoundTooManyException(List<BleDiscoveredDevice> devices) {
super();
this.devices = new ArrayList<>(devices);
}
public List<BleDiscoveredDevice> getDiscoveredDevices() {
return Collections.unmodifiableList(this.devices);
}
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.BleDiscoveredDevice
import java.util.*
class ScanFailFoundTooManyException(devices: List<BleDiscoveredDevice>) : ScanFailException() {
private val devices: List<BleDiscoveredDevice> = ArrayList(devices)
val discoveredDevices: List<BleDiscoveredDevice>
get() = Collections.unmodifiableList(devices)
}

View file

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

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class ScanFailNotFoundException : ScanFailException()

View file

@ -1,7 +0,0 @@
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);
}
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class ServiceNotFoundException(serviceUuid: String) : FailedToConnectException("service not found: $serviceUuid")

View file

@ -0,0 +1,100 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
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.callbacks.BleCommCallbacks
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
import java.util.concurrent.BlockingQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class BleIO(private val aapsLogger: AAPSLogger, private val chars: Map<CharacteristicType, BluetoothGattCharacteristic>, private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>, private val gatt: BluetoothGatt, private val bleCommCallbacks: BleCommCallbacks) {
private var state: IOState = IOState.IDLE
/***
*
* @param characteristic where to read from(CMD or DATA)
* @return a byte array with the received data
*/
@Throws(BleIOBusyException::class, InterruptedException::class, TimeoutException::class)
fun receivePacket(characteristic: CharacteristicType): ByteArray {
synchronized(state) {
if (state != IOState.IDLE) {
throw BleIOBusyException()
}
state = IOState.READING
}
val ret = incomingPackets[characteristic]?.poll(DEFAULT_IO_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
?: throw TimeoutException()
synchronized(state) { state = IOState.IDLE }
return ret
}
/***
*
* @param characteristic where to write to(CMD or DATA)
* @param payload the data to send
* @throws CouldNotSendBleException
*/
@Throws(CouldNotSendBleException::class, BleIOBusyException::class, InterruptedException::class, CouldNotConfirmWriteException::class, TimeoutException::class)
fun sendAndConfirmPacket(characteristic: CharacteristicType, payload: ByteArray) {
synchronized(state) {
if (state != IOState.IDLE) {
throw BleIOBusyException()
}
state = IOState.WRITING
}
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on" + characteristic.name + "/" + payload.toString())
val ch = chars[characteristic]
val set = ch!!.setValue(payload)
if (!set) {
throw CouldNotSendBleException("setValue")
}
val sent = gatt.writeCharacteristic(ch)
if (!sent) {
throw CouldNotSendBleException("writeCharacteristic")
}
bleCommCallbacks.confirmWrite(payload, DEFAULT_IO_TIMEOUT_MS)
synchronized(state) { state = IOState.IDLE }
}
/**
* Called before sending a new message.
* The incoming queues should be empty, so we log when they are not.
*/
fun flushIncomingQueues() {}
/**
* Enable intentions on the characteristics.
* This will signal the pod it can start sending back data
* @return
*/
@Throws(CouldNotSendBleException::class, CouldNotEnableNotifications::class, DescriptorNotFoundException::class, InterruptedException::class, CouldNotConfirmDescriptorWriteException::class)
fun readyToRead() {
for (type in CharacteristicType.values()) {
val ch = chars[type]
val notificationSet = gatt.setCharacteristicNotification(ch, true)
if (!notificationSet) {
throw CouldNotEnableNotifications(type)
}
val descriptors = ch!!.descriptors
if (descriptors.size != 1) {
throw DescriptorNotFoundException()
}
val descriptor = descriptors[0]
descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
gatt.writeDescriptor(descriptor)
bleCommCallbacks.confirmWriteDescriptor(descriptor.uuid.toString(), DEFAULT_IO_TIMEOUT_MS)
}
}
companion object {
private const val DEFAULT_IO_TIMEOUT_MS = 1000
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
enum class IOState {
IDLE, WRITING, READING
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet
class BlePacket

View file

@ -1,94 +0,0 @@
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<ParcelUuid> 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<ParcelUuid> 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<ParcelUuid> 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<ParcelUuid> 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 +
'}';
}
}

View file

@ -0,0 +1,80 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DiscoveredInvalidPodException
class BleDiscoveredDevice(val scanResult: ScanResult, private val podId: Long) {
private val sequenceNo: Int
private val lotNo: Long
@Throws(DiscoveredInvalidPodException::class)
private fun validateServiceUUIDs() {
val scanRecord = scanResult.scanRecord
?: throw DiscoveredInvalidPodException("Scan record is null");
val serviceUuids = scanRecord.serviceUuids
if (serviceUuids.size != 9) {
throw DiscoveredInvalidPodException("Expected 9 service UUIDs, got" + serviceUuids.size, serviceUuids)
}
if (extractUUID16(serviceUuids[0]) != MAIN_SERVICE_UUID) {
// this is the service that we filtered for
throw DiscoveredInvalidPodException("The first exposed service UUID should be 4024, got " + extractUUID16(serviceUuids[0]), serviceUuids)
}
// TODO understand what is serviceUUIDs[1]. 0x2470. Alarms?
if (extractUUID16(serviceUuids[2]) != "000a") {
// constant?
throw DiscoveredInvalidPodException("The third exposed service UUID should be 000a, got " + serviceUuids[2], serviceUuids)
}
}
@Throws(DiscoveredInvalidPodException::class)
private fun validatePodId() {
val scanRecord = scanResult.scanRecord
val serviceUUIDs = scanRecord.serviceUuids
val hexPodId = extractUUID16(serviceUUIDs[3]) + extractUUID16(serviceUUIDs[4])
val podId = hexPodId.toLong(16)
if (this.podId != podId) {
throw DiscoveredInvalidPodException("This is not the POD we are looking for. " + this.podId + " found: " + this.podId, serviceUUIDs)
}
}
private fun parseLotNo(): Long {
val scanRecord = scanResult.scanRecord
val serviceUUIDs = scanRecord.serviceUuids
val lotSeq = extractUUID16(serviceUUIDs[5]) +
extractUUID16(serviceUUIDs[6]) +
extractUUID16(serviceUUIDs[7])
return lotSeq.substring(0, 10).toLong(16)
}
private fun parseSeqNo(): Int {
val scanRecord = scanResult.scanRecord
val serviceUUIDs = scanRecord.serviceUuids
val lotSeq = extractUUID16(serviceUUIDs[7]) +
extractUUID16(serviceUUIDs[8])
return lotSeq.substring(2).toInt(16)
}
override fun toString(): String {
return "BleDiscoveredDevice{" +
"scanResult=" + scanResult +
", podID=" + podId +
", sequenceNo=" + sequenceNo +
", lotNo=" + lotNo +
'}'
}
companion object {
const val MAIN_SERVICE_UUID = "4024";
private fun extractUUID16(uuid: ParcelUuid): String {
return uuid.toString().substring(4, 8)
}
}
init {
validateServiceUUIDs()
validatePodId()
lotNo = parseLotNo()
sequenceNo = parseSeqNo()
}
}

View file

@ -1,62 +0,0 @@
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<BleDiscoveredDevice> collected = scanCollector.collect();
if (collected.size() == 0) {
throw new ScanFailNotFoundException();
} else if (collected.size() > 1) {
throw new ScanFailFoundTooManyException(collected);
}
return collected.get(0);
}
}

View file

@ -0,0 +1,48 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.os.ParcelUuid
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
import java.util.*
class PodScanner(private val logger: AAPSLogger, private val bluetoothAdapter: BluetoothAdapter) {
@Throws(InterruptedException::class, ScanFailException::class)
fun scanForPod(serviceUUID: String?, podID: Long): BleDiscoveredDevice {
val scanner = bluetoothAdapter.bluetoothLeScanner
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString(serviceUUID))
.build()
val scanSettings = ScanSettings.Builder()
.setLegacy(false)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val scanCollector = ScanCollector(logger, podID)
logger.debug(LTag.PUMPBTCOMM, "Scanning with filters: $filter settings$scanSettings")
scanner.startScan(Arrays.asList(filter), scanSettings, scanCollector)
Thread.sleep(SCAN_DURATION_MS.toLong())
scanner.flushPendingScanResults(scanCollector)
scanner.stopScan(scanCollector)
val collected = scanCollector.collect()
if (collected.isEmpty()) {
throw ScanFailNotFoundException()
} else if (collected.size > 1) {
throw ScanFailFoundTooManyException(collected)
}
return collected[0]
}
companion object {
const val SCAN_FOR_SERVICE_UUID = "00004024-0000-1000-8000-00805F9B34FB"
const val POD_ID_NOT_ACTIVATED = 4294967294L
private const val SCAN_DURATION_MS = 5000
}
}

View file

@ -1,66 +0,0 @@
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<String, ScanResult> found;
private int scanFailed;
public ScanCollector(AAPSLogger logger, long podID) {
this.podID = podID;
this.logger = logger;
this.found = new ConcurrentHashMap<String, ScanResult>();
}
@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<BleDiscoveredDevice> collect()
throws ScanFailException {
List<BleDiscoveredDevice> 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);
}
}

View file

@ -0,0 +1,47 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
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
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class ScanCollector(private val logger: AAPSLogger, private val podID: Long) : ScanCallback() {
// there could be different threads calling the onScanResult callback
private val found: ConcurrentHashMap<String, ScanResult> = ConcurrentHashMap()
private var scanFailed = 0
override fun onScanResult(callbackType: Int, result: ScanResult) {
// callbackType will be ALL
logger.debug(LTag.PUMPBTCOMM, "Scan found: $result")
found[result.device.address] = result
}
override fun onScanFailed(errorCode: Int) {
logger.warn(LTag.PUMPBTCOMM, "Scan failed with errorCode: $errorCode")
super.onScanFailed(errorCode)
}
@Throws(ScanFailException::class) fun collect(): List<BleDiscoveredDevice> {
val ret: MutableList<BleDiscoveredDevice> = ArrayList()
if (scanFailed != 0) {
throw ScanFailException(scanFailed)
}
logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: $podID")
for (result in found.values) {
try {
val device = BleDiscoveredDevice(result, podID)
ret.add(device)
logger.debug(LTag.PUMPBTCOMM, "ScanCollector found: " + result.toString() + "Pod ID: " + podID)
} catch (e: DiscoveredInvalidPodException) {
logger.debug(LTag.PUMPBTCOMM, "ScanCollector: pod not matching$e")
// this is not the POD we are looking for
}
}
return Collections.unmodifiableList(ret)
}
}