Initial bolus implementation, further connection improvements

This commit is contained in:
jbr7rr 2023-05-25 13:32:24 +02:00
parent a9ebdcfe68
commit 3027c2ffa5
12 changed files with 279 additions and 49 deletions

View file

@ -27,6 +27,7 @@ dependencies {
implementation project(':core:main') implementation project(':core:main')
implementation project(':core:ui') implementation project(':core:ui')
implementation project(':core:validators') implementation project(':core:validators')
implementation project(':pump:pump-common')
implementation project(':core:utils') implementation project(':core:utils')
testImplementation project(':core:main') testImplementation project(':core:main')

View file

@ -8,12 +8,15 @@ import android.os.IBinder
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.notifications.Notification import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.pump.DetailedBolusInfo import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
import info.nightscout.interfaces.pump.Pump import info.nightscout.interfaces.pump.Pump
import info.nightscout.interfaces.pump.PumpEnactResult import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.pump.PumpPluginBase import info.nightscout.interfaces.pump.PumpPluginBase
@ -52,6 +55,7 @@ import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.round import kotlin.math.round
@Singleton class MedtrumPlugin @Inject constructor( @Singleton class MedtrumPlugin @Inject constructor(
@ -59,6 +63,7 @@ import kotlin.math.round
aapsLogger: AAPSLogger, aapsLogger: AAPSLogger,
rh: ResourceHelper, rh: ResourceHelper,
commandQueue: CommandQueue, commandQueue: CommandQueue,
private val constraintChecker: Constraints,
private val sp: SP, private val sp: SP,
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
private val rxBus: RxBus, private val rxBus: RxBus,
@ -69,6 +74,7 @@ import kotlin.math.round
private val uiInteraction: UiInteraction, private val uiInteraction: UiInteraction,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val pumpSync: PumpSync, private val pumpSync: PumpSync,
private val detailedBolusInfoStorage: DetailedBolusInfoStorage,
private val temporaryBasalStorage: TemporaryBasalStorage private val temporaryBasalStorage: TemporaryBasalStorage
) : PumpPluginBase( ) : PumpPluginBase(
PluginDescription() PluginDescription()
@ -204,7 +210,7 @@ import kotlin.math.round
} }
override fun lastDataTime(): Long { override fun lastDataTime(): Long {
return medtrumPump.lastTimeReceivedFromPump * 1000L return medtrumPump.lastTimeReceivedFromPump
} }
override val baseBasalRate: Double override val baseBasalRate: Double
@ -216,14 +222,46 @@ import kotlin.math.round
override val batteryLevel: Int override val batteryLevel: Int
get() = 0 // TODO get() = 0 // TODO
@Synchronized
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
return PumpEnactResult(injector) // TODO aapsLogger.debug(LTag.PUMP, "deliverTreatment: " + detailedBolusInfo.insulin + "U")
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
detailedBolusInfo.insulin = constraintChecker.applyBolusConstraints(Constraint(detailedBolusInfo.insulin)).value()
return if (detailedBolusInfo.insulin > 0 && detailedBolusInfo.carbs == 0.0) {
aapsLogger.debug(LTag.PUMP, "deliverTreatment: Delivering bolus: " + detailedBolusInfo.insulin + "U")
detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history
val t = EventOverviewBolusProgress.Treatment(0.0, 0, detailedBolusInfo.bolusType == DetailedBolusInfo.BolusType.SMB, detailedBolusInfo.id)
val connectionOK = medtrumService?.setBolus(detailedBolusInfo.insulin, t) ?: false
val result = PumpEnactResult(injector)
result.success = connectionOK && abs(detailedBolusInfo.insulin - t.insulin) < pumpDescription.bolusStep
result.bolusDelivered = t.insulin
if (!result.success) {
// Todo error code?
result.comment = "error"
} else {
result.comment = "ok"
}
aapsLogger.debug(LTag.PUMP, "deliverTreatment: OK. Success: ${result.success} Asked: ${detailedBolusInfo.insulin} Delivered: ${result.bolusDelivered}")
result
} else {
aapsLogger.debug(LTag.PUMP, "deliverTreatment: Invalid input")
val result = PumpEnactResult(injector)
result.success = false
result.bolusDelivered = 0.0
result.comment = rh.gs(info.nightscout.core.ui.R.string.invalid_input)
aapsLogger.error("deliverTreatment: Invalid input")
result
}
} }
override fun stopBolusDelivering() { override fun stopBolusDelivering() {
// TODO if (!isInitialized()) return
aapsLogger.info(LTag.PUMP, "stopBolusDelivering")
medtrumService?.stopBolus()
} }
@Synchronized
override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult { override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false) if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)

