diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index 143919db87..b9cd7b9d0a 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -48,6 +48,7 @@ import info.nightscout.androidaps.plugins.source.EversensePlugin import info.nightscout.androidaps.plugins.source.GlimpPlugin import info.nightscout.androidaps.plugins.source.PoctechPlugin import info.nightscout.androidaps.plugins.source.TomatoPlugin +import info.nightscout.androidaps.plugins.source.GlunovoPlugin import info.nightscout.androidaps.utils.SafeParse import info.nightscout.androidaps.utils.alertDialogs.OKDialog.show import info.nightscout.androidaps.utils.protection.PasswordCheck @@ -90,6 +91,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang @Inject lateinit var glimpPlugin: GlimpPlugin @Inject lateinit var poctechPlugin: PoctechPlugin @Inject lateinit var tomatoPlugin: TomatoPlugin + @Inject lateinit var glunovoPlugin: GlunovoPlugin @Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin @Inject lateinit var statusLinePlugin: StatusLinePlugin @Inject lateinit var tidepoolPlugin: TidepoolPlugin @@ -160,6 +162,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang addPreferencesFromResourceIfEnabled(eversensePlugin, rootKey) addPreferencesFromResourceIfEnabled(dexcomPlugin, rootKey) addPreferencesFromResourceIfEnabled(tomatoPlugin, rootKey) + addPreferencesFromResourceIfEnabled(glunovoPlugin, rootKey) addPreferencesFromResourceIfEnabled(poctechPlugin, rootKey) addPreferencesFromResourceIfEnabled(glimpPlugin, rootKey) addPreferencesFromResourceIfEnabled(loopPlugin, rootKey, config.APS) diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt index 2a14115056..6d58752f2b 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -353,6 +353,12 @@ abstract class PluginsModule { @AllConfigs @IntoMap @IntKey(470) + abstract fun bindGlunovoPlugin(plugin: GlunovoPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(475) abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase // @Binds diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt index 3e1bb68d85..cb78c7882e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt @@ -28,20 +28,22 @@ class StorageConstraintPlugin @Inject constructor( aapsLogger: AAPSLogger, rh: ResourceHelper, private val rxBus: RxBus -) : PluginBase(PluginDescription() - .mainType(PluginType.CONSTRAINTS) - .neverVisible(true) - .alwaysEnabled(true) - .showInList(false) - .pluginName(R.string.storage), +) : PluginBase( + PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .neverVisible(true) + .alwaysEnabled(true) + .showInList(false) + .pluginName(R.string.storage), aapsLogger, rh, injector ), Constraints { + @Suppress("ReplaceGetOrSet") override fun isClosedLoopAllowed(value: Constraint): Constraint { val diskFree = availableInternalMemorySize() - aapsLogger.debug(LTag.CONSTRAINTS, "Internal storage free (Mb):$diskFree") if (diskFree < Constants.MINIMUM_FREE_SPACE) { - value[aapsLogger, false, rh.gs(R.string.diskfull, Constants.MINIMUM_FREE_SPACE)] = this + aapsLogger.debug(LTag.CONSTRAINTS, "Internal storage free (Mb):$diskFree") + value.set(aapsLogger, false, rh.gs(R.string.diskfull, Constants.MINIMUM_FREE_SPACE), this) val notification = Notification(Notification.DISK_FULL, rh.gs(R.string.diskfull, Constants.MINIMUM_FREE_SPACE), Notification.NORMAL) rxBus.send(EventNewNotification(notification)) } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt index 77a09e402e..5a5bda55a3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt @@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.general.nsclient.services import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.content.pm.ResolveInfo import android.os.* import androidx.work.OneTimeWorkRequest import com.google.common.base.Charsets @@ -17,7 +16,6 @@ import info.nightscout.androidaps.events.EventConfigBuilderChange import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.DataSyncSelector -import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBus @@ -41,12 +39,12 @@ import info.nightscout.androidaps.plugins.general.overview.notifications.Notific import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin.NSClientSourceWorker import info.nightscout.androidaps.receivers.DataWorker -import info.nightscout.androidaps.services.Intents import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.JsonHelper.safeGetString import info.nightscout.androidaps.utils.JsonHelper.safeGetStringAllowNull import info.nightscout.androidaps.utils.T.Companion.mins +import info.nightscout.androidaps.utils.XDripBroadcast import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers @@ -80,6 +78,7 @@ class NSClientService : DaggerService() { @Inject lateinit var dataWorker: DataWorker @Inject lateinit var dataSyncSelector: DataSyncSelector @Inject lateinit var repository: AppRepository + @Inject lateinit var xDripBroadcast: XDripBroadcast companion object { @@ -476,15 +475,7 @@ class NSClientService : DaggerService() { OneTimeWorkRequest.Builder(LocalProfilePlugin.NSProfileWorker::class.java) .setInputData(dataWorker.storeInputData(profileStoreJson, null)) .build()) - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - val bundle = Bundle() - bundle.putString("profile", profileStoreJson.toString()) - bundle.putBoolean("delta", isDelta) - val intent = Intent(Intents.ACTION_NEW_PROFILE) - intent.putExtras(bundle) - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) - broadcast(intent) - } + xDripBroadcast.sendProfile(profileStoreJson) } } if (data.has("treatments")) { @@ -502,18 +493,7 @@ class NSClientService : DaggerService() { OneTimeWorkRequest.Builder(NSClientAddUpdateWorker::class.java) .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null)) .build()) - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - val splitted = splitArray(addedOrUpdatedTreatments) - for (part in splitted) { - val bundle = Bundle() - bundle.putString("treatments", part.toString()) - bundle.putBoolean("delta", isDelta) - val intent = Intent(Intents.ACTION_CHANGED_TREATMENT) - intent.putExtras(bundle) - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) - broadcast(intent) - } - } + xDripBroadcast.sendTreatments(addedOrUpdatedTreatments) } } if (data.has("devicestatus")) { @@ -550,18 +530,7 @@ class NSClientService : DaggerService() { dataWorker.enqueue(OneTimeWorkRequest.Builder(NSClientSourceWorker::class.java) .setInputData(dataWorker.storeInputData(sgvs, null)) .build()) - val splitted = splitArray(sgvs) - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - for (part in splitted) { - val bundle = Bundle() - bundle.putString("sgvs", part.toString()) - bundle.putBoolean("delta", isDelta) - val intent = Intent(Intents.ACTION_NEW_SGV) - intent.putExtras(bundle) - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) - broadcast(intent) - } - } + xDripBroadcast.sendSgvs(sgvs) } rxBus.send(EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData))) } catch (e: JSONException) { @@ -675,40 +644,6 @@ class NSClientService : DaggerService() { } } - private fun splitArray(array: JSONArray): List { - var ret: MutableList = ArrayList() - try { - val size = array.length() - var count = 0 - var newarr: JSONArray? = null - for (i in 0 until size) { - if (count == 0) { - if (newarr != null) ret.add(newarr) - newarr = JSONArray() - count = 20 - } - newarr?.put(array[i]) - --count - } - if (newarr != null && newarr.length() > 0) ret.add(newarr) - } catch (e: JSONException) { - aapsLogger.error("Unhandled exception", e) - ret = ArrayList() - ret.add(array) - } - return ret - } - - private fun broadcast(intent: Intent) { - val receivers: List = packageManager.queryBroadcastReceivers(intent, 0) - for (resolveInfo in receivers) - resolveInfo.activityInfo.packageName?.let { - intent.setPackage(it) - sendBroadcast(intent) - aapsLogger.debug(LTag.CORE, "Sending broadcast " + intent.action + " to: " + it) - } - } - init { if (handler == null) { val handlerThread = HandlerThread(NSClientService::class.java.simpleName + "Handler") diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index 2583996995..72e63f1faf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -42,7 +42,6 @@ import info.nightscout.androidaps.interfaces.ActivePlugin; import info.nightscout.androidaps.interfaces.Config; import info.nightscout.androidaps.interfaces.GlucoseUnit; import info.nightscout.androidaps.interfaces.IobCobCalculator; -import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.Profile; import info.nightscout.androidaps.interfaces.ProfileFunction; import info.nightscout.androidaps.logging.AAPSLogger; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt index fec67d9e5f..5efd0af7e8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt @@ -537,7 +537,11 @@ class IobCobCalculatorPlugin @Inject constructor( for (pos in extendedBoluses.indices) { val e = extendedBoluses[pos] if (e.timestamp > toTime) continue - if (e.end > now) e.duration = now - e.timestamp + if (e.end > now) { + val newDuration = now - e.timestamp + e.amount *= newDuration.toDouble() / e.duration + e.duration = newDuration + } val profile = profileFunction.getProfile(e.timestamp) ?: return total val calc = e.iobCalc(toTime, profile, activePlugin.activeInsulin) total.plus(calc) @@ -632,7 +636,11 @@ class IobCobCalculatorPlugin @Inject constructor( val e = extendedBoluses[pos] if (e.timestamp > toTime) continue val profile = profileFunction.getProfile(e.timestamp) ?: continue - if (e.end > now) e.duration = now - e.timestamp + if (e.end > now) { + val newDuration = now - e.timestamp + e.amount *= newDuration.toDouble() / e.duration + e.duration = newDuration + } val calc = e.iobCalc(toTime, profile, activePlugin.activeInsulin) totalExt.plus(calc) } @@ -667,7 +675,11 @@ class IobCobCalculatorPlugin @Inject constructor( val e = extendedBoluses[pos] if (e.timestamp > toTime) continue val profile = profileFunction.getProfile(e.timestamp) ?: continue - if (e.end > now) e.duration = now - e.timestamp + if (e.end > now) { + val newDuration = now - e.timestamp + e.amount *= newDuration.toDouble() / e.duration + e.duration = newDuration + } val calc = e.iobCalc(toTime, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget, activePlugin.activeInsulin) totalExt.plus(calc) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt index 48b3dbc80e..c942bf413d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt @@ -140,6 +140,7 @@ class BGSourceFragment : DaggerFragment() { R.string.nsclientbg -> Sources.NSClientSource R.string.poctech -> Sources.PocTech R.string.tomato -> Sources.Tomato + R.string.glunovo -> Sources.Glunovo R.string.xdrip -> Sources.Xdrip else -> Sources.Unknown } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt new file mode 100644 index 0000000000..906c9094ec --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt @@ -0,0 +1,163 @@ +package info.nightscout.androidaps.plugins.source + +import android.content.Context +import android.net.Uri +import android.os.Handler +import android.os.HandlerThread +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.entities.GlucoseValue +import info.nightscout.androidaps.database.entities.TherapyEvent +import info.nightscout.androidaps.database.transactions.CgmSourceTransaction +import info.nightscout.androidaps.interfaces.BgSource +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.XDripBroadcast +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GlunovoPlugin @Inject constructor( + injector: HasAndroidInjector, + resourceHelper: ResourceHelper, + aapsLogger: AAPSLogger, + private val sp: SP, + private val context: Context, + private val repository: AppRepository, + private val xDripBroadcast: XDripBroadcast, + private val dateUtil: DateUtil, + private val fabricPrivacy: FabricPrivacy +) : PluginBase( + PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + .pluginIcon(R.drawable.ic_glunovo) + .pluginName(R.string.glunovo) + .preferencesId(R.xml.pref_bgsource) + .shortName(R.string.glunovo) + .description(R.string.description_source_glunovo), + aapsLogger, resourceHelper, injector +), BgSource { + + private val loopHandler: Handler = Handler(HandlerThread(this::class.java.simpleName + "Handler").also { it.start() }.looper) + private lateinit var refreshLoop: Runnable + + private val contentUri: Uri = Uri.parse("content://$AUTHORITY/$TABLE_NAME") + + init { + refreshLoop = Runnable { + try { + handleNewData() + } catch (e: Exception) { + fabricPrivacy.logException(e) + aapsLogger.error("Error while processing data", e) + } + val lastReadTimestamp = sp.getLong(R.string.key_last_processed_glunovo_timestamp, 0L) + val differenceToNow = INTERVAL - (dateUtil.now() - lastReadTimestamp) % INTERVAL + T.secs(10).msecs() + loopHandler.postDelayed(refreshLoop, differenceToNow) + } + } + + private val disposable = CompositeDisposable() + + override fun onStart() { + super.onStart() + loopHandler.postDelayed(refreshLoop, T.secs(30).msecs()) // do not start immediately, app may be still starting + } + + override fun onStop() { + super.onStop() + loopHandler.removeCallbacks(refreshLoop) + disposable.clear() + } + + private fun handleNewData() { + if (!isEnabled()) return + + context.contentResolver.query(contentUri, null, null, null, null)?.let { cr -> + val glucoseValues = mutableListOf() + val calibrations = mutableListOf() + cr.moveToFirst() + + while (!cr.isAfterLast) { + val timestamp = cr.getLong(0) + val value = cr.getDouble(1) //value in mmol/l... + val curr = cr.getDouble(2) + + // bypass already processed + if (timestamp < sp.getLong(R.string.key_last_processed_glunovo_timestamp, 0L)) { + cr.moveToNext() + continue + } + + if (timestamp > dateUtil.now() || timestamp == 0L) { + aapsLogger.error(LTag.BGSOURCE, "Error in received data date/time $timestamp") + cr.moveToNext() + continue + } + + if (value < 2 || value > 25) { + aapsLogger.error(LTag.BGSOURCE, "Error in received data value (value out of bounds) $value") + cr.moveToNext() + continue + } + + if (curr != 0.0) + glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( + timestamp = timestamp, + value = value * Constants.MMOLL_TO_MGDL, + raw = 0.0, + noise = null, + trendArrow = GlucoseValue.TrendArrow.NONE, + sourceSensor = GlucoseValue.SourceSensor.GLUNOVO_NATIVE + ) + else + calibrations.add( + CgmSourceTransaction.Calibration( + timestamp = timestamp, + value = value, + glucoseUnit = TherapyEvent.GlucoseUnit.MMOL + ) + ) + sp.putLong(R.string.key_last_processed_glunovo_timestamp, timestamp) + cr.moveToNext() + } + cr.close() + + if (glucoseValues.isNotEmpty() || calibrations.isNotEmpty()) + repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, calibrations, null)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving values from Glunovo App", it) + } + .blockingGet() + .also { savedValues -> + savedValues.inserted.forEach { + xDripBroadcast.send(it) + aapsLogger.debug(LTag.DATABASE, "Inserted bg $it") + } + } + } + } + + override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = + glucoseValue.sourceSensor == GlucoseValue.SourceSensor.GLUNOVO_NATIVE && sp.getBoolean(R.string.key_dexcomg5_nsupload, false) + + companion object { + + @Suppress("SpellCheckingInspection") + const val AUTHORITY = "alexpr.co.uk.infinivocgm.cgm_db.CgmExternalProvider/" + const val TABLE_NAME = "CgmReading" + const val INTERVAL = 180000L // 3 min + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt index 1bdbdc7d64..c285b9d338 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt @@ -60,7 +60,6 @@ class QueueThread internal constructor( //write time sp.putLong(R.string.key_btwatchdog_lastbark, System.currentTimeMillis()) //toggle BT - pump.stopConnecting() pump.disconnect("watchdog") SystemClock.sleep(1000) val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() @@ -112,14 +111,18 @@ class QueueThread internal constructor( // Pickup 1st command and set performing variable if (queue.size() > 0) { queue.pickup() - if (queue.performing() != null) { - aapsLogger.debug(LTag.PUMPQUEUE, "performing " + queue.performing()?.status()) + val cont = queue.performing()?.let { + aapsLogger.debug(LTag.PUMPQUEUE, "performing " + it.status()) rxBus.send(EventQueueChanged()) - queue.performing()?.execute() + rxBus.send(EventPumpStatusChanged(it.status())) + it.execute() queue.resetPerforming() rxBus.send(EventQueueChanged()) lastCommandTime = System.currentTimeMillis() SystemClock.sleep(100) + true + } ?: false + if (cont) { continue } } diff --git a/app/src/main/java/info/nightscout/androidaps/services/Intents.kt b/app/src/main/java/info/nightscout/androidaps/services/Intents.kt index c52c68e4c6..bf3a8d3695 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/Intents.kt +++ b/app/src/main/java/info/nightscout/androidaps/services/Intents.kt @@ -7,12 +7,11 @@ interface Intents { // AAPS -> Xdrip const val ACTION_NEW_TREATMENT = "info.nightscout.client.NEW_TREATMENT" - const val ACTION_CHANGED_TREATMENT = "info.nightscout.client.CHANGED_TREATMENT" - const val ACTION_REMOVED_TREATMENT = "info.nightscout.client.REMOVED_TREATMENT" const val ACTION_NEW_PROFILE = "info.nightscout.client.NEW_PROFILE" const val ACTION_NEW_SGV = "info.nightscout.client.NEW_SGV" - const val ACTION_NEW_MBG = "info.nightscout.client.NEW_MBG" - const val ACTION_NEW_CAL = "info.nightscout.client.NEW_CAL" + + // AAPS -> xDrip 640G mode + const val XDRIP_PLUS_NS_EMULATOR = "com.eveningoutpost.dexdrip.NS_EMULATOR" // xDrip -> AAPS const val ACTION_NEW_BG_ESTIMATE = "com.eveningoutpost.dexdrip.BgEstimate" diff --git a/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt b/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt index d669ef8bc3..98cf2c90f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt @@ -7,6 +7,7 @@ import info.nightscout.androidaps.R import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.services.Intents import info.nightscout.androidaps.utils.sharedPreferences.SP import org.json.JSONArray import org.json.JSONException @@ -24,6 +25,7 @@ class XDripBroadcast @Inject constructor( private val sp: SP ) { + // sent in 640G mode fun send(glucoseValue: GlucoseValue) { if (sp.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) @@ -41,7 +43,7 @@ class XDripBroadcast @Inject constructor( bundle.putString("action", "add") bundle.putString("collection", "entries") bundle.putString("data", entriesBody.toString()) - val intent = Intent(XDRIP_PLUS_NS_EMULATOR) + val intent = Intent(Intents.XDRIP_PLUS_NS_EMULATOR) intent.putExtras(bundle).addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) context.sendBroadcast(intent) val receivers = context.packageManager.queryBroadcastReceivers(intent, 0) @@ -57,7 +59,75 @@ class XDripBroadcast @Inject constructor( } } - companion object { - const val XDRIP_PLUS_NS_EMULATOR = "com.eveningoutpost.dexdrip.NS_EMULATOR" + // sent in NSClient dbaccess mode + fun sendProfile(profileStoreJson: JSONObject) { + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) + broadcast( + Intent(Intents.ACTION_NEW_PROFILE).apply { + addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + putExtras(Bundle().apply { putString("profile", profileStoreJson.toString()) }) + } + ) + + } + + // sent in NSClient dbaccess mode + fun sendTreatments(addedOrUpdatedTreatments: JSONArray) { + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) + splitArray(addedOrUpdatedTreatments).forEach { part -> + broadcast( + Intent(Intents.ACTION_NEW_TREATMENT).apply { + addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + putExtras(Bundle().apply { putString("treatments", part.toString()) }) + } + ) + } + } + + // sent in NSClient dbaccess mode + fun sendSgvs(sgvs: JSONArray) { + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) + splitArray(sgvs).forEach { part -> + broadcast( + Intent(Intents.ACTION_NEW_SGV).apply { + addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + putExtras(Bundle().apply { putString("sgvs", part.toString()) }) + } + ) + } + } + + private fun splitArray(array: JSONArray): List { + var ret: MutableList = ArrayList() + try { + val size = array.length() + var count = 0 + var newarr: JSONArray? = null + for (i in 0 until size) { + if (count == 0) { + if (newarr != null) ret.add(newarr) + newarr = JSONArray() + count = 20 + } + newarr?.put(array[i]) + --count + } + if (newarr != null && newarr.length() > 0) ret.add(newarr) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + ret = ArrayList() + ret.add(array) + } + return ret + } + + private fun broadcast(intent: Intent) { + context.packageManager.queryBroadcastReceivers(intent, 0).forEach { resolveInfo -> + resolveInfo.activityInfo.packageName?.let { + intent.setPackage(it) + context.sendBroadcast(intent) + aapsLogger.debug(LTag.CORE, "Sending broadcast " + intent.action + " to: " + it) + } + } } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d24ca2e1d..b622714e15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -807,6 +807,8 @@ Time elapsed Poctech Receive BG values from Poctech app + Glunovo + Receive values from Glunovo app Receive BG values from Tomato app (MiaoMiao device) high_temptarget_raises_sensitivity low_temptarget_lowers_sensitivity @@ -1132,5 +1134,6 @@ BG data status Recalculated data used BG too close:\n%1$s\n%2$s + last_processed_glunovo_timestamp diff --git a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.kt b/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.kt index a20e509b19..6e275190e9 100644 --- a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.kt +++ b/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.kt @@ -46,4 +46,6 @@ class InputDuration( this.value = value / 60 return this } + + override fun toString(): String = "InputDuration: $value $unit" } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4a59385c49..9a9fc16d67 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { rxkotlin_version = '2.4.0' room_version = '2.3.0' lifecycle_version = '2.3.1' - dagger_version = '2.40' + dagger_version = '2.40.1' coroutinesVersion = '1.4.1' activityVersion = '1.3.1' fragmentktx_version = '1.3.6' diff --git a/core/src/main/java/info/nightscout/androidaps/extensions/ExtendedBolusExtension.kt b/core/src/main/java/info/nightscout/androidaps/extensions/ExtendedBolusExtension.kt index 339dd0b797..a09163585c 100644 --- a/core/src/main/java/info/nightscout/androidaps/extensions/ExtendedBolusExtension.kt +++ b/core/src/main/java/info/nightscout/androidaps/extensions/ExtendedBolusExtension.kt @@ -110,7 +110,7 @@ fun extendedBolusFromJson(jsonObject: JSONObject): ExtendedBolus? { val pumpSerial = JsonHelper.safeGetStringAllowNull(jsonObject, "pumpSerial", null) if (timestamp == 0L) return null - if (duration == 0L) return null + if (duration == 0L && durationInMilliseconds == 0L) return null if (amount == 0.0) return null return ExtendedBolus( @@ -135,7 +135,7 @@ fun ExtendedBolus.iobCalc(time: Long, profile: Profile, insulinInterface: Insuli val dia = profile.dia val diaAgo = time - dia * 60 * 60 * 1000 val aboutFiveMinIntervals = ceil(realDuration / 5.0).toInt() - val spacing = realDuration / aboutFiveMinIntervals + val spacing = realDuration / aboutFiveMinIntervals.toDouble() for (j in 0L until aboutFiveMinIntervals) { // find middle of the interval val calcDate = (timestamp + j * spacing * 60 * 1000 + 0.5 * spacing * 60 * 1000).toLong() diff --git a/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt b/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt index b80ca45c65..1e2ba381a7 100644 --- a/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt +++ b/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt @@ -134,14 +134,14 @@ fun TemporaryBasal.toStringShort(): String = fun TemporaryBasal.iobCalc(time: Long, profile: Profile, insulinInterface: Insulin): IobTotal { val result = IobTotal(time) - val realDuration: Int = getPassedDurationToTimeInMinutes(time) + val realDuration = getPassedDurationToTimeInMinutes(time) var netBasalAmount = 0.0 if (realDuration > 0) { var netBasalRate: Double val dia = profile.dia val diaAgo = time - dia * 60 * 60 * 1000 val aboutFiveMinIntervals = ceil(realDuration / 5.0).toInt() - val tempBolusSpacing = (realDuration / aboutFiveMinIntervals).toDouble() + val tempBolusSpacing = realDuration / aboutFiveMinIntervals.toDouble() for (j in 0L until aboutFiveMinIntervals) { // find middle of the interval val calcDate = (timestamp + j * tempBolusSpacing * 60 * 1000 + 0.5 * tempBolusSpacing * 60 * 1000).toLong() @@ -175,7 +175,7 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, insulinInterface: Insul fun TemporaryBasal.iobCalc(time: Long, profile: Profile, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean, insulinInterface: Insulin): IobTotal { val result = IobTotal(time) - val realDuration: Double = getPassedDurationToTimeInMinutes(time).toDouble() + val realDuration = getPassedDurationToTimeInMinutes(time) var netBasalAmount = 0.0 var sensitivityRatio = lastAutosensResult.ratio val normalTarget = 100.0 @@ -190,7 +190,7 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, lastAutosensResult: Aut val dia = profile.dia val diaAgo = time - dia * 60 * 60 * 1000 val aboutFiveMinIntervals = ceil(realDuration / 5.0).toInt() - val tempBolusSpacing = realDuration / aboutFiveMinIntervals + val tempBolusSpacing = realDuration / aboutFiveMinIntervals.toDouble() for (j in 0L until aboutFiveMinIntervals) { // find middle of the interval val calcDate = (timestamp + j * tempBolusSpacing * 60 * 1000 + 0.5 * tempBolusSpacing * 60 * 1000).toLong() diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpSync.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpSync.kt index cc14666dac..f7a6acb102 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpSync.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpSync.kt @@ -27,8 +27,13 @@ interface PumpSync { * * Call this function when new pump is paired to accept data from new pump * to prevent overlapping pump histories + * @param endRunning if true end previous running TBR and EB */ - fun connectNewPump() + + // @JvmOverloads and default value impossible on interface + // replace by `fun connectNewPump(endRunning: Boolean = true)` after full conversion to kotlin + fun connectNewPump(endRunning: Boolean) + fun connectNewPump() = connectNewPump(true) /* * GENERAL STATUS @@ -55,8 +60,29 @@ interface PumpSync { */ data class PumpState(val temporaryBasal: TemporaryBasal?, val extendedBolus: ExtendedBolus?, val bolus: Bolus?, val profile: Profile?) { - data class TemporaryBasal(val timestamp: Long, val duration: Long, val rate: Double, val isAbsolute: Boolean, val type: TemporaryBasalType, val id: Long, val pumpId: Long?) - data class ExtendedBolus(val timestamp: Long, val duration: Long, val amount: Double, val rate: Double) + data class TemporaryBasal @JvmOverloads constructor( + val timestamp: Long, + val duration: Long, + val rate: Double, + val isAbsolute: Boolean, + val type: TemporaryBasalType, + val id: Long, + val pumpId: Long?, + // used only to cancel TBR on pump change + val pumpType: PumpType = PumpType.USER, + val pumpSerial: String = "" + ) + + data class ExtendedBolus @JvmOverloads constructor( + val timestamp: Long, + val duration: Long, + val amount: Double, + val rate: Double, + // used only to cancel EB on pump change + val pumpType: PumpType = PumpType.USER, + val pumpSerial: String = "" + ) + data class Bolus(val timestamp: Long, val amount: Double) } diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/RunningConfiguration.kt b/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/RunningConfiguration.kt index 2e5b3871b5..5ce9eb1fda 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/RunningConfiguration.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/RunningConfiguration.kt @@ -61,6 +61,8 @@ class RunningConfiguration @Inject constructor( // called in NSClient mode only fun apply(configuration: JSONObject) { + assert(config.NSCLIENT) + if (configuration.has("version")) { rxBus.send(EventNSClientNewLog("VERSION", "Received AndroidAPS version ${configuration.getString("version")}")) if (config.VERSION_NAME.startsWith(configuration.getString("version")).not()) { @@ -100,7 +102,7 @@ class RunningConfiguration @Inject constructor( if (sp.getString(R.string.key_virtualpump_type, "fake") != pumpType) { sp.putString(R.string.key_virtualpump_type, pumpType) activePlugin.activePump.pumpDescription.fillFor(PumpType.getByDescription(pumpType)) - pumpSync.connectNewPump() + pumpSync.connectNewPump(endRunning = false) // do not end running TBRs, we call this only to accept data properly aapsLogger.debug(LTag.CORE, "Changing pump type to $pumpType") } } diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/PumpSyncImplementation.kt b/core/src/main/java/info/nightscout/androidaps/plugins/pump/PumpSyncImplementation.kt index 7cf4d09b88..66b7112b96 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/PumpSyncImplementation.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/PumpSyncImplementation.kt @@ -37,7 +37,15 @@ class PumpSyncImplementation @Inject constructor( private val disposable = CompositeDisposable() - override fun connectNewPump() { + override fun connectNewPump(endRunning: Boolean) { + if (endRunning) { + expectedPumpState().temporaryBasal?.let { + syncStopTemporaryBasalWithPumpId(dateUtil.now(), dateUtil.now(), it.pumpType, it.pumpSerial) + } + expectedPumpState().extendedBolus?.let { + syncStopExtendedBolusWithPumpId(dateUtil.now(), dateUtil.now(), it.pumpType, it.pumpSerial) + } + } sp.remove(R.string.key_active_pump_type) sp.remove(R.string.key_active_pump_serial_number) sp.remove(R.string.key_active_pump_change_timestamp) @@ -77,7 +85,7 @@ class PumpSyncImplementation @Inject constructor( } override fun expectedPumpState(): PumpSync.PumpState { - val bolus = repository.getLastBolusRecordWrapped().blockingGet(); + val bolus = repository.getLastBolusRecordWrapped().blockingGet() val temporaryBasal = repository.getTemporaryBasalActiveAt(dateUtil.now()).blockingGet() val extendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet() @@ -91,7 +99,9 @@ class PumpSyncImplementation @Inject constructor( rate = temporaryBasal.value.rate, isAbsolute = temporaryBasal.value.isAbsolute, type = PumpSync.TemporaryBasalType.fromDbType(temporaryBasal.value.type), - pumpId = temporaryBasal.value.interfaceIDs.pumpId + pumpId = temporaryBasal.value.interfaceIDs.pumpId, + pumpType = temporaryBasal.value.interfaceIDs.pumpType?.let { PumpType.fromDbPumpType(it)} ?: PumpType.USER, + pumpSerial = temporaryBasal.value.interfaceIDs.pumpSerial ?: "", ) else null, extendedBolus = @@ -100,7 +110,9 @@ class PumpSyncImplementation @Inject constructor( timestamp = extendedBolus.value.timestamp, duration = extendedBolus.value.duration, amount = extendedBolus.value.amount, - rate = extendedBolus.value.rate + rate = extendedBolus.value.rate, + pumpType = extendedBolus.value.interfaceIDs.pumpType?.let { PumpType.fromDbPumpType(it)} ?: PumpType.USER, + pumpSerial = extendedBolus.value.interfaceIDs.pumpSerial ?: "" ) else null, bolus = diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt index 8783644cde..be39b6dda7 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt @@ -359,6 +359,41 @@ enum class PumpType { fun getByDescription(desc: String): PumpType = values().firstOrNull { it.description == desc } ?: GENERIC_AAPS + + fun fromDbPumpType(pt: InterfaceIDs.PumpType): PumpType = + when (pt) { + InterfaceIDs.PumpType.GENERIC_AAPS -> GENERIC_AAPS + InterfaceIDs.PumpType.CELLNOVO -> CELLNOVO + InterfaceIDs.PumpType.ACCU_CHEK_COMBO -> ACCU_CHEK_COMBO + InterfaceIDs.PumpType.ACCU_CHEK_SPIRIT -> ACCU_CHEK_SPIRIT + InterfaceIDs.PumpType.ACCU_CHEK_INSIGHT -> ACCU_CHEK_INSIGHT_VIRTUAL + InterfaceIDs.PumpType.ACCU_CHEK_INSIGHT_BLUETOOTH -> ACCU_CHEK_INSIGHT + InterfaceIDs.PumpType.ACCU_CHEK_SOLO -> ACCU_CHEK_SOLO + InterfaceIDs.PumpType.ANIMAS_VIBE -> ANIMAS_VIBE + InterfaceIDs.PumpType.ANIMAS_PING -> ANIMAS_PING + InterfaceIDs.PumpType.DANA_R -> DANA_R + InterfaceIDs.PumpType.DANA_R_KOREAN -> DANA_R_KOREAN + InterfaceIDs.PumpType.DANA_RS -> DANA_RS + InterfaceIDs.PumpType.DANA_RS_KOREAN -> DANA_RS_KOREAN + InterfaceIDs.PumpType.DANA_RV2 -> DANA_RV2 + InterfaceIDs.PumpType.DANA_I -> DANA_I + InterfaceIDs.PumpType.OMNIPOD_EROS -> OMNIPOD_EROS + InterfaceIDs.PumpType.OMNIPOD_DASH -> OMNIPOD_DASH + InterfaceIDs.PumpType.MEDTRONIC_512_517 -> MEDTRONIC_512_712 + InterfaceIDs.PumpType.MEDTRONIC_515_715 -> MEDTRONIC_515_715 + InterfaceIDs.PumpType.MEDTRONIC_522_722 -> MEDTRONIC_522_722 + InterfaceIDs.PumpType.MEDTRONIC_523_723_REVEL -> MEDTRONIC_523_723_REVEL + InterfaceIDs.PumpType.MEDTRONIC_554_754_VEO -> MEDTRONIC_554_754_VEO + InterfaceIDs.PumpType.MEDTRONIC_640G -> MEDTRONIC_640G + InterfaceIDs.PumpType.TANDEM_T_SLIM -> TANDEM_T_SLIM + InterfaceIDs.PumpType.TANDEM_T_SLIM_G4 -> TANDEM_T_SLIM_G4 + InterfaceIDs.PumpType.TANDEM_T_FLEX -> TANDEM_T_FLEX + InterfaceIDs.PumpType.TANDEM_T_SLIM_X2 -> TANDEM_T_SLIM_X2 + InterfaceIDs.PumpType.YPSOPUMP -> YPSOPUMP + InterfaceIDs.PumpType.MDI -> MDI + InterfaceIDs.PumpType.USER -> USER + InterfaceIDs.PumpType.DIACONN_G8 -> DIACONN_G8 + } } constructor(description: String, model: String, parent: PumpType, pumpCapability: PumpCapability? = null, source: Sources? = null) { diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java index 2f37f541a6..eee40443e2 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java +++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java @@ -265,11 +265,11 @@ public class DateTimeUtil { } - public static long getATDWithAddedMinutes(long atd, int minutesDiff) { + public static long getATDWithAddedSeconds(Long atd, int addedSeconds) { GregorianCalendar oldestEntryTime = DateTimeUtil.toGregorianCalendar(atd); - oldestEntryTime.add(Calendar.MINUTE, minutesDiff); + oldestEntryTime.add(Calendar.SECOND, addedSeconds); - return oldestEntryTime.getTimeInMillis(); + return toATechDate(oldestEntryTime.getTimeInMillis()); } @@ -279,7 +279,6 @@ public class DateTimeUtil { return toATechDate(oldestEntryTime); } - public static long getTimeInFutureFromMinutes(long startTime, int minutes) { return startTime + getTimeInMs(minutes); } diff --git a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt index 2472ef84b8..615a0be902 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt @@ -70,6 +70,7 @@ class UserEntryPresentationHelper @Inject constructor( Sources.NSClientSource -> R.drawable.ic_nsclient_bg Sources.PocTech -> R.drawable.ic_poctech Sources.Tomato -> R.drawable.ic_sensor + Sources.Glunovo -> R.drawable.ic_glunovo Sources.Xdrip -> R.drawable.ic_blooddrop_48 Sources.LocalProfile -> R.drawable.ic_local_profile Sources.Loop -> R.drawable.ic_loop_closed_white diff --git a/core/src/main/res/drawable/ic_glunovo.xml b/core/src/main/res/drawable/ic_glunovo.xml new file mode 100644 index 0000000000..6fe7de817b --- /dev/null +++ b/core/src/main/res/drawable/ic_glunovo.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt index b06098f4ee..0477dc1c8d 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt @@ -1,7 +1,5 @@ package info.nightscout.androidaps.database.embedments -import info.nightscout.androidaps.database.entities.TherapyEvent - data class InterfaceIDs( var nightscoutSystemId: String? = null, var nightscoutId: String? = null, diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt index a292557866..241485a254 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/entities/GlucoseValue.kt @@ -108,6 +108,7 @@ data class GlucoseValue( GLIMP("Glimp"), LIBRE_2_NATIVE("Libre2"), POCTECH_NATIVE("Poctech"), + GLUNOVO_NATIVE("Glunovo"), MM_600_SERIES("MM600Series"), EVERSENSE("Eversense"), RANDOM("Random"), diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt index 889f365ec4..0b0db5bf82 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt @@ -146,6 +146,7 @@ data class UserEntry( NSClientSource, PocTech, Tomato, + Glunovo, Xdrip, LocalProfile, //From LocalProfile plugin Loop, //From Loop plugin diff --git a/medtronic/Changelog.txt b/medtronic/Changelog.txt new file mode 100644 index 0000000000..fc0b934b1c --- /dev/null +++ b/medtronic/Changelog.txt @@ -0,0 +1,11 @@ + +V1 - Medtronic initial implementation +... lots of changes + + +V2 - Rewrite into kotlin, new database (for v3.0) +0001 - initial version +0002 - some fixes +0003 - SMB fix (798) +0004 - Zero TBR Duration fix (798), refactoring of TempBasalProcessDTO +0005 - fixes to MedtronicHistoryEntry lateinit problem \ No newline at end of file diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.kt index 7d60dfe90f..5b86577182 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.kt @@ -32,6 +32,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.Riley import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ResetRileyLinkConfigurationTask import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask +import info.nightscout.androidaps.plugins.pump.common.sync.PumpDbEntryTBR import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil import info.nightscout.androidaps.plugins.pump.common.utils.ProfileUtil import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry @@ -111,7 +112,7 @@ class MedtronicPumpPlugin @Inject constructor( private var isBusy = false override fun onStart() { - aapsLogger.debug(LTag.PUMP, deviceID() + " started.") + aapsLogger.debug(LTag.PUMP, deviceID() + " started. (V2.0005)") serviceConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName) { aapsLogger.debug(LTag.PUMP, "RileyLinkMedtronicService is disconnected") @@ -468,6 +469,7 @@ class MedtronicPumpPlugin @Inject constructor( } } + @Synchronized override fun isThisProfileSet(profile: Profile): Boolean { aapsLogger.debug(LTag.PUMP, "isThisProfileSet: basalInitalized=" + medtronicPumpStatus.basalProfileStatus) if (!isInitialized) return true @@ -580,6 +582,7 @@ class MedtronicPumpPlugin @Inject constructor( scheduleNextRefresh(MedtronicStatusRefreshType.PumpTime, 0) } + @Synchronized override fun deliverBolus(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { aapsLogger.info(LTag.PUMP, "MedtronicPumpPlugin::deliverBolus - " + BolusDeliveryType.DeliveryPrepared) setRefreshButtonEnabled(false) @@ -691,6 +694,7 @@ class MedtronicPumpPlugin @Inject constructor( // if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged), // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed + @Synchronized override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType): PumpEnactResult { setRefreshButtonEnabled(false) if (isPumpNotReachable) { @@ -742,6 +746,7 @@ class MedtronicPumpPlugin @Inject constructor( return PumpEnactResult(injector).success(false).enacted(false) .comment(R.string.medtronic_cmd_cant_cancel_tbr_stop_op) } else { + //cancelTBRWithTemporaryId() aapsLogger.info(LTag.PUMP, logPrefix + "setTempBasalAbsolute - Current TBR cancelled.") } } @@ -760,8 +765,9 @@ class MedtronicPumpPlugin @Inject constructor( medtronicPumpStatus.tempBasalAmount = absoluteRate medtronicPumpStatus.tempBasalLength = durationInMinutes - val tempData = info.nightscout.androidaps.plugins.pump.common.sync.PumpDbEntryTBR(absoluteRate, true, durationInMinutes, tbrType) + val tempData = PumpDbEntryTBR(absoluteRate, true, durationInMinutes, tbrType) + medtronicPumpStatus.runningTBRWithTemp = tempData pumpSyncStorage.addTemporaryBasalRateWithTempId(tempData, true, this) incrementStatistics(MedtronicConst.Statistics.TBRsSet) @@ -771,6 +777,63 @@ class MedtronicPumpPlugin @Inject constructor( } } + @Deprecated("Not used, TBRs fixed in history, should be removed.") + private fun cancelTBRWithTemporaryId() { + val tbrs : MutableList = pumpSyncStorage.getTBRs() + if (tbrs.size > 0 && medtronicPumpStatus.runningTBRWithTemp!=null) { + aapsLogger.info(LTag.PUMP, logPrefix + "cancelTBRWithTemporaryId - TBR items: ${tbrs.size}") + + var item : PumpDbEntryTBR? = null + + if (tbrs.size==1) { + item = tbrs.get(0); + } else { + for (tbr in tbrs) { + if (tbr.date == medtronicPumpStatus.runningTBRWithTemp!!.date) { + item = tbr + break; + } + } + } + + if (item!=null) { + + aapsLogger.debug(LTag.PUMP, "DD: cancelTBRWithTemporaryId: tempIdEntry=${item}") + + val differenceS = (System.currentTimeMillis() - item.date) / 1000 + + aapsLogger.debug(LTag.PUMP, "syncTemporaryBasalWithTempId " + + "[date=${item.date}, " + + "rate=${item.rate}, " + + "duration=${differenceS} s, " + + "isAbsolute=${!item.isAbsolute}, temporaryId=${item.temporaryId}, " + + "pumpId=NO, pumpType=${medtronicPumpStatus.pumpType}, " + + "pumpSerial=${medtronicPumpStatus.serialNumber}]") + + val result = pumpSync.syncTemporaryBasalWithTempId( + timestamp = item.date, + rate = item.rate, + duration= differenceS * 1000L, + isAbsolute = item.isAbsolute, + temporaryId = item.temporaryId, + type = item.tbrType, + pumpId = null, + pumpType = medtronicPumpStatus.pumpType, + pumpSerial = medtronicPumpStatus.serialNumber) + + aapsLogger.debug(LTag.PUMP, "syncTemporaryBasalWithTempId - Result: $result") + } + + } else { + aapsLogger.info(LTag.PUMP, logPrefix + "cancelTBRWithTemporaryId - TBR items: ${tbrs.size}, runningTBRWithTemp=${medtronicPumpStatus.runningTBRWithTemp}") + } + + if (medtronicPumpStatus.runningTBRWithTemp!=null) { + medtronicPumpStatus.runningTBRWithTemp = null + } + } + + @Synchronized override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType): PumpEnactResult { return if (percent == 0) { setTempBasalAbsolute(0.0, durationInMinutes, profile, enforceNew, tbrType) @@ -966,6 +1029,7 @@ class MedtronicPumpPlugin @Inject constructor( } } + @Synchronized override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult { aapsLogger.info(LTag.PUMP, logPrefix + "cancelTempBasal - started") if (isPumpNotReachable) { @@ -1001,7 +1065,7 @@ class MedtronicPumpPlugin @Inject constructor( aapsLogger.info(LTag.PUMP, logPrefix + "cancelTempBasal - Cancel TBR successful.") val runningTBR = medtronicPumpStatus.runningTBR - + // TODO if (runningTBR != null) { if (medtronicHistoryData.isTBRActive(runningTBR)) { @@ -1026,6 +1090,8 @@ class MedtronicPumpPlugin @Inject constructor( } } + //cancelTBRWithTemporaryId() + PumpEnactResult(injector).success(true).enacted(true) // .isTempCancel(true) } @@ -1043,6 +1109,7 @@ class MedtronicPumpPlugin @Inject constructor( return medtronicPumpStatus.serialNumber } + @Synchronized override fun setNewBasalProfile(profile: Profile): PumpEnactResult { aapsLogger.info(LTag.PUMP, logPrefix + "setNewBasalProfile") diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.kt index dc38b62601..8776906d95 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.kt @@ -14,13 +14,13 @@ import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil */ abstract class MedtronicHistoryEntry : MedtronicHistoryEntryInterface { - lateinit var rawData: List + var rawData: List = listOf() protected var sizes = IntArray(3) - lateinit var head: ByteArray - lateinit var datetime: ByteArray - lateinit var body: ByteArray + var head: ByteArray = byteArrayOf() + var datetime: ByteArray = byteArrayOf() + var body: ByteArray = byteArrayOf() var id: Long = 0 @@ -41,7 +41,13 @@ abstract class MedtronicHistoryEntry : MedtronicHistoryEntryInterface { /** * Pump id that will be used with AAPS object (time * 1000 + historyType (max is FF = 255) */ - open var pumpId: Long = 0L + var pumpId: Long = 0L + get() { + if (field == 0L) { + field = generatePumpId() + } + return field + } /** * if history object is already linked to AAPS object (either Treatment, TempBasal or TDD (tdd's @@ -158,7 +164,7 @@ abstract class MedtronicHistoryEntry : MedtronicHistoryEntryInterface { sb.append("]") return sb.toString() } - if (head.size != 0) { + if (head!=null && head.size != 0) { sb.append(", head=") sb.append(ByteUtil.shortHexString(head)) } diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.kt index 0bc1e8106e..c16bf5c362 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.kt @@ -92,14 +92,6 @@ class PumpHistoryEntry : MedtronicHistoryEntry() { } } - override var pumpId: Long = 0L - get() { - if (field == 0L) { - field = generatePumpId() - } - return field - } - fun hasBolusChanged(entry: PumpHistoryEntry): Boolean { if (entryType == PumpHistoryEntryType.Bolus) { val thisOne: BolusDTO = this.decodedData["Object"] as BolusDTO diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.kt index 4a23d5e222..edd3de2c35 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.kt @@ -508,25 +508,25 @@ class MedtronicHistoryData @Inject constructor( if (temporaryId != null) { val result = pumpSync.syncBolusWithTempId( - tryToGetByLocalTime(bolus.atechDateTime), - deliveredAmount, - temporaryId, - type, - bolus.pumpId, - medtronicPumpStatus.pumpType, - medtronicPumpStatus.serialNumber) + timestamp = tryToGetByLocalTime(bolus.atechDateTime), + amount = deliveredAmount, + temporaryId = temporaryId, + type = null, + pumpId = bolus.pumpId, + pumpType = medtronicPumpStatus.pumpType, + pumpSerial = medtronicPumpStatus.serialNumber) aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "syncBolusWithTempId [date=%d, temporaryId=%d, pumpId=%d, insulin=%.2f, pumpSerial=%s] - Result: %b", bolus.atechDateTime, temporaryId, bolus.pumpId, deliveredAmount, medtronicPumpStatus.serialNumber, result)) } else { val result = pumpSync.syncBolusWithPumpId( - tryToGetByLocalTime(bolus.atechDateTime), - deliveredAmount, - type, - bolus.pumpId, - medtronicPumpStatus.pumpType, - medtronicPumpStatus.serialNumber) + timestamp = tryToGetByLocalTime(bolus.atechDateTime), + amount = deliveredAmount, + type = null, + pumpId = bolus.pumpId, + pumpType = medtronicPumpStatus.pumpType, + pumpSerial = medtronicPumpStatus.serialNumber) aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "syncBolusWithPumpId [date=%d, pumpId=%d, insulin=%.2f, pumpSerial=%s] - Result: %b", bolus.atechDateTime, bolus.pumpId, deliveredAmount, @@ -571,62 +571,42 @@ class MedtronicHistoryData @Inject constructor( private fun processTBREntries(entryList: MutableList) { entryList.reverse() val tbr = entryList[0].getDecodedDataEntry("Object") as TempBasalPair - var readOldItem = false - if (tbr.isCancelTBR) { - val oneMoreEntryFromHistory = getOneMoreEntryFromHistory(PumpHistoryEntryType.TempBasalCombined) +// var readOldItem = false + + val oneMoreEntryFromHistory = getOneMoreEntryFromHistory(PumpHistoryEntryType.TempBasalCombined) + + if (tbr.isCancelTBR) { // if we have cancel we need to limit previous TBR with this cancel if (oneMoreEntryFromHistory != null) { entryList.add(0, oneMoreEntryFromHistory) - readOldItem = true } else { entryList.removeAt(0) } + } else { + if (oneMoreEntryFromHistory != null) { + val tbrPrev = oneMoreEntryFromHistory.getDecodedDataEntry("Object") as TempBasalPair + if (tbrPrev.isZeroTBR) { // if we had Zere TBR in last previous TBR, then we need to limit it, so we need to process it too + entryList.add(0, oneMoreEntryFromHistory) + } + } } val tbrRecords = pumpSyncStorage.getTBRs() - aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, ProcessHistoryRecord.TBR.description + " List (before filter): %s, FromDb=%s", gson.toJson(entryList), - tbrRecords)) - var processDTO: TempBasalProcessDTO? = null - val processList: MutableList = mutableListOf() - for (treatment in entryList) { - val tbr2 = treatment.getDecodedDataEntry("Object") as TempBasalPair - if (tbr2.isCancelTBR) { - if (processDTO != null) { - processDTO.itemTwo = treatment - processDTO.cancelPresent = true - if (readOldItem) { - processDTO.processOperation = TempBasalProcessDTO.Operation.Edit - readOldItem = false - } - } else { - aapsLogger.warn(LTag.PUMP, "processDTO was null - shouldn't happen, ignoring item. ItemTwo=$treatment") - } - } else { - if (processDTO != null) { - processList.add(processDTO) - } - processDTO = TempBasalProcessDTO( - itemOne = treatment, - processOperation = TempBasalProcessDTO.Operation.Add, - aapsLogger = aapsLogger, - objectType = TempBasalProcessDTO.ObjectType.TemporaryBasal - ) - } - } - if (processDTO != null) { - processList.add(processDTO) - } + + val processList: MutableList = createTBRProcessList(entryList); + if (processList.isNotEmpty()) { for (tempBasalProcessDTO in processList) { - aapsLogger.debug(LTag.PUMP, "DD: tempBasalProcessDTO.itemOne: " + gson.toJson(tempBasalProcessDTO.itemOne)) - aapsLogger.debug(LTag.PUMP, "DD: tempBasalProcessDTO.itemTwo: " + (if (tempBasalProcessDTO.itemTwo == null) "null" else gson.toJson(tempBasalProcessDTO.itemTwo!!))) + aapsLogger.debug(LTag.PUMP, "DD: tempBasalProcessDTO: " + tempBasalProcessDTO.toTreatmentString()) + //aapsLogger.debug(LTag.PUMP, "DD: tempBasalProcessDTO.itemOne: " + gson.toJson(tempBasalProcessDTO.itemOne)) + //aapsLogger.debug(LTag.PUMP, "DD: tempBasalProcessDTO.itemTwo: " + (if (tempBasalProcessDTO.itemTwo == null) "null" else gson.toJson(tempBasalProcessDTO.itemTwo!!))) @Suppress("Unchecked_Cast") val entryWithTempId = findDbEntry(tempBasalProcessDTO.itemOne, tbrRecords as MutableList) as PumpDbEntryTBR? aapsLogger.debug(LTag.PUMP, "DD: entryWithTempId: " + (entryWithTempId?.toString() ?: "null")) - val tbrEntry = tempBasalProcessDTO.itemOneTbr //.getDecodedDataEntry("Object") as TempBasalPair + val tbrEntry = tempBasalProcessDTO.itemOneTbr aapsLogger.debug(LTag.PUMP, String.format("DD: tbrEntry=%s, tempBasalProcessDTO=%s", gson.toJson(tbrEntry), gson.toJson(tempBasalProcessDTO))) @@ -649,7 +629,7 @@ class MedtronicHistoryData @Inject constructor( tryToGetByLocalTime(tempBasalProcessDTO.atechDateTime), tbrEntry.insulinRate, tempBasalProcessDTO.durationAsSeconds * 1000L, - !tbrEntry.isPercent, + isAbsolute = !tbrEntry.isPercent, entryWithTempId.temporaryId, PumpSync.TemporaryBasalType.NORMAL, tempBasalProcessDTO.pumpId, @@ -705,10 +685,10 @@ class MedtronicHistoryData @Inject constructor( date = tryToGetByLocalTime(tempBasalProcessDTO.atechDateTime), pumpType = medtronicPumpStatus.pumpType, serialNumber = medtronicPumpStatus.serialNumber, - entry = PumpDbEntryTBR(rate = tbrEntry.insulinRate, - isAbsolute = !tbrEntry.isPercent, - durationInSeconds = tempBasalProcessDTO.durationAsSeconds, - tbrType = PumpSync.TemporaryBasalType.NORMAL), + rate = tbrEntry.insulinRate, + isAbsolute = !tbrEntry.isPercent, + durationInSeconds = tempBasalProcessDTO.durationAsSeconds, + tbrType = PumpSync.TemporaryBasalType.NORMAL, pumpId = tempBasalProcessDTO.pumpId) } } @@ -720,6 +700,56 @@ class MedtronicHistoryData @Inject constructor( } // collection } + fun createTBRProcessList(entryList: MutableList) : MutableList { + + aapsLogger.debug(LTag.PUMP, "${ProcessHistoryRecord.TBR.description} List (before filter): ${gson.toJson(entryList)}") + + var processDTO: TempBasalProcessDTO? = null + val processList: MutableList = mutableListOf() + for (treatment in entryList) { + val tbr2 = treatment.getDecodedDataEntry("Object") as TempBasalPair + if (tbr2.isCancelTBR) { + if (processDTO != null) { + processDTO.itemTwo = treatment + } else { + aapsLogger.warn(LTag.PUMP, "processDTO was null - shouldn't happen, ignoring item. ItemTwo=$treatment") + } + } else { + if (processDTO != null) { + processList.add(processDTO) + } + processDTO = TempBasalProcessDTO( + itemOne = treatment, + aapsLogger = aapsLogger, + objectType = TempBasalProcessDTO.ObjectType.TemporaryBasal + ) + } + } + if (processDTO != null) { + processList.add(processDTO) + } + + var previousItem: TempBasalProcessDTO? = null + + // fix for Zero TBRs + for (tempBasalProcessDTO in processList) { + if (previousItem!=null) { + + var pheEnd = PumpHistoryEntry() + pheEnd.atechDateTime = DateTimeUtil.getATDWithAddedSeconds(tempBasalProcessDTO.itemOne.atechDateTime, -2) + pheEnd.addDecodedData("Object", TempBasalPair(0.0, false, 0)) + + previousItem.itemTwo = pheEnd + + previousItem = null + } + if (tempBasalProcessDTO.itemOneTbr!!.isZeroTBR) { + previousItem = tempBasalProcessDTO + } + } + + return processList + } fun isTBRActive(dbEntry: PumpDbEntryTBR): Boolean { @@ -881,7 +911,6 @@ class MedtronicHistoryData @Inject constructor( while (i < filtered2Items.size) { val tbrProcess = TempBasalProcessDTO( itemOne = filtered2Items[i], - processOperation = TempBasalProcessDTO.Operation.Add, aapsLogger = aapsLogger, objectType = TempBasalProcessDTO.ObjectType.Suspend) @@ -959,7 +988,6 @@ class MedtronicHistoryData @Inject constructor( if (items.size > 0) { val tbrProcess = TempBasalProcessDTO( itemOne = items[items.size - 1], - processOperation = TempBasalProcessDTO.Operation.Add, aapsLogger = aapsLogger, objectType = TempBasalProcessDTO.ObjectType.Suspend) @@ -975,7 +1003,6 @@ class MedtronicHistoryData @Inject constructor( if (items.size > 0) { val tbrProcess = TempBasalProcessDTO( itemOne = items[0], - processOperation = TempBasalProcessDTO.Operation.Add, aapsLogger = aapsLogger, objectType = TempBasalProcessDTO.ObjectType.Suspend) diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.kt index fcc7e71996..3c48934552 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.kt @@ -91,6 +91,9 @@ class TempBasalPair : TempBasalPair { val isCancelTBR: Boolean get() = MedtronicUtil.isSame(insulinRate, 0.0) && durationMinutes == 0 + val isZeroTBR: Boolean + get() = MedtronicUtil.isSame(insulinRate, 0.0) && durationMinutes != 0 + val description: String get() { if (isCancelTBR) { diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.kt index a0ce5241de..b4b3b6cefb 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.kt @@ -4,9 +4,9 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry +import java.lang.StringBuilder class TempBasalProcessDTO constructor(var itemOne: PumpHistoryEntry, - var processOperation: Operation = Operation.None, var aapsLogger: AAPSLogger, var objectType: ObjectType = ObjectType.TemporaryBasal) { @@ -21,8 +21,6 @@ class TempBasalProcessDTO constructor(var itemOne: PumpHistoryEntry, var itemOneTbr: TempBasalPair? = null var itemTwoTbr: TempBasalPair? = null - var cancelPresent: Boolean = false - val atechDateTime: Long get() = itemOne.atechDateTime @@ -31,26 +29,26 @@ class TempBasalProcessDTO constructor(var itemOne: PumpHistoryEntry, val durationAsSeconds: Int get() { - aapsLogger.debug(LTag.PUMP, "durationAsSeconds: [objectType=$objectType]") + //aapsLogger.debug(LTag.PUMP, "durationAsSeconds: [objectType=$objectType]") if (objectType == ObjectType.TemporaryBasal) { if (itemTwo == null) { if (itemOneTbr != null) { - aapsLogger.debug("TemporaryBasalPair - itemOneSingle: $itemOneTbr") + //aapsLogger.debug("TemporaryBasalPair - itemOneSingle: $itemOneTbr") return itemOneTbr!!.durationMinutes * 60 } else { - aapsLogger.error("Couldn't find TempBasalPair in entry: $itemOne") + //aapsLogger.error("Couldn't find TempBasalPair in entry: $itemOne") return 0 } } else { - aapsLogger.debug(LTag.PUMP, "Found 2 items for duration: itemOne=$itemOne, itemTwo=$itemTwo") + //aapsLogger.debug(LTag.PUMP, "Found 2 items for duration: itemOne=$itemOne, itemTwo=$itemTwo") val secondsDiff = DateTimeUtil.getATechDateDiferenceAsSeconds(itemOne.atechDateTime, itemTwo!!.atechDateTime) - aapsLogger.debug(LTag.PUMP, "Difference in seconds: $secondsDiff") + //aapsLogger.debug(LTag.PUMP, "Difference in seconds: $secondsDiff") return secondsDiff } } else { - aapsLogger.debug(LTag.PUMP, "Found 2 items for duration (in SuspendMode): itemOne=$itemOne, itemTwo=$itemTwo") + //aapsLogger.debug(LTag.PUMP, "Found 2 items for duration (in SuspendMode): itemOne=$itemOne, itemTwo=$itemTwo") val secondsDiff = DateTimeUtil.getATechDateDiferenceAsSeconds(itemOne.atechDateTime, itemTwo!!.atechDateTime) - aapsLogger.debug(LTag.PUMP, "Difference in seconds: $secondsDiff") + //aapsLogger.debug(LTag.PUMP, "Difference in seconds: $secondsDiff") return secondsDiff } } @@ -61,8 +59,31 @@ class TempBasalProcessDTO constructor(var itemOne: PumpHistoryEntry, } } + fun toTreatmentString(): String { + val stringBuilder = StringBuilder() + + stringBuilder.append(itemOne.DT) + + if (itemTwo!=null) { + stringBuilder.append(" - ") + stringBuilder.append(itemTwo!!.DT) + } + + var dur = durationAsSeconds + + stringBuilder.append(" " + durationAsSeconds + " s (" + durationAsSeconds/60 + ")") + + if (itemTwoTbr!=null) { + stringBuilder.append(" " + itemOneTbr!!.insulinRate + " / " + itemTwoTbr!!.insulinRate) + } else { + stringBuilder.append(" " + itemOneTbr!!.insulinRate) + } + + return stringBuilder.toString() + } + override fun toString(): String { - return "ItemOne: $itemOne, ItemTwo: $itemTwo, Duration: $durationAsSeconds, Operation: $processOperation, ObjectType: $objectType" + return "ItemOne: $itemOne, ItemTwo: $itemTwo, Duration: $durationAsSeconds, ObjectType: $objectType" } enum class Operation { diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.kt index 05da8df050..0351a4c391 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.kt @@ -34,6 +34,7 @@ class MedtronicPumpStatus @Inject constructor(private val rh: ResourceHelper, var maxBolus: Double? = null var maxBasal: Double? = null var runningTBR: PumpDbEntryTBR? = null + var runningTBRWithTemp: PumpDbEntryTBR? = null // statuses var pumpDeviceState = PumpDeviceState.NeverContacted diff --git a/medtronic/src/test/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryDataUTest.kt b/medtronic/src/test/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryDataUTest.kt new file mode 100644 index 0000000000..4f9fef20c1 --- /dev/null +++ b/medtronic/src/test/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryDataUTest.kt @@ -0,0 +1,91 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data + +import java.lang.reflect.Type +import com.google.gson.reflect.TypeToken +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.internal.LinkedTreeMap +import dagger.android.AndroidInjector +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.TestBase +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.PumpSync +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.plugins.pump.common.sync.PumpSyncStorage +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil +import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.hamcrest.Matchers.notNullValue +import org.junit.Assert.* + +import org.junit.Test +import org.mockito.Mock +import java.io.File +import java.net.URL + +class MedtronicHistoryDataUTest : TestBase() { + + @Mock lateinit var activePlugin: ActivePlugin + @Mock lateinit var medtronicUtil: MedtronicUtil + @Mock lateinit var medtronicPumpHistoryDecoder: MedtronicPumpHistoryDecoder + @Mock lateinit var medtronicPumpStatus: MedtronicPumpStatus + @Mock lateinit var pumpSync: PumpSync + @Mock lateinit var pumpSyncStorage: PumpSyncStorage + @Mock lateinit var sp: SP + + + private val packetInjector = HasAndroidInjector { + AndroidInjector { + + } + } + + + + @Test + fun createTBRProcessList() { + + var unitToTest = MedtronicHistoryData(packetInjector, aapsLogger, sp, activePlugin, + medtronicUtil, medtronicPumpHistoryDecoder, + medtronicPumpStatus, + pumpSync, + pumpSyncStorage) + + + val gson = Gson() + + val fileText = ClassLoader.getSystemResource("tbr_data.json").readText() + + val listType: Type = object : TypeToken?>() {}.getType() + val yourClassList: MutableList = gson.fromJson(fileText, listType) + + for (pumpHistoryEntry in yourClassList) { + val stringObject = pumpHistoryEntry.decodedData["Object"] as LinkedTreeMap + + val rate : Double = stringObject.get("insulinRate") as Double + val durationMinutes: Double = stringObject.get("durationMinutes") as Double + val durationMinutesInt : Int = durationMinutes.toInt() + + var tmbPair = TempBasalPair(rate, false, durationMinutesInt) + + pumpHistoryEntry.decodedData.remove("Object") + pumpHistoryEntry.addDecodedData("Object", tmbPair) + } + + System.out.println("TBR Pre-Process List: " + gson.toJson(yourClassList)) + + val createTBRProcessList = unitToTest.createTBRProcessList(yourClassList) + + System.out.println("TBR Process List: " + createTBRProcessList.size) + + for (tempBasalProcessDTO in createTBRProcessList) { + System.out.println(tempBasalProcessDTO.toTreatmentString()) + } + + } + + +} \ No newline at end of file diff --git a/medtronic/src/test/resources/tbr_data.json b/medtronic/src/test/resources/tbr_data.json new file mode 100644 index 0000000000..7e2aa44b84 --- /dev/null +++ b/medtronic/src/test/resources/tbr_data.json @@ -0,0 +1,1761 @@ +[{ + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:35:08", + "atechDateTime": 20211031013508, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:39:47", + "atechDateTime": 20211031013947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:39:49", + "atechDateTime": 20211031013949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.1500000000000001, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:49:41", + "atechDateTime": 20211031014941, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:49:43", + "atechDateTime": 20211031014943, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.8, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:54:59", + "atechDateTime": 20211031015459, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:55:05", + "atechDateTime": 20211031015505, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 01:59:43", + "atechDateTime": 20211031015943, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:04:45", + "atechDateTime": 20211031020445, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.55, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:09:47", + "atechDateTime": 20211031020947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:09:48", + "atechDateTime": 20211031020948, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.35000000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:14:56", + "atechDateTime": 20211031021456, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:14:57", + "atechDateTime": 20211031021457, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.3000000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:25:16", + "atechDateTime": 20211031022516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:25:18", + "atechDateTime": 20211031022518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.85, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:29:50", + "atechDateTime": 20211031022950, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:29:51", + "atechDateTime": 20211031022951, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.25, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:34:45", + "atechDateTime": 20211031023445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:34:47", + "atechDateTime": 20211031023447, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.0500000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:45:16", + "atechDateTime": 20211031024516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:45:18", + "atechDateTime": 20211031024518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.75, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:50:16", + "atechDateTime": 20211031025016, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:50:18", + "atechDateTime": 20211031025018, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:54:45", + "atechDateTime": 20211031025445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:54:46", + "atechDateTime": 20211031025446, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.75, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 02:59:47", + "atechDateTime": 20211031025947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:05:16", + "atechDateTime": 20211031030516, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.85, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:10:07", + "atechDateTime": 20211031031007, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:10:08", + "atechDateTime": 20211031031008, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.05, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:15:11", + "atechDateTime": 20211031031511, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:15:13", + "atechDateTime": 20211031031513, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:35:16", + "atechDateTime": 20211031033516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:35:18", + "atechDateTime": 20211031033518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:49:50", + "atechDateTime": 20211031034950, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:49:52", + "atechDateTime": 20211031034952, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.35000000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:55:07", + "atechDateTime": 20211031035507, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 03:55:08", + "atechDateTime": 20211031035508, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.9000000000000004, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:00:16", + "atechDateTime": 20211031040016, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:04:45", + "atechDateTime": 20211031040445, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.2, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:09:47", + "atechDateTime": 20211031040947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:09:49", + "atechDateTime": 20211031040949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:14:44", + "atechDateTime": 20211031041444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.7000000000000002, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:24:42", + "atechDateTime": 20211031042442, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:24:43", + "atechDateTime": 20211031042443, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.05, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:30:16", + "atechDateTime": 20211031043016, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:30:18", + "atechDateTime": 20211031043018, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.2, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:34:45", + "atechDateTime": 20211031043445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:34:47", + "atechDateTime": 20211031043447, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:44:43", + "atechDateTime": 20211031044443, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:44:44", + "atechDateTime": 20211031044444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.85, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:49:41", + "atechDateTime": 20211031044941, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:49:43", + "atechDateTime": 20211031044943, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:54:44", + "atechDateTime": 20211031045444, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:54:45", + "atechDateTime": 20211031045445, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.8, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 04:59:51", + "atechDateTime": 20211031045951, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:04:44", + "atechDateTime": 20211031050444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:09:46", + "atechDateTime": 20211031050946, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:09:47", + "atechDateTime": 20211031050947, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:15:16", + "atechDateTime": 20211031051516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:15:18", + "atechDateTime": 20211031051518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.5, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:20:07", + "atechDateTime": 20211031052007, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:20:08", + "atechDateTime": 20211031052008, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:34:44", + "atechDateTime": 20211031053444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.6, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:39:46", + "atechDateTime": 20211031053946, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:39:47", + "atechDateTime": 20211031053947, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:49:48", + "atechDateTime": 20211031054948, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:49:49", + "atechDateTime": 20211031054949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.4000000000000004, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 05:59:45", + "atechDateTime": 20211031055945, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:05:43", + "atechDateTime": 20211031060543, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:10:14", + "atechDateTime": 20211031061014, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:10:15", + "atechDateTime": 20211031061015, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.25, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:35:13", + "atechDateTime": 20211031063513, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:35:15", + "atechDateTime": 20211031063515, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:50:13", + "atechDateTime": 20211031065013, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:50:14", + "atechDateTime": 20211031065014, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.15000000000000002, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:51:08", + "atechDateTime": 20211031065108, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:51:10", + "atechDateTime": 20211031065110, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.9000000000000001, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:54:45", + "atechDateTime": 20211031065445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 06:54:47", + "atechDateTime": 20211031065447, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:00:15", + "atechDateTime": 20211031070015, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:04:46", + "atechDateTime": 20211031070446, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:14:56", + "atechDateTime": 20211031071456, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:14:58", + "atechDateTime": 20211031071458, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.35000000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:19:53", + "atechDateTime": 20211031071953, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:19:55", + "atechDateTime": 20211031071955, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:25:20", + "atechDateTime": 20211031072520, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:25:22", + "atechDateTime": 20211031072522, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:39:46", + "atechDateTime": 20211031073946, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:44:44", + "atechDateTime": 20211031074444, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:44:46", + "atechDateTime": 20211031074446, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:55:14", + "atechDateTime": 20211031075514, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 07:55:16", + "atechDateTime": 20211031075516, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:00:17", + "atechDateTime": 20211031080017, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:04:46", + "atechDateTime": 20211031080446, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.1, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:14:48", + "atechDateTime": 20211031081448, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:14:50", + "atechDateTime": 20211031081450, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.55, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:29:46", + "atechDateTime": 20211031082946, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:29:48", + "atechDateTime": 20211031082948, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.4000000000000004, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:40:27", + "atechDateTime": 20211031084027, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:40:28", + "atechDateTime": 20211031084028, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.8, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:49:46", + "atechDateTime": 20211031084946, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:49:48", + "atechDateTime": 20211031084948, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.85, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:56:11", + "atechDateTime": 20211031085611, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:56:13", + "atechDateTime": 20211031085613, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.35, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 08:56:26", + "atechDateTime": 20211031085626, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:00:49", + "atechDateTime": 20211031090049, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.6, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:04:43", + "atechDateTime": 20211031090443, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:04:44", + "atechDateTime": 20211031090444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:15:16", + "atechDateTime": 20211031091516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:15:18", + "atechDateTime": 20211031091518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.05, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:19:47", + "atechDateTime": 20211031091947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:19:49", + "atechDateTime": 20211031091949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.25, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:30:02", + "atechDateTime": 20211031093002, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:30:04", + "atechDateTime": 20211031093004, + "decodedData": { + "Object": { + "durationMinutes": 60, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:54:44", + "atechDateTime": 20211031095444, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.0500000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 09:59:51", + "atechDateTime": 20211031095951, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:05:22", + "atechDateTime": 20211031100522, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.8, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:09:45", + "atechDateTime": 20211031100945, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:09:47", + "atechDateTime": 20211031100947, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.2000000000000002, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:24:48", + "atechDateTime": 20211031102448, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:24:49", + "atechDateTime": 20211031102449, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.5, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:30:19", + "atechDateTime": 20211031103019, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:30:20", + "atechDateTime": 20211031103020, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.6000000000000001, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:35:16", + "atechDateTime": 20211031103516, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:35:17", + "atechDateTime": 20211031103517, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.4000000000000004, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:41:00", + "atechDateTime": 20211031104100, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:41:02", + "atechDateTime": 20211031104102, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 5.3500000000000005, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:49:48", + "atechDateTime": 20211031104948, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:49:49", + "atechDateTime": 20211031104949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.2000000000000002, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 10:59:44", + "atechDateTime": 20211031105944, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:05:18", + "atechDateTime": 20211031110518, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.9000000000000001, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:10:34", + "atechDateTime": 20211031111034, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:10:35", + "atechDateTime": 20211031111035, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 1.4500000000000002, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:14:48", + "atechDateTime": 20211031111448, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:14:50", + "atechDateTime": 20211031111450, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:30:15", + "atechDateTime": 20211031113015, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.5, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:45:43", + "atechDateTime": 20211031114543, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:45:44", + "atechDateTime": 20211031114544, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.25, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:50:12", + "atechDateTime": 20211031115012, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:50:13", + "atechDateTime": 20211031115013, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.05, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:54:45", + "atechDateTime": 20211031115445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:54:46", + "atechDateTime": 20211031115446, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.8000000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 11:59:49", + "atechDateTime": 20211031115949, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:04:47", + "atechDateTime": 20211031120447, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.25, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:14:46", + "atechDateTime": 20211031121446, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:14:48", + "atechDateTime": 20211031121448, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.5500000000000003, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:19:45", + "atechDateTime": 20211031121945, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:19:47", + "atechDateTime": 20211031121947, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.2, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:20:24", + "atechDateTime": 20211031122024, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:20:26", + "atechDateTime": 20211031122026, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.15, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:24:52", + "atechDateTime": 20211031122452, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:24:53", + "atechDateTime": 20211031122453, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 0.05, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:25:01", + "atechDateTime": 20211031122501, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:25:03", + "atechDateTime": 20211031122503, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.15, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:34:45", + "atechDateTime": 20211031123445, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:34:47", + "atechDateTime": 20211031123447, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 2.95, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:39:47", + "atechDateTime": 20211031123947, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:39:49", + "atechDateTime": 20211031123949, + "decodedData": { + "Object": { + "durationMinutes": 30, + "insulinRate": 3.6, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:41:18", + "atechDateTime": 20211031124118, + "decodedData": { + "Object": { + "durationMinutes": 0, + "insulinRate": 0.0, + "isPercent": false + } + } +}, { + "entryType": "TempBasalCombined", + "DT": "31.10.2021 12:41:19", + "atechDateTime": 20211031124119, + "decodedData": { + "Object": { + "durationMinutes": 60, + "insulinRate": 0.0, + "isPercent": false + } + } +}] diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt index ce3a4a3e7b..f304c32757 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/OmnipodDashPumpPlugin.kt @@ -235,7 +235,6 @@ class OmnipodDashPumpPlugin @Inject constructor( override fun connect(reason: String) { aapsLogger.info(LTag.PUMP, "connect reason=$reason") podStateManager.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING - synchronized(this) { stopConnecting?.let { aapsLogger.warn(LTag.PUMP, "Already connecting: $stopConnecting") @@ -244,7 +243,6 @@ class OmnipodDashPumpPlugin @Inject constructor( val stop = CountDownLatch(1) stopConnecting = stop } - thread( start = true, name = "ConnectionThread", @@ -253,6 +251,9 @@ class OmnipodDashPumpPlugin @Inject constructor( stopConnecting?.let { val error = omnipodManager.connect(it).ignoreElements().blockingGet() aapsLogger.info(LTag.PUMPCOMM, "connect error=$error") + if (error == null) { + podStateManager.incrementSuccessfulConnectionAttemptsAfterRetries() + } } } finally { synchronized(this) { @@ -270,6 +271,7 @@ class OmnipodDashPumpPlugin @Inject constructor( override fun stopConnecting() { aapsLogger.info(LTag.PUMP, "stopConnecting") + podStateManager.incrementFailedConnectionsAfterRetries() stopConnecting?.countDown() omnipodManager.disconnect(true) } @@ -340,15 +342,16 @@ class OmnipodDashPumpPlugin @Inject constructor( aapsLogger.info(LTag.PUMP, "syncBolusWithPumpId on CANCEL_BOLUS returned: $sync") } } - - podStateManager.alarmType?.let { - showNotification( - Notification.OMNIPOD_POD_FAULT, - it.toString(), - Notification.URGENT, - R.raw.boluserror - ) - if (!podStateManager.alarmSynced) { + if (!podStateManager.alarmSynced) { + podStateManager.alarmType?.let { + if (!commandQueue.isCustomCommandInQueue(CommandDeactivatePod::class.java)) { + showNotification( + Notification.OMNIPOD_POD_FAULT, + it.toString(), + Notification.URGENT, + R.raw.boluserror + ) + } pumpSync.insertAnnouncement( error = it.toString(), pumpId = Random.Default.nextLong(), @@ -694,7 +697,7 @@ class OmnipodDashPumpPlugin @Inject constructor( } val percent = (waited.toFloat() / estimatedDeliveryTimeSeconds) * 100 updateBolusProgressDialog( - rh.gs(R.string.bolusdelivering, requestedBolusAmount), + rh.gs(R.string.dash_bolusdelivering, requestedBolusAmount), percent.toInt() ) } @@ -1191,7 +1194,7 @@ class OmnipodDashPumpPlugin @Inject constructor( private fun handleTimeChange(): PumpEnactResult { return profileFunction.getProfile()?.let { setNewBasalProfile(it, OmnipodCommandType.SET_TIME) - } ?: PumpEnactResult(injector).success(true).enacted(false).comment("No profile active") + } ?: PumpEnactResult(injector).success(false).enacted(false).comment("No profile active") } private fun updateAlertConfiguration(): PumpEnactResult { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManager.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManager.kt index 718df1597b..4f3f682892 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManager.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManager.kt @@ -23,8 +23,6 @@ interface OmnipodDashManager { fun suspendDelivery(hasBasalBeepEnabled: Boolean): Observable - fun setTime(): Observable - fun setTempBasal(rate: Double, durationInMinutes: Short, tempBasalBeeps: Boolean): Observable fun stopTempBasal(hasTempBasalBeepEnabled: Boolean): Observable diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt index 2a88cc2d0d..0d62eb9a68 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt @@ -196,7 +196,7 @@ class OmnipodDashManagerImpl @Inject constructor( DefaultStatusResponse::class ) }.doOnComplete { - podStateManager.timeZone = TimeZone.getDefault() + podStateManager.updateTimeZone() } } @@ -506,12 +506,6 @@ class OmnipodDashManagerImpl @Inject constructor( ).interceptPodEvents() } - override fun setTime(): Observable { - // TODO - logger.error(LTag.PUMPCOMM, "NOT IMPLEMENTED: setTime()") - return Observable.empty() - } - private fun observeSendProgramTempBasalCommand(rate: Double, durationInMinutes: Short, tempBasalBeeps: Boolean): Observable { return Observable.defer { bleManager.sendCommand( diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManager.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManager.kt index 6f5ab0ee62..fa38444317 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManager.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManager.kt @@ -33,8 +33,11 @@ interface OmnipodDashPodStateManager { var bluetoothConnectionState: BluetoothConnectionState var connectionAttempts: Int var successfulConnections: Int + val successfulConnectionAttemptsAfterRetries: Int + val failedConnectionsAfterRetries: Int - var timeZone: TimeZone + val timeZoneId: String? + val timeZoneUpdated: Long? val sameTimeZone: Boolean // The TimeZone is the same on the phone and on the pod val lastUpdatedSystem: Long // System.currentTimeMillis() val lastStatusResponseReceived: Long @@ -85,6 +88,9 @@ interface OmnipodDashPodStateManager { fun updateFromPairing(uniqueId: Id, pairResult: PairResult) fun reset() fun connectionSuccessRatio(): Float + fun incrementSuccessfulConnectionAttemptsAfterRetries() + fun incrementFailedConnectionsAfterRetries() + fun updateTimeZone() fun createActiveCommand( historyId: String, diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManagerImpl.kt index 07404b8607..2c79549a0d 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/state/OmnipodDashPodStateManagerImpl.kt @@ -23,6 +23,8 @@ import io.reactivex.Single import java.io.Serializable import java.time.Duration import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset import java.time.ZonedDateTime import java.util.* import javax.inject.Inject @@ -114,25 +116,35 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( podState.successfulConnections = value } - override var timeZone: TimeZone - get() = TimeZone.getTimeZone(podState.timeZone) - set(tz) { - podState.timeZone = tz.toZoneId().normalized().id - store() - } + override val successfulConnectionAttemptsAfterRetries: Int + @Synchronized + get() = podState.successfulConnectionAttemptsAfterRetries + + @Synchronized + override fun incrementSuccessfulConnectionAttemptsAfterRetries() { + podState.successfulConnectionAttemptsAfterRetries++ + } + + override val failedConnectionsAfterRetries: Int + @Synchronized + get() = podState.failedConnectionsAfterRetries + + override fun incrementFailedConnectionsAfterRetries() { + podState.failedConnectionsAfterRetries++ + } + + override val timeZoneId: String? + get() = podState.timeZone override val sameTimeZone: Boolean get() { val now = System.currentTimeMillis() val currentTimezone = TimeZone.getDefault() val currentOffset = currentTimezone.getOffset(now) - val podOffset = timeZone.getOffset(now) + val podOffset = podState.timeZoneOffset logger.debug( LTag.PUMPCOMM, - "sameTimeZone currentTimezone=${currentTimezone.getDisplayName( - true, - TimeZone.SHORT - )} " + + "sameTimeZone " + "currentOffset=$currentOffset " + "podOffset=$podOffset" ) @@ -219,8 +231,9 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( get() { val minutesSinceActivation = podState.minutesSinceActivation val activationTime = podState.activationTime - if ((activationTime != null) && (minutesSinceActivation != null)) { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(activationTime), timeZone.toZoneId()) + val timeZoneOffset = podState.timeZoneOffset + if ((activationTime != null) && (minutesSinceActivation != null) && (timeZoneOffset != null)) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(activationTime), ZoneId.ofOffset("", ZoneOffset.ofTotalSeconds(timeZoneOffset / 1000))) .plusMinutes(minutesSinceActivation.toLong()) .plus(Duration.ofMillis(System.currentTimeMillis() - lastUpdatedSystem)) } @@ -229,9 +242,25 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( override val timeDrift: Duration? get() { - return Duration.between(ZonedDateTime.now(), time) + return time?.let { + return Duration.between(ZonedDateTime.now(), it) + } ?: null } + override val timeZoneUpdated: Long? + get() { + return podState.timeZoneUpdated + } + + override fun updateTimeZone() { + val timeZone = TimeZone.getDefault() + val now = System.currentTimeMillis() + + podState.timeZoneOffset = timeZone.getOffset(now) + podState.timeZone = timeZone.id + podState.timeZoneUpdated = now + } + override val expiry: ZonedDateTime? get() { val podLifeInHours = podLifeInHours @@ -627,13 +656,10 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( } override fun connectionSuccessRatio(): Float { - if (connectionAttempts == 0) { + if (failedConnectionsAfterRetries + successfulConnectionAttemptsAfterRetries == 0) { return 0.0F - } else if (connectionAttempts <= successfulConnections) { - // Prevent bogus quality > 1 during initialisation - return 1.0F } - return successfulConnections.toFloat() / connectionAttempts.toFloat() + return successfulConnectionAttemptsAfterRetries.toFloat() / (successfulConnectionAttemptsAfterRetries + failedConnectionsAfterRetries) } override fun reset() { @@ -675,6 +701,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED var connectionAttempts = 0 var successfulConnections = 0 + var successfulConnectionAttemptsAfterRetries = 0 + var failedConnectionsAfterRetries = 0 var messageSequenceNumber: Short = 0 var sequenceNumberOfLastProgrammingCommand: Short? = null var activationTime: Long? = null @@ -682,8 +710,9 @@ class OmnipodDashPodStateManagerImpl @Inject constructor( var bluetoothAddress: String? = null var ltk: ByteArray? = null var eapAkaSequenceNumber: Long = 1 - var bolusPulsesRemaining: Short = 0 - var timeZone: String = "" // TimeZone ID (e.g. "Europe/Amsterdam") + var timeZone: String? = null // TimeZone ID (e.g. "Europe/Amsterdam") + var timeZoneOffset: Int? = null + var timeZoneUpdated: Long? = null var alarmSynced: Boolean = false var bleVersion: SoftwareVersion? = null diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt index 4703b82d0c..5c2902ab91 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/ui/OmnipodDashOverviewFragment.kt @@ -50,6 +50,7 @@ import org.apache.commons.lang3.StringUtils import java.time.Duration import java.time.ZonedDateTime import java.util.* +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.collections.ArrayList @@ -213,6 +214,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() { disposables += rxBus .toObservable(EventPumpStatusChanged::class.java) .observeOn(aapsSchedulers.main) + .delay(30, TimeUnit.MILLISECONDS, aapsSchedulers.main) .subscribe( { updateBluetoothConnectionStatus(it) @@ -254,17 +256,18 @@ class OmnipodDashOverviewFragment : DaggerFragment() { ?: PLACEHOLDER val connectionSuccessPercentage = podStateManager.connectionSuccessRatio() * 100 + val connectionAttempts = podStateManager.failedConnectionsAfterRetries + podStateManager.successfulConnectionAttemptsAfterRetries val successPercentageString = String.format("%.2f %%", connectionSuccessPercentage) val quality = - "${podStateManager.successfulConnections}/${podStateManager.connectionAttempts} :: $successPercentageString" + "${podStateManager.successfulConnectionAttemptsAfterRetries}/$connectionAttempts :: $successPercentageString" bluetoothStatusBinding.omnipodDashBluetoothConnectionQuality.text = quality val connectionStatsColor = when { - connectionSuccessPercentage > 90 -> - Color.WHITE - connectionSuccessPercentage > 60 -> + connectionSuccessPercentage < 70 && podStateManager.successfulConnectionAttemptsAfterRetries > 50 -> + Color.RED + connectionSuccessPercentage < 90 && podStateManager.successfulConnectionAttemptsAfterRetries > 50 -> Color.YELLOW else -> - Color.RED + Color.WHITE } bluetoothStatusBinding.omnipodDashBluetoothConnectionQuality.setTextColor(connectionStatsColor) bluetoothStatusBinding.omnipodDashDeliveryStatus.text = podStateManager.deliveryStatus?.let { @@ -303,12 +306,20 @@ class OmnipodDashOverviewFragment : DaggerFragment() { podStateManager.bluetoothVersion.toString() ) - // Update time on Pod + val timeZone = podStateManager.timeZoneId?.let { timeZoneId -> + podStateManager.timeZoneUpdated?.let { timeZoneUpdated -> + val tz = TimeZone.getTimeZone(timeZoneId) + val inDST = tz.inDaylightTime(Date(timeZoneUpdated)) + val locale = resources.configuration.locales.get(0) + tz.getDisplayName(inDST, TimeZone.SHORT, locale) + } ?: PLACEHOLDER + } ?: PLACEHOLDER + podInfoBinding.timeOnPod.text = podStateManager.time?.let { rh.gs( R.string.omnipod_common_time_with_timezone, dateUtil.dateAndTimeString(it.toEpochSecond() * 1000), - podStateManager.timeZone.getDisplayName(true, TimeZone.SHORT) + timeZone ) } ?: PLACEHOLDER diff --git a/omnipod-dash/src/main/res/values/strings.xml b/omnipod-dash/src/main/res/values/strings.xml index 1b6f4dbb45..607f6ebdc5 100644 --- a/omnipod-dash/src/main/res/values/strings.xml +++ b/omnipod-dash/src/main/res/values/strings.xml @@ -46,4 +46,5 @@ Unknown state for the command Rate: %1$.2f U, duration: %2$d minutes %1$.2f U + Delivering %1$.2f U diff --git a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/driver/manager/ErosPodStateManager.java b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/driver/manager/ErosPodStateManager.java index e066f5e1a9..1394c2f8b4 100644 --- a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/driver/manager/ErosPodStateManager.java +++ b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/driver/manager/ErosPodStateManager.java @@ -50,7 +50,7 @@ public abstract class ErosPodStateManager { } public final void discardState() { - this.podState = null; + this.podState = new PodState(this.podState.address); storePodState(); } diff --git a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInitializePodViewModel.kt b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInitializePodViewModel.kt index a6f3c69799..b2a76ed05e 100644 --- a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInitializePodViewModel.kt +++ b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInitializePodViewModel.kt @@ -27,7 +27,7 @@ class ErosInitializePodViewModel @Inject constructor( override fun isPodDeactivatable(): Boolean = podStateManager.activationProgress.isAtLeast(ActivationProgress.PAIRING_COMPLETED) - override fun doExecuteAction(): Single = Single.just(aapsOmnipodManager.initializePod()) + override fun doExecuteAction(): Single = Single.fromCallable { aapsOmnipodManager.initializePod() } @StringRes override fun getTitleId(): Int = R.string.omnipod_common_pod_activation_wizard_initialize_pod_title diff --git a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInsertCannulaViewModel.kt b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInsertCannulaViewModel.kt index b3c071b294..9c556a7fe8 100644 --- a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInsertCannulaViewModel.kt +++ b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/ui/wizard/activation/viewmodel/action/ErosInsertCannulaViewModel.kt @@ -29,7 +29,7 @@ class ErosInsertCannulaViewModel @Inject constructor( override fun isPodDeactivatable(): Boolean = podStateManager.activationProgress.isAtLeast(ActivationProgress.PAIRING_COMPLETED) - override fun doExecuteAction(): Single = Single.just(aapsOmnipodManager.insertCannula(profileFunction.getProfile())) + override fun doExecuteAction(): Single = Single.fromCallable { aapsOmnipodManager.insertCannula(profileFunction.getProfile()) } @StringRes override fun getTitleId(): Int = R.string.omnipod_common_pod_activation_wizard_insert_cannula_title diff --git a/pump-common/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.kt b/pump-common/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.kt index daaec647ce..5960258c71 100644 --- a/pump-common/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.kt +++ b/pump-common/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.kt @@ -323,6 +323,7 @@ abstract class PumpPluginAbstract protected constructor( return ret } + @Synchronized override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { return try { if (detailedBolusInfo.insulin == 0.0 && detailedBolusInfo.carbs == 0.0) {