Automatic timeZone and DST handling

This commit is contained in:
jbr7rr 2023-06-25 21:52:01 +02:00
parent 201fb5768a
commit 19b9ca1da4
15 changed files with 189 additions and 14 deletions

View file

@ -136,6 +136,7 @@ open class Notification {
const val BLUETOOTH_NOT_ENABLED = 82 const val BLUETOOTH_NOT_ENABLED = 82
const val PATCH_NOT_ACTIVE = 83 const val PATCH_NOT_ACTIVE = 83
const val PUMP_SETTINGS_FAILED = 84 const val PUMP_SETTINGS_FAILED = 84
const val PUMP_TIMEZONE_UPDATE_FAILED = 85
const val USER_MESSAGE = 1000 const val USER_MESSAGE = 1000

View file

@ -9,4 +9,5 @@ interface Medtrum {
fun setUserOptions(): PumpEnactResult // set user settings fun setUserOptions(): PumpEnactResult // set user settings
fun clearAlarms(): PumpEnactResult // clear alarms fun clearAlarms(): PumpEnactResult // clear alarms
fun deactivate(): PumpEnactResult // deactivate patch fun deactivate(): PumpEnactResult // deactivate patch
fun updateTime(): PumpEnactResult // update time
} }

View file

@ -30,6 +30,7 @@ abstract class Command(
STOP_PUMP, STOP_PUMP,
CLEAR_ALARMS, // so far only Medtrum specific CLEAR_ALARMS, // so far only Medtrum specific
DEACTIVATE, // so far only Medtrum specific DEACTIVATE, // so far only Medtrum specific
UPDATE_TIME, // so far only Medtrum specific
INSIGHT_SET_TBR_OVER_ALARM, // insight only INSIGHT_SET_TBR_OVER_ALARM, // insight only
CUSTOM_COMMAND CUSTOM_COMMAND
} }

View file