View file

@ -12,6 +12,7 @@ import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
@ -66,7 +67,15 @@ class MedtrumPump @Inject constructor(
set(value) { set(value) {
_primeProgress.value = value _primeProgress.value = value
} }
private val _lastBasalRate = MutableStateFlow(0.0)
val lastBasalRateFlow: StateFlow<Double> = _lastBasalRate
var lastBasalRate: Double
get() = _lastBasalRate.value
set(value) {
_lastBasalRate.value = value
}
/** Stuff stored in SP */ /** Stuff stored in SP */
private var _patchSessionToken = 0L private var _patchSessionToken = 0L
var patchSessionToken: Long var patchSessionToken: Long
@ -136,9 +145,17 @@ class MedtrumPump @Inject constructor(
var alarmFlags = 0 var alarmFlags = 0
var alarmParameter = 0 var alarmParameter = 0
// bolus status
var bolusingTreatment: EventOverviewBolusProgress.Treatment? = null // actually delivered treatment
var bolusAmountToBeDelivered = 0.0 // amount to be delivered
var bolusProgressLastTimeStamp: Long = 0 // timestamp of last bolus progress message
var bolusStopped = false // bolus finished
var bolusStopForced = false // bolus forced to stop by user
var bolusDone = false // success end
// Last basal status update // Last basal status update
// TODO: Save this in SP? // TODO: Save this in SP?
var lastBasalRate = 0.0
var lastBasalSequence = 0 var lastBasalSequence = 0
var lastBasalPatchId = 0L var lastBasalPatchId = 0L
var lastBasalStartTime = 0L var lastBasalStartTime = 0L
@ -270,6 +287,17 @@ class MedtrumPump @Inject constructor(
handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now()) handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now())
} }
fun handleBolusStatusUpdate(bolusType: Int, bolusCompleted: Boolean, amountDelivered: Double) {
aapsLogger.debug(LTag.PUMP, "handleBolusStatusUpdate: bolusType: $bolusType bolusCompleted: $bolusCompleted amountDelivered: $amountDelivered")
bolusProgressLastTimeStamp = dateUtil.now()
if (bolusCompleted) {
bolusDone = true
bolusingTreatment?.insulin = amountDelivered
} else {
bolusingTreatment?.insulin = amountDelivered
}
}
fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) { fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) {
aapsLogger.debug( aapsLogger.debug(
LTag.PUMP, LTag.PUMP,

View file

@ -0,0 +1,12 @@
package info.nightscout.pump.medtrum.comm.enums
enum class BolusType {
NONE,
NORMAL,
EXTEND,
COMBINATION;
fun getValue(): Int {
return ordinal
}
}

View file

@ -41,14 +41,6 @@ class AuthorizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
} }
override fun handleResponse(data: ByteArray): Boolean { override fun handleResponse(data: ByteArray): Boolean {
if (data.size > 3) {
val incomingOpCode: Byte = data.copyOfRange(RESP_OPCODE_START, RESP_OPCODE_END).first()
if (incomingOpCode == CommandType.SUBSCRIBE.code) {
// TODO: Test and see if this can be removed
aapsLogger.error(LTag.PUMPCOMM, "handleResponse: Got subscribe response instead of authorize response, handling subscribe packet")
return SubscribePacket(injector).handleResponse(data)
}
}
val success = super.handleResponse(data) val success = super.handleResponse(data)
if (success) { if (success) {
deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt() deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt()

View file

@ -1,11 +1,13 @@
package info.nightscout.pump.medtrum.comm.packets package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
import info.nightscout.interfaces.pump.PumpSync import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD
import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.BolusType
import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.extension.toLong
@ -20,6 +22,7 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
@Inject lateinit var medtrumPump: MedtrumPump @Inject lateinit var medtrumPump: MedtrumPump
@Inject lateinit var pumpSync: PumpSync @Inject lateinit var pumpSync: PumpSync
@Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage @Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage
@Inject lateinit var detailedBolusInfoStorage: DetailedBolusInfoStorage
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
companion object { companion object {
@ -85,6 +88,51 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
when (recordType) { when (recordType) {
BOLUS_RECORD, BOLUS_RECORD_ALT -> { BOLUS_RECORD, BOLUS_RECORD_ALT -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD") aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD")
val typeAndWizard = data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 1).toInt()
val bolusCause = data.copyOfRange(RESP_RECORD_DATA_START + 1, RESP_RECORD_DATA_START + 2).toInt()
val unknown = data.copyOfRange(RESP_RECORD_DATA_START + 2, RESP_RECORD_DATA_START + 4).toInt()
val bolusStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
val bolusNormalAmount = data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 10).toInt() * 0.05
val bolusNormalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
val bolusExtendedAmount = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
val bolusExtendedDuration = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toInt()
val bolusExtendedDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 16, RESP_RECORD_DATA_START + 18).toInt() * 0.05
val bolusCarb = data.copyOfRange(RESP_RECORD_DATA_START + 18, RESP_RECORD_DATA_START + 20).toInt()
val bolusGlucose = data.copyOfRange(RESP_RECORD_DATA_START + 20, RESP_RECORD_DATA_START + 22).toInt()
val bolusIOB = data.copyOfRange(RESP_RECORD_DATA_START + 22, RESP_RECORD_DATA_START + 24).toInt()
val unkown1 = data.copyOfRange(RESP_RECORD_DATA_START + 24, RESP_RECORD_DATA_START + 26).toInt()
val unkown2 = data.copyOfRange(RESP_RECORD_DATA_START + 26, RESP_RECORD_DATA_START + 28).toInt()
val bolusType = enumValues<BolusType>()[typeAndWizard and 0x0F]
val bolusWizard = (typeAndWizard and 0xF0) != 0
aapsLogger.debug(
LTag.PUMPCOMM,
"GetRecordPacket HandleResponse: BOLUS_RECORD: typeAndWizard: $typeAndWizard, bolusCause: $bolusCause, unknown: $unknown, bolusStartTime: $bolusStartTime, " + "bolusNormalAmount: $bolusNormalAmount, bolusNormalDelivered: $bolusNormalDelivered, bolusExtendedAmount: $bolusExtendedAmount, bolusExtendedDuration: $bolusExtendedDuration, " + "bolusExtendedDelivered: $bolusExtendedDelivered, bolusCarb: $bolusCarb, bolusGlucose: $bolusGlucose, bolusIOB: $bolusIOB, unkown1: $unkown1, unkown2: $unkown2, " + "bolusType: $bolusType, bolusWizard: $bolusWizard"
)
if (bolusType == BolusType.NORMAL) {
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
val newRecord = pumpSync.syncBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
type = detailedBolusInfo?.bolusType,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType,
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
)
if (!newRecord && detailedBolusInfo != null) {
// detailedInfo can be from another similar record. Reinsert
detailedBolusInfoStorage.add(detailedBolusInfo)
}
} else {
aapsLogger.error(
LTag.PUMPCOMM,
"from record: EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) " + "Bolus type: $bolusType not supported"
)
}
} }
BASAL_RECORD, BASAL_RECORD_ALT -> { BASAL_RECORD, BASAL_RECORD_ALT -> {

View file

@ -98,13 +98,10 @@ class NotificationPacket(val injector: HasAndroidInjector) {
aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received")
var bolusData = data.copyOfRange(offset, offset + 1).toInt() var bolusData = data.copyOfRange(offset, offset + 1).toInt()
var bolusType = bolusData and 0x7F var bolusType = bolusData and 0x7F
var bolusCompleted = (bolusData shr 7) and 0x01 // TODO: Check for other flags here :) val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0 // TODO: Check for other flags here :)
var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05
// TODO Sync bolus flow:
// If bolus is known add status
// If bolus is not known start read bolus
// When bolus is completed, remove bolus from medtrumPump
aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered") aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered")
medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered)
offset += 3 offset += 3
} }

View file

@ -90,8 +90,8 @@ class BLEComm @Inject internal constructor(
private val mBluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter private val mBluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
private var mBluetoothGatt: BluetoothGatt? = null private var mBluetoothGatt: BluetoothGatt? = null
var isConnected = false // TODO: These may be removed have no function private var isConnected = false // Only to track internal ble state
var isConnecting = false// TODO: These may be removed have no function private var isConnecting = false // Only to track internal ble state
private var uartWrite: BluetoothGattCharacteristic? = null private var uartWrite: BluetoothGattCharacteristic? = null
private var uartRead: BluetoothGattCharacteristic? = null private var uartRead: BluetoothGattCharacteristic? = null
@ -126,14 +126,12 @@ class BLEComm @Inject internal constructor(
isConnected = false isConnected = false
// TODO: Maybe replace this by (or add) a isScanning parameter?
isConnecting = true isConnecting = true
// Find our Medtrum Device! // Find our Medtrum Device!
filters.add( filters.add(
ScanFilter.Builder().setDeviceName("MT").build() ScanFilter.Builder().setDeviceName("MT").build()
) )
// TODO Check if we need to add MAC for reconnects? Not sure if otherwise we can find the device
mBluetoothAdapter?.bluetoothLeScanner?.startScan(filters, settings, mScanCallback) mBluetoothAdapter?.bluetoothLeScanner?.startScan(filters, settings, mScanCallback)
return true return true
} }
@ -159,6 +157,9 @@ class BLEComm @Inject internal constructor(
return false return false
} }
// TODO: THIS IS A WORKAROUND TEST
mWritePackets = WriteCommandPackets()
if (mDevice != null && mDeviceSN == deviceSN) { if (mDevice != null && mDeviceSN == deviceSN) {
// Skip scanning and directly connect to gatt // Skip scanning and directly connect to gatt
aapsLogger.debug(LTag.PUMPBTCOMM, "Skipping scan and directly connecting to gatt") aapsLogger.debug(LTag.PUMPBTCOMM, "Skipping scan and directly connecting to gatt")
@ -178,7 +179,7 @@ class BLEComm @Inject internal constructor(
/** Connect flow: 2. When device is found this is called by onScanResult() */ /** Connect flow: 2. When device is found this is called by onScanResult() */
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Synchronized @Synchronized
fun connectGatt(device: BluetoothDevice) { private fun connectGatt(device: BluetoothDevice) {
mBluetoothGatt = mBluetoothGatt =
device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE) device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE)
} }
@ -196,12 +197,14 @@ class BLEComm @Inject internal constructor(
if (isConnecting) { if (isConnecting) {
stopScan() stopScan()
} }
mBluetoothGatt?.disconnect() if (isConnected) {
} mBluetoothGatt?.disconnect()
} else {
@Synchronized close()
fun stopConnecting() { isConnected = false
isConnecting = false isConnecting = false
mCallback?.onBLEDisconnected()
}
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@ -347,8 +350,6 @@ class BLEComm @Inject internal constructor(
aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!") aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!")
/** Connect flow: 6. Connected */ /** Connect flow: 6. Connected */
mCallback?.onBLEConnected() mCallback?.onBLEConnected()
isConnected = true
isConnecting = false
} }
} }
} }
@ -384,9 +385,9 @@ class BLEComm @Inject internal constructor(
private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) { private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) {
aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status) aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status)
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
handler.postDelayed({ isConnected = true
mBluetoothGatt?.discoverServices() isConnecting = false
}, WRITE_DELAY_MILLIS) mBluetoothGatt?.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
close() close()
isConnected = false isConnected = false

