Implemented state to initialize pump connection

This commit is contained in:
jbr7rr 2023-02-26 19:41:41 +01:00
parent 27159d6e47
commit 485826682e
4 changed files with 307 additions and 51 deletions
pump/medtrum/src/main/java/info/nightscout/pump/medtrum

View file

@ -0,0 +1,61 @@
package info.nightscout.pump.medtrum.comm
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.encryption.Crypt
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import info.nightscout.shared.utils.DateUtil
import javax.inject.Inject
import javax.inject.Singleton
// TODO object would be better? Or split this class up in an entirely different way
@Singleton
class WriteCommand @Inject internal constructor(
private val dateUtil: DateUtil
) {
val COMMAND_SYNCHRONIZE: Byte = 3
val COMMAND_SUBSCRIBE: Byte = 4
val COMMAND_AUTH_REQ: Byte = 5
val COMMAND_GET_DEVICE_TYPE: Byte = 6
val COMMAND_SET_TIME: Byte = 10
val COMMAND_GET_TIME: Byte = 11
val COMMAND_SET_TIME_ZONE: Byte = 12
private val mCrypt = Crypt()
private val timeUtil = MedtrumTimeUtil()
fun authorize(deviceSerial: Long): ByteArray {
val role = 2 // Fixed to 2 for pump
val key = mCrypt.keyGen(deviceSerial)
return byteArrayOf(COMMAND_AUTH_REQ) + byteArrayOf(role.toByte()) + 0.toByteArray(4) + key.toByteArray(4)
}
fun getDeviceType(): ByteArray {
return byteArrayOf(COMMAND_GET_DEVICE_TYPE)
}
fun getTime(): ByteArray {
return byteArrayOf(COMMAND_GET_TIME)
}
fun setTime(): ByteArray {
val time = timeUtil.getCurrentTimePumpSeconds()
return byteArrayOf(COMMAND_SET_TIME) + 2.toByte() + time.toByteArray(4)
}
fun setTimeZone(): ByteArray {
val time = timeUtil.getCurrentTimePumpSeconds()
var offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
if (offsetMins < 0) offsetMins += 65536
return byteArrayOf(COMMAND_SET_TIME_ZONE) + offsetMins.toByteArray(2) + time.toByteArray(4)
}
fun synchronize(): ByteArray {
return byteArrayOf(COMMAND_SYNCHRONIZE)
}
fun subscribe(): ByteArray {
return byteArrayOf(COMMAND_SUBSCRIBE) + 4095.toByteArray(2)
}
}

View file

@ -49,11 +49,10 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
interface BLECommCallback { interface BLECommCallback {
fun onBLEConnected()
open fun onBLEConnected() {} fun onNotification(notification: ByteArray)
open fun onNotification(notification: ByteArray) {} fun onIndication(indication: ByteArray)
open fun onIndication(indication: ByteArray) {} fun onSendMessageError(reason: String)
open fun onSendMessageError(reason: String)
} }
@Singleton @Singleton
@ -237,7 +236,7 @@ class BLEComm @Inject internal constructor(
} }
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged") aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged data: " + characteristic.value + "UUID: " + characteristic.getUuid().toString())
val value = characteristic.getValue() val value = characteristic.getValue()
if (characteristic.getUuid() == UUID.fromString(READ_UUID)) { if (characteristic.getUuid() == UUID.fromString(READ_UUID)) {
@ -249,8 +248,8 @@ class BLEComm @Inject internal constructor(
mReadPacket?.addData(value) mReadPacket?.addData(value)
} }
if (mReadPacket?.allDataReceived() == true) { if (mReadPacket?.allDataReceived() == true) {
mReadPacket = null
mReadPacket?.getData()?.let { mCallback?.onIndication(it) } mReadPacket?.getData()?.let { mCallback?.onIndication(it) }
mReadPacket = null
} }
} }
} }

View file

@ -23,8 +23,10 @@ import info.nightscout.interfaces.queue.Command
import info.nightscout.interfaces.queue.CommandQueue import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.interfaces.ui.UiInteraction import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.pump.medtrum.MedtrumPlugin import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.encryption.Crypt import info.nightscout.pump.medtrum.comm.WriteCommand
import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit import info.nightscout.rx.events.EventAppExit
@ -38,6 +40,7 @@ import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.core.ObservableEmitter
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import org.joda.time.DateTime import org.joda.time.DateTime
@ -67,16 +70,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var pumpSync: PumpSync @Inject lateinit var pumpSync: PumpSync
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var writeCommand: WriteCommand
companion object { val timeUtil = MedtrumTimeUtil()
private const val COMMAND_AUTH_REQ: Byte = 5
}
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val mBinder: IBinder = LocalBinder() private val mBinder: IBinder = LocalBinder()
private val mCrypt = Crypt()
private var mDeviceSN: Long = 0 private var mDeviceSN: Long = 0
private var currentState: State = IdleState()
// TODO: Stuff like this in a settings class?
private var mLastDeviceTime: Long = 0
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -106,12 +111,12 @@ class MedtrumService : DaggerService(), BLECommCallback {
fun stopConnecting() { fun stopConnecting() {
// TODO proper way for this might need send commands etc // TODO proper way for this might need send commands etc
bleComm.stopConnecting() // bleComm.stopConnecting()
} }
fun disconnect(from: String) { fun disconnect(from: String) {
// TODO proper way for this might need send commands etc // TODO proper way for this might need send commands etc
bleComm.disconnect(from) // bleComm.disconnect(from)
} }
fun readPumpStatus() { fun readPumpStatus() {
@ -145,49 +150,15 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO // TODO
} }
fun tempBasal(percent: Int, durationInHours: Int): Boolean {
// TODO
return false
}
fun highTempBasal(percent: Int): Boolean {
// TODO
return false
}
fun tempBasalShortDuration(percent: Int, durationInMinutes: Int): Boolean {
if (durationInMinutes != 15 && durationInMinutes != 30) {
aapsLogger.error(LTag.PUMPCOMM, "Wrong duration param")
return false
}
// TODO
return false
}
fun tempBasalStop(): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun updateBasalsInPump(profile: Profile): Boolean { fun updateBasalsInPump(profile: Profile): Boolean {
if (!isConnected) return false if (!isConnected) return false
// TODO // TODO
return false return false
} }
private fun authorize() {
aapsLogger.debug(LTag.PUMPCOMM, "Start auth!")
val role = 2 // Fixed to 2 for pump
val key = mCrypt.keyGen(mDeviceSN)
val commandData = byteArrayOf(COMMAND_AUTH_REQ) + byteArrayOf(role.toByte()) + 0.toByteArray(4) + key.toByteArray(4)
bleComm.sendMessage(commandData)
}
/** BLECommCallbacks */ /** BLECommCallbacks */
override fun onBLEConnected() { override fun onBLEConnected() {
// TODO Replace by FSM Entry? currentState.onConnected()
authorize()
} }
override fun onNotification(notification: ByteArray) { override fun onNotification(notification: ByteArray) {
@ -197,7 +168,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onIndication(indication: ByteArray) { override fun onIndication(indication: ByteArray) {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onIndication" + indication.contentToString()) aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onIndication" + indication.contentToString())
// TODO currentState.onIndication(indication)
} }
override fun onSendMessageError(reason: String) { override fun onSendMessageError(reason: String) {
@ -207,6 +178,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
/** Service stuff */ /** Service stuff */
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
val serviceInstance: MedtrumService val serviceInstance: MedtrumService
get() = this@MedtrumService get() = this@MedtrumService
} }
@ -218,4 +190,208 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return Service.START_STICKY return Service.START_STICKY
} }
/**
* States are used to keep track of the communication and to guide the flow
*/
private fun toState(nextState: State) {
currentState = nextState
currentState.onEnter()
}
// State class, Can we move this to different file?
private abstract inner class State {
open fun onEnter() {}
open fun onIndication(data: ByteArray) {
aapsLogger.debug(LTag.PUMPCOMM, "onIndicationr: " + this.toString() + "Should not be called here!")
}
open fun onConnected() {}
}
private inner class IdleState : State() {
override fun onConnected() {
toState(AuthState())
}
}
private inner class AuthState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached AuthState")
bleComm.sendMessage(writeCommand.authorize(mDeviceSN))
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
// TODO Get pump version info (do we care?)
if (responseCode == 0 && commandCode == writeCommand.COMMAND_AUTH_REQ) {
// Succes!
toState(GetDeviceTypeState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class GetDeviceTypeState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetDeviceTypeState")
bleComm.sendMessage(writeCommand.getDeviceType())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
// TODO Get device type (do we care?)
if (responseCode == 0 && commandCode == writeCommand.COMMAND_GET_DEVICE_TYPE) {
// Succes!
toState(GetTimeState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class GetTimeState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetTimeState")
bleComm.sendMessage(writeCommand.getTime())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
val time = data.copyOfRange(6, 10).toLong()
if (responseCode == 0 && commandCode == writeCommand.COMMAND_GET_TIME) {
// Succes!
mLastDeviceTime = time
val currTimeSec = dateUtil.nowWithoutMilliseconds() / 1000
if (abs(timeUtil.convertPumpTimeToSystemTimeSeconds(time) - currTimeSec) <= 5) { // Allow 5 sec deviation
toState(SynchronizeState())
} else {
aapsLogger.debug(
LTag.PUMPCOMM,
"GetTimeState.onIndication need to set time. systemTime: " + currTimeSec + " PumpTime: " + time + " Pump Time to system time: " + timeUtil.convertPumpTimeToSystemTimeSeconds(
time
)
)
toState(SetTimeState())
}
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class SetTimeState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeState")
bleComm.sendMessage(writeCommand.setTime())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
if (responseCode == 0 && commandCode == writeCommand.COMMAND_SET_TIME) {
// Succes!
toState(SetTimeZoneState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class SetTimeZoneState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeZoneState")
bleComm.sendMessage(writeCommand.setTimeZone())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
if (responseCode == 0 && commandCode == writeCommand.COMMAND_SET_TIME_ZONE) {
// Succes!
toState(SynchronizeState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class SynchronizeState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SynchronizeState")
bleComm.sendMessage(writeCommand.synchronize())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
if (responseCode == 0 && commandCode == writeCommand.COMMAND_SYNCHRONIZE) {
// Succes!
// TODO: Handle pump state parameters
toState(SubscribeState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class SubscribeState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SubscribeState")
bleComm.sendMessage(writeCommand.subscribe())
}
override fun onIndication(data: ByteArray) {
// TODO Create class for this? Maybe combine with authorize write command, something like danaRS packets? + Unit Tests
val commandCode: Byte = data.copyOfRange(1, 2).toInt().toByte()
val responseCode = data.copyOfRange(4, 6).toInt()
if (responseCode == 0 && commandCode == writeCommand.COMMAND_SUBSCRIBE) {
// Succes!
toState(ReadyState())
} else {
// Failure
bleComm.disconnect("Failure")
toState(IdleState())
}
}
}
private inner class ReadyState : State() {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached ReadyState!")
}
// Just a placeholder, this state is reached when the patch is ready to receive commands (Bolus, temp basal and whatever)
}
} }

View file

@ -0,0 +1,20 @@
package info.nightscout.pump.medtrum.util
import java.time.Duration
import java.time.Instant
class MedtrumTimeUtil {
fun getCurrentTimePumpSeconds() : Long {
val startInstant = Instant.parse("2020-01-01T00:00:00Z")
val currentInstant = Instant.now()
return Duration.between(startInstant, currentInstant).seconds
}
fun convertPumpTimeToSystemTimeSeconds(pumpTime: Long) : Long {
val startInstant = Instant.parse("2020-01-01T00:00:00Z")
val pumpInstant = startInstant.plusSeconds(pumpTime)
val epochInstant = Instant.EPOCH
return Duration.between(epochInstant, pumpInstant).seconds
}
}