Switch to Kotlin.
and small fixes after conversion
This commit is contained in:
parent
b4d769844c
commit
647793e0b7
56 changed files with 726 additions and 964 deletions
|
@ -1,125 +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.BluetoothGattCharacteristic;
|
|
||||||
import android.bluetooth.BluetoothManager;
|
|
||||||
import android.bluetooth.BluetoothProfile;
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 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 BleIO bleio;
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void activateNewPod()
|
|
||||||
throws InterruptedException,
|
|
||||||
ScanFailException,
|
|
||||||
FailedToConnectException,
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connect()
|
|
||||||
throws FailedToConnectException,
|
|
||||||
CouldNotSendBleException,
|
|
||||||
InterruptedException,
|
|
||||||
BleIOBusyException, TimeoutException, CouldNotConfirmWrite, CouldNotEnableNotifications, DescriptorNotFoundException, CouldNotConfirmDescriptorWriteException {
|
|
||||||
// TODO: locking?
|
|
||||||
|
|
||||||
BluetoothDevice podDevice = this.bluetoothAdapter.getRemoteDevice(this.podAddress);
|
|
||||||
|
|
||||||
Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets = new EnumMap<CharacteristicType, BlockingQueue<byte[]>>(CharacteristicType.class);
|
|
||||||
incomingPackets.put(CharacteristicType.CMD, new LinkedBlockingDeque<>());
|
|
||||||
incomingPackets.put(CharacteristicType.DATA, new LinkedBlockingDeque<>());
|
|
||||||
incomingPackets = Collections.unmodifiableMap(incomingPackets);
|
|
||||||
|
|
||||||
BleCommCallbacks bleCommCallbacks = new BleCommCallbacks(aapsLogger, incomingPackets);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to " + this.podAddress);
|
|
||||||
boolean autoConnect = true;
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
autoConnect = false;
|
|
||||||
// TODO: remove this in the future
|
|
||||||
// it's easier to start testing from scratch on each run.
|
|
||||||
}
|
|
||||||
gatt = podDevice.connectGatt(this.context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE);
|
|
||||||
|
|
||||||
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS);
|
|
||||||
|
|
||||||
int connectionState = this.bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT);
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: " + connectionState);
|
|
||||||
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
|
||||||
throw new FailedToConnectException(this.podAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceDiscoverer discoverer = new ServiceDiscoverer(this.aapsLogger, gatt, bleCommCallbacks);
|
|
||||||
Map<CharacteristicType, BluetoothGattCharacteristic> chars = discoverer.discoverServices();
|
|
||||||
|
|
||||||
this.bleio = new BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks);
|
|
||||||
this.aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod");
|
|
||||||
this.bleio.sendAndConfirmPacket(CharacteristicType.CMD, new BleCommandHello(CONTROLLER_ID).asByteArray());
|
|
||||||
this.bleio.readyToRead();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
||||||
|
|
||||||
|
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import javax.inject.Inject
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashCommunicationManager
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
|
||||||
|
import kotlin.Throws
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotSendBleException
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.BleIOBusyException
|
||||||
|
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.exceptions.CouldNotConfirmDescriptorWriteException
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
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.BleManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ServiceDiscoverer
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
|
import android.content.Context
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.BlockingQueue
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class BleManager @Inject constructor(private val context: Context) : OmnipodDashCommunicationManager {
|
||||||
|
|
||||||
|
private val bluetoothAdapter: BluetoothAdapter
|
||||||
|
private val bluetoothManager: BluetoothManager
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
private var podAddress: String? = null
|
||||||
|
private var gatt: BluetoothGatt? = null
|
||||||
|
private var bleio: BleIO? = null
|
||||||
|
@Throws(InterruptedException::class, ScanFailException::class, FailedToConnectException::class, CouldNotSendBleException::class, BleIOBusyException::class, TimeoutException::class, CouldNotConfirmWrite::class, CouldNotEnableNotifications::class, DescriptorNotFoundException::class, CouldNotConfirmDescriptorWriteException::class)
|
||||||
|
fun activateNewPod() {
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
|
||||||
|
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(FailedToConnectException::class, CouldNotSendBleException::class, InterruptedException::class, BleIOBusyException::class, TimeoutException::class, CouldNotConfirmWrite::class, CouldNotEnableNotifications::class, DescriptorNotFoundException::class, CouldNotConfirmDescriptorWriteException::class)
|
||||||
|
fun connect() {
|
||||||
|
// TODO: locking?
|
||||||
|
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||||
|
var 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)
|
||||||
|
this.gatt = gatt
|
||||||
|
|
||||||
|
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()
|
||||||
|
bleio = BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod")
|
||||||
|
bleio!!.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).asByteArray())
|
||||||
|
bleio!!.readyToRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val CONNECT_TIMEOUT_MS = 5000
|
||||||
|
private const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else.
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
|
bluetoothAdapter = bluetoothManager.adapter
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public enum CharacteristicType {
|
|
||||||
CMD("1a7e2441-e3ed-4464-8b7e-751e03d0dc5f"),
|
|
||||||
DATA("1a7e2442-e3ed-4464-8b7e-751e03d0dc5f");
|
|
||||||
|
|
||||||
public final String value;
|
|
||||||
|
|
||||||
CharacteristicType(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CharacteristicType byValue(String value) {
|
|
||||||
for (CharacteristicType type : values()) {
|
|
||||||
if (type.value.equals(value)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unknown Characteristic Type: " + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUUID() {
|
|
||||||
return new UUID(
|
|
||||||
new BigInteger(this.value.replace("-", "").substring(0, 16), 16).longValue(),
|
|
||||||
new BigInteger(this.value.replace("-", "").substring(16), 16).longValue()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
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 {
|
||||||
|
for (type in values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown Characteristic Type: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
import android.bluetooth.BluetoothGattService;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.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;
|
|
||||||
|
|
||||||
public class ServiceDiscoverer {
|
|
||||||
private static final String SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f";
|
|
||||||
private static final int DISCOVER_SERVICES_TIMEOUT_MS = 5000;
|
|
||||||
|
|
||||||
private final BluetoothGatt gatt;
|
|
||||||
private final BleCommCallbacks bleCallbacks;
|
|
||||||
private final AAPSLogger logger;
|
|
||||||
private Map<CharacteristicType, BluetoothGattCharacteristic> chars;
|
|
||||||
|
|
||||||
public ServiceDiscoverer(AAPSLogger logger, BluetoothGatt gatt, BleCommCallbacks bleCallbacks) {
|
|
||||||
this.gatt = gatt;
|
|
||||||
this.bleCallbacks = bleCallbacks;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UUID uuidFromString(String s) {
|
|
||||||
return new UUID(
|
|
||||||
new BigInteger(s.replace("-", "").substring(0, 16), 16).longValue(),
|
|
||||||
new BigInteger(s.replace("-", "").substring(16), 16).longValue()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* This is first step after connection establishment
|
|
||||||
*/
|
|
||||||
public Map<CharacteristicType, BluetoothGattCharacteristic> discoverServices()
|
|
||||||
throws InterruptedException,
|
|
||||||
ServiceNotFoundException,
|
|
||||||
CharacteristicNotFoundException {
|
|
||||||
|
|
||||||
logger.debug(LTag.PUMPBTCOMM, "Discovering services");
|
|
||||||
gatt.discoverServices();
|
|
||||||
this.bleCallbacks.waitForServiceDiscovery(DISCOVER_SERVICES_TIMEOUT_MS);
|
|
||||||
logger.debug(LTag.PUMPBTCOMM, "Services discovered");
|
|
||||||
|
|
||||||
BluetoothGattService service = gatt.getService(
|
|
||||||
uuidFromString(SERVICE_UUID));
|
|
||||||
if (service == null) {
|
|
||||||
throw new ServiceNotFoundException(SERVICE_UUID);
|
|
||||||
}
|
|
||||||
BluetoothGattCharacteristic cmdChar = service.getCharacteristic(CharacteristicType.CMD.getUUID());
|
|
||||||
if (cmdChar == null) {
|
|
||||||
throw new CharacteristicNotFoundException(CharacteristicType.CMD.getValue());
|
|
||||||
}
|
|
||||||
BluetoothGattCharacteristic dataChar = service.getCharacteristic(CharacteristicType.DATA.getUUID());
|
|
||||||
if (dataChar == null) {
|
|
||||||
throw new CharacteristicNotFoundException(CharacteristicType.DATA.getValue());
|
|
||||||
}
|
|
||||||
Map<CharacteristicType, BluetoothGattCharacteristic> chars = new EnumMap(CharacteristicType.class);
|
|
||||||
chars.put(CharacteristicType.CMD, cmdChar);
|
|
||||||
chars.put(CharacteristicType.DATA, dataChar);
|
|
||||||
this.chars = Collections.unmodifiableMap(chars);
|
|
||||||
return this.chars;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
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.CharacteristicType
|
||||||
|
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(
|
||||||
|
uuidFromString(SERVICE_UUID))
|
||||||
|
?: throw ServiceNotFoundException(SERVICE_UUID)
|
||||||
|
val cmdChar = service.getCharacteristic(CharacteristicType.CMD.uUID)
|
||||||
|
?: throw CharacteristicNotFoundException(CharacteristicType.CMD.value)
|
||||||
|
val dataChar = service.getCharacteristic(CharacteristicType.DATA.uUID)
|
||||||
|
?: throw CharacteristicNotFoundException(CharacteristicType.DATA.value)
|
||||||
|
var chars = mapOf(CharacteristicType.CMD to cmdChar,
|
||||||
|
CharacteristicType.DATA to dataChar)
|
||||||
|
return chars
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f"
|
||||||
|
private const val DISCOVER_SERVICES_TIMEOUT_MS = 5000
|
||||||
|
private fun uuidFromString(s: String): UUID {
|
||||||
|
return UUID(
|
||||||
|
BigInteger(s.replace("-", "").substring(0, 16), 16).toLong(),
|
||||||
|
BigInteger(s.replace("-", "").substring(16), 16).toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,147 +0,0 @@
|
||||||
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<CharacteristicType, BlockingQueue<byte[]>> incomingPackets;
|
|
||||||
private final BlockingQueue<CharacteristicWriteConfirmation> writeQueue;
|
|
||||||
private final BlockingQueue<DescriptorWriteConfirmation> descriptorWriteQueue;
|
|
||||||
|
|
||||||
public BleCommCallbacks(AAPSLogger aapsLogger, Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets) {
|
|
||||||
this.serviceDiscoveryComplete = new CountDownLatch(1);
|
|
||||||
this.connected = new CountDownLatch(1);
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.incomingPackets = incomingPackets;
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
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.CouldNotConfirmWrite
|
||||||
|
import java.util.*
|
||||||
|
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(aapsLogger: AAPSLogger, incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>) : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
private val serviceDiscoveryComplete: CountDownLatch
|
||||||
|
private val connected: CountDownLatch
|
||||||
|
private val aapsLogger: AAPSLogger
|
||||||
|
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>
|
||||||
|
private val writeQueue: BlockingQueue<CharacteristicWriteConfirmation>
|
||||||
|
private val descriptorWriteQueue: BlockingQueue<DescriptorWriteConfirmation>
|
||||||
|
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, CouldNotConfirmWrite::class)
|
||||||
|
fun confirmWrite(expectedPayload: ByteArray, timeout_ms: Int) {
|
||||||
|
val received = writeQueue.poll(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
?: throw TimeoutException()
|
||||||
|
if (!Arrays.equals(expectedPayload, received.payload)) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm write. Got " + received.payload + ".Excepted: " + expectedPayload + ". Status: " + received.status)
|
||||||
|
throw CouldNotConfirmWrite(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)
|
||||||
|
var received: ByteArray? = null
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
received = characteristic.value
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicWrite value " + characteristic.getStringValue(0))
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "OnCharacteristicWrite with status/char/value " +
|
||||||
|
status + "/" +
|
||||||
|
byValue(characteristic.uuid.toString()) + "/" +
|
||||||
|
received)
|
||||||
|
try {
|
||||||
|
if (writeQueue.size > 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Write confirm queue should be empty. found: " + writeQueue.size)
|
||||||
|
writeQueue.clear()
|
||||||
|
}
|
||||||
|
val offered = writeQueue.offer(CharacteristicWriteConfirmation(received, status), 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 = descriptorWriteQueue.poll(timeout_ms.toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
if (descriptorUUID != confirmed.uuid) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not confirm descriptor write. Got " + confirmed.uuid + ".Expected: " + descriptorUUID + ". Status: " + confirmed.status)
|
||||||
|
throw CouldNotConfirmDescriptorWriteException(confirmed.uuid!!, descriptorUUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||||
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
|
var uuid: String? = null
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
uuid = descriptor.uuid.toString()
|
||||||
|
}
|
||||||
|
val confirmation = DescriptorWriteConfirmation(status, uuid)
|
||||||
|
try {
|
||||||
|
if (descriptorWriteQueue.size > 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Descriptor write queue should be empty, found: " + descriptorWriteQueue.size)
|
||||||
|
descriptorWriteQueue.clear()
|
||||||
|
}
|
||||||
|
val offered = descriptorWriteQueue.offer(confirmation, 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 other thread should be waiting for the exchange
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
serviceDiscoveryComplete = CountDownLatch(1)
|
||||||
|
connected = CountDownLatch(1)
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
this.incomingPackets = incomingPackets
|
||||||
|
writeQueue = LinkedBlockingQueue(1)
|
||||||
|
descriptorWriteQueue = LinkedBlockingQueue(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
|
||||||
|
|
||||||
|
class CharacteristicWriteConfirmation(var payload: ByteArray?, var status: Int)
|
|
@ -1,11 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
|
||||||
|
|
||||||
|
class DescriptorWriteConfirmation(var status: Int, var uuid: String?)
|
|
@ -1,22 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
||||||
|
|
||||||
|
abstract class BleCommand {
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asByteArray(): ByteArray {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
|
|
||||||
|
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
)
|
|
@ -1,30 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
fun byValue(value: Byte): BleCommandType {
|
||||||
|
for (type in values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown BleCommandType: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
|
||||||
|
|
||||||
public class BleIOBusyException extends Exception{
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class BleIOBusyException : Exception()
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CharacteristicNotFoundException(cmdCharacteristicUuid: String) : FailedToConnectException("characteristic not found: $cmdCharacteristicUuid")
|
|
@ -1,12 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotConfirmDescriptorWriteException(private val received: String, private val expected: String) : Exception()
|
|
@ -1,13 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotConfirmWrite(private val sent: ByteArray, confirmed: ByteArray) : Exception() {
|
||||||
|
|
||||||
|
private val confirmed: Any
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.confirmed = confirmed
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
@ -1,7 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
|
||||||
|
|
||||||
public class CouldNotSendBleException extends Exception {
|
|
||||||
public CouldNotSendBleException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotSendBleException(msg: String?) : Exception(msg)
|
|
@ -1,4 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
|
||||||
|
|
||||||
public class DescriptorNotFoundException extends Exception {
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class DescriptorNotFoundException : Exception()
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
import android.os.ParcelUuid
|
||||||
|
|
||||||
|
class DiscoveredInvalidPodException(message: String, serviceUUIds: List<ParcelUuid?>) : Exception("$message service UUIDs: $serviceUUIds")
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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") {}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
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>
|
||||||
|
val discoveredDevices: List<BleDiscoveredDevice>
|
||||||
|
get() = Collections.unmodifiableList(devices)
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.devices = ArrayList(devices)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions;
|
|
||||||
|
|
||||||
public class ScanFailNotFoundException extends ScanFailException {
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class ScanFailNotFoundException : ScanFailException()
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class ServiceNotFoundException(serviceUuid: String) : FailedToConnectException("service not found: $serviceUuid")
|
|
@ -1,140 +0,0 @@
|
||||||
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<CharacteristicType, BluetoothGattCharacteristic> chars;
|
|
||||||
private final Map<CharacteristicType, BlockingQueue<byte[]>> incomingPackets;
|
|
||||||
private final BluetoothGatt gatt;
|
|
||||||
private final BleCommCallbacks bleCommCallbacks;
|
|
||||||
|
|
||||||
private IOState state;
|
|
||||||
|
|
||||||
public BleIO(AAPSLogger aapsLogger, Map<CharacteristicType, BluetoothGattCharacteristic> chars, Map<CharacteristicType, BlockingQueue<byte[]>> 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<BluetoothGattDescriptor> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
/***
|
||||||
|
*
|
||||||
|
* @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, CouldNotConfirmWrite::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
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
state = IOState.IDLE
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io;
|
|
||||||
|
|
||||||
public enum IOState {
|
|
||||||
IDLE,
|
|
||||||
WRITING,
|
|
||||||
READING;
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
|
||||||
|
|
||||||
|
enum class IOState {
|
||||||
|
IDLE, WRITING, READING
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet;
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet
|
||||||
|
|
||||||
public class BlePacket {
|
class BlePacket
|
||||||
}
|
|
|
@ -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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
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]) != "4024") {
|
||||||
|
// 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: " + 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 {
|
||||||
|
|
||||||
|
private fun extractUUID16(uuid: ParcelUuid): String {
|
||||||
|
return uuid.toString().substring(4, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
validateServiceUUIDs()
|
||||||
|
validatePodID()
|
||||||
|
lotNo = parseLotNo()
|
||||||
|
sequenceNo = parseSeqNo()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.size == 0) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
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>
|
||||||
|
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) {
|
||||||
|
scanFailed = errorCode
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
found = ConcurrentHashMap()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue