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

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
interface BLECommCallback {
open fun onBLEConnected() {}
open fun onNotification(notification: ByteArray) {}
open fun onIndication(indication: ByteArray) {}
open fun onSendMessageError(reason: String)
fun onBLEConnected()
fun onNotification(notification: ByteArray)
fun onIndication(indication: ByteArray)
fun onSendMessageError(reason: String)
}
@Singleton
@ -237,7 +236,7 @@ class BLEComm @Inject internal constructor(
}
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()
if (characteristic.getUuid() == UUID.fromString(READ_UUID)) {
@ -249,8 +248,8 @@ class BLEComm @Inject internal constructor(
mReadPacket?.addData(value)
}
if (mReadPacket?.allDataReceived() == true) {
mReadPacket = null
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.ui.UiInteraction
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.encryption.Crypt
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.comm.WriteCommand
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.bus.RxBus
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.utils.DateUtil
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.core.ObservableEmitter
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.joda.time.DateTime
@ -67,16 +70,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var pumpSync: PumpSync
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var writeCommand: WriteCommand
companion object {
private const val COMMAND_AUTH_REQ: Byte = 5
}
val timeUtil = MedtrumTimeUtil()
private val disposable = CompositeDisposable()
private val mBinder: IBinder = LocalBinder()
private val mCrypt = Crypt()
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() {
super.onCreate()
@ -106,12 +111,12 @@ class MedtrumService : DaggerService(), BLECommCallback {
fun stopConnecting() {
// TODO proper way for this might need send commands etc
bleComm.stopConnecting()
// bleComm.stopConnecting()
}
fun disconnect(from: String) {
// TODO proper way for this might need send commands etc
bleComm.disconnect(from)
// bleComm.disconnect(from)
}
fun readPumpStatus() {
@ -145,49 +150,15 @@ class MedtrumService : DaggerService(), BLECommCallback {
// 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 {
if (!isConnected) return false
// TODO
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 */
override fun onBLEConnected() {
// TODO Replace by FSM Entry?
authorize()
currentState.onConnected()
}
override fun onNotification(notification: ByteArray) {
@ -197,7 +168,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onIndication(indication: ByteArray) {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onIndication" + indication.contentToString())
// TODO
currentState.onIndication(indication)
}
override fun onSendMessageError(reason: String) {
@ -207,6 +178,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
/** Service stuff */
inner class LocalBinder : Binder() {
val serviceInstance: MedtrumService
get() = this@MedtrumService
}
@ -218,4 +190,208 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
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
}
}