View file

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.os.SystemClock
import dagger.android.DaggerService import dagger.android.DaggerService
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.fabric.FabricPrivacy
@ -14,6 +15,7 @@ import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.pump.PumpEnactResult import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.pump.PumpSync import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.queue.Callback
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
@ -42,6 +44,7 @@ import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.round
class MedtrumService : DaggerService(), BLECommCallback { class MedtrumService : DaggerService(), BLECommCallback {
@ -130,12 +133,10 @@ class MedtrumService : DaggerService(), BLECommCallback {
} }
fun stopConnecting() { fun stopConnecting() {
// TODO proper way for this might need send commands etc bleComm.disconnect("stopConnecting")
bleComm.stopConnecting()
} }
fun disconnect(from: String) { fun disconnect(from: String) {
// TODO proper way for this might need send commands etc
bleComm.disconnect(from) bleComm.disconnect(from)
} }
@ -155,14 +156,58 @@ class MedtrumService : DaggerService(), BLECommCallback {
} }
} }
fun bolus(insulin: Double, carbs: Int, carbTime: Long, t: EventOverviewBolusProgress.Treatment): Boolean { fun setBolus(insulin: Double, t: EventOverviewBolusProgress.Treatment): Boolean {
if (!isConnected) return false if (!isConnected) return false
// TODO val result = sendPacketAndGetResponse(SetBolusPacket(injector, insulin))
return false
medtrumPump.bolusDone = false
medtrumPump.bolusingTreatment = t
medtrumPump.bolusAmountToBeDelivered = insulin
medtrumPump.bolusStopped = false
medtrumPump.bolusStopForced = false
medtrumPump.bolusProgressLastTimeStamp = dateUtil.now()
val bolusStart = System.currentTimeMillis()
val bolusingEvent = EventOverviewBolusProgress
while (medtrumPump.bolusStopped == false && result == true && medtrumPump.bolusDone == false) {
SystemClock.sleep(100)
if (System.currentTimeMillis() - medtrumPump.bolusProgressLastTimeStamp > T.secs(15).msecs()) {
medtrumPump.bolusStopped = true
medtrumPump.bolusStopForced = true
aapsLogger.debug(LTag.PUMPCOMM, "Communication stopped")
bleComm.disconnect("Communication stopped")
} else {
bolusingEvent.t = medtrumPump.bolusingTreatment
bolusingEvent.status = rh.gs(info.nightscout.pump.common.R.string.bolus_delivered_so_far, medtrumPump.bolusingTreatment?.insulin, medtrumPump.bolusAmountToBeDelivered)
bolusingEvent.percent = round((medtrumPump.bolusingTreatment?.insulin?.div(medtrumPump.bolusAmountToBeDelivered) ?: 0.0) * 100).toInt() - 1
rxBus.send(bolusingEvent)
}
}
bolusingEvent.t = medtrumPump.bolusingTreatment
bolusingEvent.percent = 99
medtrumPump.bolusingTreatment = null
val bolusDurationInMSec = (insulin * 60 * 1000)
val expectedEnd = bolusStart + bolusDurationInMSec + 2000
while (System.currentTimeMillis() < expectedEnd) {
SystemClock.sleep(1000)
}
// Do not call update status directly, reconnection may be needed
commandQueue.readStatus(rh.gs(info.nightscout.pump.medtrum.R.string.gettingbolusstatus), object : Callback() {
override fun run() {
rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.medtrum.R.string.gettingbolusstatus)))
bolusingEvent.percent = 100
}
})
return result
} }
fun bolusStop() { fun stopBolus() {
// TODO var result = sendPacketAndGetResponse(CancelBolusPacket(injector))
aapsLogger.debug(LTag.PUMPCOMM, "bolusStop: result: $result")
} }
fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean { fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean {
@ -190,12 +235,15 @@ class MedtrumService : DaggerService(), BLECommCallback {
} }
fun updateBasalsInPump(profile: Profile): Boolean { fun updateBasalsInPump(profile: Profile): Boolean {
var result = false var result = true
val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
result = packet?.let { sendPacketAndGetResponse(it) } == true
// TODO: We might want to get rid of this and cancel the TBR before we set the basal profile
// Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record) // Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record)
// Cancel any TBR in progress
if (medtrumPump.tempBasalInProgress) {
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
}
val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
if (result) result = packet?.let { sendPacketAndGetResponse(it) } == true
// Get history records, this will update the pump state and add changes in TBR to AAPS history // Get history records, this will update the pump state and add changes in TBR to AAPS history
if (result) result = syncRecords() if (result) result = syncRecords()
@ -211,7 +259,6 @@ class MedtrumService : DaggerService(), BLECommCallback {
private fun syncRecords(): Boolean { private fun syncRecords(): Boolean {
aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}") aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}")
var result = false var result = false
// TODO: Check if we need to sync older records as well
// Note: medtrum app fetches all records when they sync? // Note: medtrum app fetches all records when they sync?
for (sequence in medtrumPump.syncedSequenceNumber..medtrumPump.currentSequenceNumber) { for (sequence in medtrumPump.syncedSequenceNumber..medtrumPump.currentSequenceNumber) {
result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence)) result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence))

View file

@ -10,6 +10,7 @@ import info.nightscout.pump.medtrum.ui.event.UIEvent
import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel
import info.nightscout.interfaces.profile.ProfileFunction import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.ConnectionState import info.nightscout.pump.medtrum.code.ConnectionState
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
@ -17,6 +18,7 @@ import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPumpStatusChanged import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper
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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -27,6 +29,7 @@ import javax.inject.Inject
class MedtrumOverviewViewModel @Inject constructor( class MedtrumOverviewViewModel @Inject constructor(
private val aapsLogger: AAPSLogger, private val aapsLogger: AAPSLogger,
private val rh: ResourceHelper,
private val rxBus: RxBus, private val rxBus: RxBus,
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy, private val fabricPrivacy: FabricPrivacy,
@ -48,6 +51,10 @@ class MedtrumOverviewViewModel @Inject constructor(
val isPatchActivated: LiveData<Boolean> val isPatchActivated: LiveData<Boolean>
get() = _isPatchActivated get() = _isPatchActivated
private val _runningBasalRate = SingleLiveEvent<String>()
val runningBasalRate: LiveData<String>
get() = _runningBasalRate
init { init {
scope.launch { scope.launch {
medtrumPump.connectionStateFlow.collect { state -> medtrumPump.connectionStateFlow.collect { state ->
@ -77,6 +84,12 @@ class MedtrumOverviewViewModel @Inject constructor(
} }
} }
} }
scope.launch {
medtrumPump.lastBasalRateFlow.collect { rate ->
aapsLogger.debug(LTag.PUMP, "MedtrumViewModel runningBasalRateFlow: $rate")
_runningBasalRate.postValue(String.format(rh.gs(R.string.current_basal_rate), rate))
}
}
} }
override fun onCleared() { override fun onCleared() {

View file

@ -91,6 +91,54 @@
</LinearLayout> </LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="5dp"
android:background="@color/list_delimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="3dp"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/current_basal_label"
android:textSize="16sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:text="@string/colon"
android:textSize="16sp" />
<TextView
android:id="@+id/current_basal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@{viewmodel.runningBasalRate}"
android:textColor="@android:color/white"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -12,12 +12,14 @@
<string name="key_synced_sequence_number" translatable="false">synced_sequence_number</string> <string name="key_synced_sequence_number" translatable="false">synced_sequence_number</string>
<string name="medtrum">Medtrum</string> <string name="medtrum">Medtrum</string>
<string name="medtrum_pump_shortname">MEDTRUM</string> <string name="medtrum_pump_shortname">MT</string>
<string name="medtrum_pump_description">Medtrum Nano</string> <string name="medtrum_pump_description">Medtrum Nano</string>
<string name="medtrumpump_settings">Medtrum pump settings</string> <string name="medtrumpump_settings">Medtrum pump settings</string>
<!-- overview fragment --> <!-- overview fragment -->
<string name="medtrum_ble_status">BLE Status</string> <string name="medtrum_ble_status">BLE Status</string>
<string name="current_basal_label">Active basal</string>
<string name="current_basal_rate"> %.2f U/h</string>
<string name="string_new_patch">Start new patch</string> <string name="string_new_patch">Start new patch</string>
<string name="string_stop_patch">Stop patch</string> <string name="string_stop_patch">Stop patch</string>
@ -40,5 +42,8 @@
<string name="snInput_title">SN</string> <string name="snInput_title">SN</string>
<string name="snInput_summary">Serial number pump base</string> <string name="snInput_summary">Serial number pump base</string>
<string name="waitingforestimatedbolusend">Waiting for bolus end. Remaining %1$d sec.</string>
<string name="gettingbolusstatus">Getting bolus status</string>
</resources> </resources>