@ -33,6 +33,7 @@ interface CommandQueue {
fun loadEvents(callback: Callback?): Boolean fun loadEvents(callback: Callback?): Boolean
fun clearAlarms(callback: Callback?): Boolean fun clearAlarms(callback: Callback?): Boolean
fun deactivate(callback: Callback?): Boolean fun deactivate(callback: Callback?): Boolean
fun updateTime(callback: Callback?): Boolean
fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean
fun isCustomCommandRunning(customCommandType: Class<out CustomCommand>): Boolean fun isCustomCommandRunning(customCommandType: Class<out CustomCommand>): Boolean
fun isCustomCommandInQueue(customCommandType: Class<out CustomCommand>): Boolean fun isCustomCommandInQueue(customCommandType: Class<out CustomCommand>): Boolean

View file

@ -395,6 +395,7 @@
<string name="load_events">LOAD EVENTS</string> <string name="load_events">LOAD EVENTS</string>
<string name="clear_alarms">CLEAR_ALARMS</string> <string name="clear_alarms">CLEAR_ALARMS</string>
<string name="deactivate">DEACTIVATE</string> <string name="deactivate">DEACTIVATE</string>
<string name="update_time">UPDATE TIME</string>
<string name="load_history">LOAD HISTORY %1$d</string> <string name="load_history">LOAD HISTORY %1$d</string>
<string name="load_tdds">LOAD TDDs</string> <string name="load_tdds">LOAD TDDs</string>
<string name="set_profile">SET PROFILE</string> <string name="set_profile">SET PROFILE</string>

View file

@ -22,6 +22,7 @@ import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNot
import info.nightscout.implementation.queue.commands.CommandLoadEvents import info.nightscout.implementation.queue.commands.CommandLoadEvents
import info.nightscout.implementation.queue.commands.CommandLoadHistory import info.nightscout.implementation.queue.commands.CommandLoadHistory
import info.nightscout.implementation.queue.commands.CommandLoadTDDs import info.nightscout.implementation.queue.commands.CommandLoadTDDs
import info.nightscout.implementation.queue.commands.CommandUpdateTime
@Module @Module
@Suppress("unused") @Suppress("unused")
@ -36,6 +37,7 @@ abstract class CommandQueueModule {
@ContributesAndroidInjector abstract fun commandLoadEventsInjector(): CommandLoadEvents @ContributesAndroidInjector abstract fun commandLoadEventsInjector(): CommandLoadEvents
@ContributesAndroidInjector abstract fun commandClearAlarmsInjector(): CommandClearAlarms @ContributesAndroidInjector abstract fun commandClearAlarmsInjector(): CommandClearAlarms
@ContributesAndroidInjector abstract fun commandDeactivateInjector(): CommandDeactivate @ContributesAndroidInjector abstract fun commandDeactivateInjector(): CommandDeactivate
@ContributesAndroidInjector abstract fun commandUpdateTimeInjector(): CommandUpdateTime
@ContributesAndroidInjector abstract fun commandLoadHistoryInjector(): CommandLoadHistory @ContributesAndroidInjector abstract fun commandLoadHistoryInjector(): CommandLoadHistory
@ContributesAndroidInjector abstract fun commandLoadTDDsInjector(): CommandLoadTDDs @ContributesAndroidInjector abstract fun commandLoadTDDsInjector(): CommandLoadTDDs
@ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus @ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus

View file

@ -38,6 +38,7 @@ import info.nightscout.implementation.queue.commands.CommandStartPump
import info.nightscout.implementation.queue.commands.CommandStopPump import info.nightscout.implementation.queue.commands.CommandStopPump
import info.nightscout.implementation.queue.commands.CommandTempBasalAbsolute import info.nightscout.implementation.queue.commands.CommandTempBasalAbsolute
import info.nightscout.implementation.queue.commands.CommandTempBasalPercent import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
import info.nightscout.implementation.queue.commands.CommandUpdateTime
import info.nightscout.interfaces.AndroidPermission import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
@ -564,6 +565,19 @@ class CommandQueueImplementation @Inject constructor(
return true return true
} }
override fun updateTime(callback: Callback?): Boolean {
if (isRunning(CommandType.UPDATE_TIME)) {
callback?.result(executingNowError())?.run()
return false
}
// remove all unfinished
removeAll(CommandType.UPDATE_TIME)
// add new command to queue
add(CommandUpdateTime(injector, callback))
notifyAboutNewCommand()
return true
}
override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean { override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean {
if (isCustomCommandInQueue(customCommand.javaClass)) { if (isCustomCommandInQueue(customCommand.javaClass)) {
callback?.result(executingNowError())?.run() callback?.result(executingNowError())?.run()

View file

@ -0,0 +1,39 @@
package info.nightscout.implementation.queue.commands
import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.pump.Dana
import info.nightscout.interfaces.pump.Diaconn
import info.nightscout.interfaces.pump.Medtrum
import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.queue.Command
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class CommandUpdateTime(
injector: HasAndroidInjector,
callback: Callback?
) : Command(injector, CommandType.UPDATE_TIME, callback) {
@Inject lateinit var activePlugin: ActivePlugin
override fun execute() {
val pump = activePlugin.activePump
if (pump is Medtrum) {
val medtrumPump = pump as Medtrum
val r = medtrumPump.updateTime()
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
callback?.result(r)?.run()
}
}
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.update_time)
override fun log(): String = "UPDATE TIME"
override fun cancel() {
aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
}
}

View file

@ -246,6 +246,10 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
// add deactivate // add deactivate
commandQueue.deactivate(null) commandQueue.deactivate(null)
Assertions.assertEquals(6, commandQueue.size()) Assertions.assertEquals(6, commandQueue.size())
// add updateTime
commandQueue.updateTime(null)
Assertions.assertEquals(7, commandQueue.size())
commandQueue.clear() commandQueue.clear()
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null) commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
@ -395,6 +399,22 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
Assertions.assertEquals(1, commandQueue.size()) Assertions.assertEquals(1, commandQueue.size())
} }
@Test
fun isUpdateTimeCommandInQueue() {
// given
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.updateTime(null)
// then
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.UPDATE_TIME))
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.updateTime(null)
Assertions.assertEquals(1, commandQueue.size())
}
@Test @Test
fun isLoadTDDsCommandInQueue() { fun isLoadTDDsCommandInQueue() {
// given // given

View file

@ -29,6 +29,7 @@ import info.nightscout.interfaces.pump.actions.CustomActionType
import info.nightscout.interfaces.pump.defs.ManufacturerType import info.nightscout.interfaces.pump.defs.ManufacturerType
import info.nightscout.interfaces.pump.defs.PumpDescription import info.nightscout.interfaces.pump.defs.PumpDescription
import info.nightscout.interfaces.pump.defs.PumpType import info.nightscout.interfaces.pump.defs.PumpType
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.queue.CommandQueue import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.interfaces.queue.CustomCommand import info.nightscout.interfaces.queue.CustomCommand
import info.nightscout.interfaces.ui.UiInteraction import info.nightscout.interfaces.ui.UiInteraction
@ -394,10 +395,6 @@ import kotlin.math.round
return PumpEnactResult(injector) // Note: Can implement this if we implement history fully (no priority) return PumpEnactResult(injector) // Note: Can implement this if we implement history fully (no priority)
} }
override fun canHandleDST(): Boolean {
return false
}
override fun getCustomActions(): List<CustomAction>? { override fun getCustomActions(): List<CustomAction>? {
return null return null
} }
@ -409,7 +406,20 @@ import kotlin.math.round
return null return null
} }
override fun canHandleDST(): Boolean {
return true
}
override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) { override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
medtrumPump.needTimeUpdate = true
// Update status to sync time with pump
if (isInitialized()) {
commandQueue.updateTime(object : Callback() {
override fun run() {
medtrumService?.timeUpdateNotification(this.result.success)
}
})
}
} }
// Medtrum interface // Medtrum interface
@ -452,4 +462,14 @@ import kotlin.math.round
val connectionOK = medtrumService?.deactivatePatch() ?: false val connectionOK = medtrumService?.deactivatePatch() ?: false
return PumpEnactResult(injector).success(connectionOK) return PumpEnactResult(injector).success(connectionOK)
} }
override fun updateTime(): PumpEnactResult {
if (!isInitialized()) {
val result = PumpEnactResult(injector).success(false)
result.comment = "pump not initialized"
return result
}
val connectionOK = medtrumService?.updateTime() ?: false
return PumpEnactResult(injector).success(connectionOK)
}
} }

View file

@ -177,7 +177,7 @@ class MedtrumPump @Inject constructor(
_deviceType = value _deviceType = value
sp.putInt(R.string.key_device_type, value) sp.putInt(R.string.key_device_type, value)
} }
private var _swVersion: String = "" // As reported by pump private var _swVersion: String = "" // As reported by pump
var swVersion: String var swVersion: String
get() = _swVersion get() = _swVersion
@ -185,7 +185,7 @@ class MedtrumPump @Inject constructor(
_swVersion = value _swVersion = value
sp.putString(R.string.key_sw_version, value) sp.putString(R.string.key_sw_version, value)
} }
private var _patchStartTime = 0L // Time in ms! private var _patchStartTime = 0L // Time in ms!
var patchStartTime: Long var patchStartTime: Long
get() = _patchStartTime get() = _patchStartTime
@ -194,10 +194,19 @@ class MedtrumPump @Inject constructor(
sp.putLong(R.string.key_patch_start_time, value) sp.putLong(R.string.key_patch_start_time, value)
} }
private var _pumpTimeZoneOffset = 0 // As reported by pump
var pumpTimeZoneOffset: Int
get() = _pumpTimeZoneOffset
set(value) {
_pumpTimeZoneOffset = value
sp.putInt(R.string.key_pump_time_zone_offset, value)
}
private var _pumpSN = 0L private var _pumpSN = 0L
val pumpSN: Long val pumpSN: Long
get() = _pumpSN get() = _pumpSN
var needTimeUpdate = false
var lastTimeReceivedFromPump = 0L // Time in ms! // TODO: Consider removing as is not used? var lastTimeReceivedFromPump = 0L // Time in ms! // TODO: Consider removing as is not used?
var suspendTime = 0L // Time in ms! var suspendTime = 0L // Time in ms!
var patchAge = 0L // Time in seconds?! // TODO: Not used var patchAge = 0L // Time in seconds?! // TODO: Not used
@ -218,7 +227,7 @@ class MedtrumPump @Inject constructor(
private var _lastBasalPatchId = 0L private var _lastBasalPatchId = 0L
val lastBasalPatchId: Long val lastBasalPatchId: Long
get() = _lastBasalPatchId get() = _lastBasalPatchId
private var _lastBasalStartTime = 0L private var _lastBasalStartTime = 0L
val lastBasalStartTime: Long val lastBasalStartTime: Long
get() = _lastBasalStartTime get() = _lastBasalStartTime
@ -255,6 +264,7 @@ class MedtrumPump @Inject constructor(
_deviceType = sp.getInt(R.string.key_device_type, 0) _deviceType = sp.getInt(R.string.key_device_type, 0)
_swVersion = sp.getString(R.string.key_sw_version, "") _swVersion = sp.getString(R.string.key_sw_version, "")
_patchStartTime = sp.getLong(R.string.key_patch_start_time, 0L) _patchStartTime = sp.getLong(R.string.key_patch_start_time, 0L)
_pumpTimeZoneOffset = sp.getInt(R.string.key_pump_time_zone_offset, 0)
loadActiveAlarms() loadActiveAlarms()
@ -266,10 +276,10 @@ class MedtrumPump @Inject constructor(
} }
} }
fun pumpType(): PumpType = fun pumpType(): PumpType =
when(deviceType) { when (deviceType) {
80, 88 -> PumpType.MEDTRUM_NANO 80, 88 -> PumpType.MEDTRUM_NANO
else -> PumpType.MEDTRUM_UNTESTED else -> PumpType.MEDTRUM_UNTESTED
} }
fun loadUserSettingsFromSP() { fun loadUserSettingsFromSP() {
@ -464,7 +474,7 @@ class MedtrumPump @Inject constructor(
activeAlarms.add(alarm) activeAlarms.add(alarm)
saveActiveAlarms() saveActiveAlarms()
} }
fun removeAlarm(alarm: AlarmState) { fun removeAlarm(alarm: AlarmState) {
activeAlarms.remove(alarm) activeAlarms.remove(alarm)
saveActiveAlarms() saveActiveAlarms()
@ -504,7 +514,7 @@ class MedtrumPump @Inject constructor(
val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name } val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name }
sp.putString(R.string.key_active_alarms, alarmsStr) sp.putString(R.string.key_active_alarms, alarmsStr)
} }
private fun loadActiveAlarms() { private fun loadActiveAlarms() {
val alarmsStr = sp.getString(R.string.key_active_alarms, "") val alarmsStr = sp.getString(R.string.key_active_alarms, "")
if (alarmsStr.isNullOrEmpty()) { if (alarmsStr.isNullOrEmpty()) {

View file

@ -1,6 +1,7 @@
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.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME_ZONE import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME_ZONE
import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
@ -10,6 +11,9 @@ import javax.inject.Inject
class SetTimeZonePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) { class SetTimeZonePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var medtrumPump: MedtrumPump
var offsetMins: Int = 0
init { init {
opCode = SET_TIME_ZONE.code opCode = SET_TIME_ZONE.code
@ -17,8 +21,16 @@ class SetTimeZonePacket(injector: HasAndroidInjector) : MedtrumPacket(injector)
override fun getRequest(): ByteArray { override fun getRequest(): ByteArray {
val time = MedtrumTimeUtil().getCurrentTimePumpSeconds() val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
var offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now()) offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
if (offsetMins < 0) offsetMins += 65536 if (offsetMins < 0) offsetMins += 65536
return byteArrayOf(opCode) + offsetMins.toByteArray(2) + time.toByteArray(4) return byteArrayOf(opCode) + offsetMins.toByteArray(2) + time.toByteArray(4)
} }
override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data)
if (success) {
medtrumPump.pumpTimeZoneOffset = offsetMins
}
return success
}
} }

View file

@ -189,10 +189,44 @@ class MedtrumService : DaggerService(), BLECommCallback {
} }
fun readPumpStatus() { fun readPumpStatus() {
// Update pump events rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.medtrum.R.string.gettingpumpstatus)))
// Update time if needed
if (medtrumPump.needTimeUpdate || medtrumPump.pumpTimeZoneOffset != dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())) {
aapsLogger.warn(LTag.PUMPCOMM, "Pump time update from readPumpStatus")
timeUpdateNotification(updateTime(false))
}
// Update history
loadEvents() loadEvents()
} }
fun timeUpdateNotification(updateSuccess: Boolean) {
if (updateSuccess) {
aapsLogger.debug(LTag.PUMPCOMM, "Pump time updated")
medtrumPump.needTimeUpdate = false
uiInteraction.addNotification(
Notification.INSIGHT_DATE_TIME_UPDATED, // :---)
rh.gs(info.nightscout.core.ui.R.string.pump_time_updated),
Notification.INFO,
)
} else {
aapsLogger.error(LTag.PUMPCOMM, "Failed to update pump time")
uiInteraction.addNotification(
Notification.PUMP_TIMEZONE_UPDATE_FAILED,
rh.gs(R.string.pump_time_update_failed),
Notification.URGENT,
)
}
}
fun updateTime(needLoadHistory: Boolean = true): Boolean {
var result = sendPacketAndGetResponse(SetTimePacket(injector))
if (result) result = sendPacketAndGetResponse(SetTimeZonePacket(injector))
if (needLoadHistory) {
if (result) result = loadEvents()
}
return result
}
fun loadEvents(): Boolean { fun loadEvents(): Boolean {
rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.medtrum.R.string.gettingpumpstatus))) rxBus.send(EventPumpStatusChanged(rh.gs(info.nightscout.pump.medtrum.R.string.gettingpumpstatus)))
// Sync records (based on the info we have from the sync) // Sync records (based on the info we have from the sync)
@ -785,6 +819,8 @@ class MedtrumService : DaggerService(), BLECommCallback {
// Succes! // Succes!
responseHandled = true responseHandled = true
responseSuccess = true responseSuccess = true
medtrumPump.needTimeUpdate = false
timeUpdateNotification(true)
toState(SynchronizeState()) toState(SynchronizeState())
} else if (mPacket?.failed == true) { } else if (mPacket?.failed == true) {
// Failure // Failure

View file

@ -21,6 +21,7 @@
<string name="key_actual_basal_profile" translatable="false">actual_basal_profile</string> <string name="key_actual_basal_profile" translatable="false">actual_basal_profile</string>
<string name="key_current_sequence_number" translatable="false">current_sequence_number</string> <string name="key_current_sequence_number" translatable="false">current_sequence_number</string>
<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="key_pump_time_zone_offset" translatable="false">pump_time_zone_offset</string>
<string name="medtrum">Medtrum</string> <string name="medtrum">Medtrum</string>
<string name="medtrum_pump_shortname">MT</string> <string name="medtrum_pump_shortname">MT</string>
@ -74,6 +75,7 @@
<string name="alarm_base_fault">Base fault</string> <string name="alarm_base_fault">Base fault</string>
<string name="alarm_battery_out">Battery out</string> <string name="alarm_battery_out">Battery out</string>
<string name="alarm_no_calibration">No calibration</string> <string name="alarm_no_calibration">No calibration</string>
<string name="pump_time_update_failed">Failed to update pump timezone, snooze message and refresh manually.</string>
<!-- wizard--> <!-- wizard-->
<string name="string_start_complete">Complete</string> <string name="string_start_complete">Complete</string>

View file

@ -38,4 +38,19 @@ class SetTimeZonePacketTest : MedtrumTestBase() {
assertEquals(7, result.size) assertEquals(7, result.size)
assertEquals(expectedByteArray.contentToString(), result.contentToString()) assertEquals(expectedByteArray.contentToString(), result.contentToString())
} }
@Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
// Inputs
val response = byteArrayOf(7, 10, 3, 0, 0, 0, -38)
// Call
val packet = SetTimeZonePacket(packetInjector)
val result = packet.handleResponse(response)
// Expected values
val expectedOffsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
assertTrue(result)
assertEquals(expectedOffsetMins, medtrumPump.pumpTimeZoneOffset)
}
} }