From 965a0cafaa067bdfbb2ceacda65682b83eddff66 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 23 May 2021 20:47:36 +0200 Subject: [PATCH 01/10] Dana BLE5 comm --- .../androidaps/danars/services/BLEComm.kt | 86 ++++++------------ .../jniLibs/arm64-v8a/libBleEncryption.so | Bin 22776 -> 22776 bytes .../jniLibs/armeabi-v7a/libBleEncryption.so | Bin 26548 -> 26548 bytes .../src/main/jniLibs/x86/libBleEncryption.so | Bin 22396 -> 22432 bytes .../main/jniLibs/x86_64/libBleEncryption.so | Bin 23048 -> 23056 bytes 5 files changed, 28 insertions(+), 58 deletions(-) diff --git a/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt b/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt index ec82af97f7..1e0177af44 100644 --- a/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt +++ b/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt @@ -62,9 +62,6 @@ class BLEComm @Inject internal constructor( private const val PACKET_START_BYTE = 0xA5.toByte() private const val PACKET_END_BYTE = 0x5A.toByte() - - private const val BLE5_PACKET_START_BYTE = 0x73.toByte() - private const val BLE5_PACKET_END_BYTE = 0xBF.toByte() } private var scheduledDisconnection: ScheduledFuture<*>? = null @@ -342,7 +339,7 @@ class BLEComm @Inject internal constructor( @kotlin.ExperimentalStdlibApi private fun readDataParsing(receivedData: ByteArray) { - //aapsLogger.debug(LTag.PUMPBTCOMM, "readDataParsing") + //aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< readDataParsing " + DanaRS_Packet.toHexString(receivedData)) var startSignatureFound = false var packetIsValid = false var isProcessing: Boolean @@ -350,13 +347,15 @@ class BLEComm @Inject internal constructor( var inputBuffer: ByteArray? = null // decrypt 2nd level after successful connection - val incomingBuffer = if (encryption == EncryptionType.ENCRYPTION_RSv3 && isConnected) - bleEncryption.decryptSecondLevelPacket(receivedData).also { - encryptedDataRead = true - sp.putLong(R.string.key_rs_last_clear_key_request, 0L) - } - else receivedData + val incomingBuffer = + if (isConnected && (encryption == EncryptionType.ENCRYPTION_RSv3 || encryption == EncryptionType.ENCRYPTION_BLE5)) + bleEncryption.decryptSecondLevelPacket(receivedData).also { + encryptedDataRead = true + sp.putLong(R.string.key_rs_last_clear_key_request, 0L) + } + else receivedData addToReadBuffer(incomingBuffer) + //aapsLogger.debug(LTag.PUMPBTCOMM, "incomingBuffer " + DanaRS_Packet.toHexString(incomingBuffer)) while (isProcessing) { var length = 0 @@ -386,58 +385,29 @@ class BLEComm @Inject internal constructor( // Verify packed end [5A 5A] if (readBuffer[length + 5] == PACKET_END_BYTE && readBuffer[length + 6] == PACKET_END_BYTE) { packetIsValid = true + } else if (readBuffer[length + 5] == readBuffer[length + 6]) { + // BLE5 + packetIsValid = true + readBuffer[length + 5] = PACKET_END_BYTE + readBuffer[length + 6] = PACKET_END_BYTE } } - // packet can be BLE5 encrypted too - if (!packetIsValid && encryption == EncryptionType.ENCRYPTION_BLE5) { - var startIndex: Int = -1 - // Find encrypted packet start [73 73] - if (bufferLength >= 6) { - for (idxStartByte in 0 until bufferLength - 2) { - if (readBuffer[idxStartByte] == BLE5_PACKET_START_BYTE && readBuffer[idxStartByte + 1] == BLE5_PACKET_START_BYTE) { - if (idxStartByte > 0) { - // if buffer doesn't start with signature remove the leading trash - aapsLogger.debug(LTag.PUMPBTCOMM, "Shifting the input buffer by $idxStartByte bytes") - System.arraycopy(readBuffer, idxStartByte, readBuffer, 0, bufferLength - idxStartByte) - bufferLength -= idxStartByte - } - startIndex = idxStartByte - break - } - } - } - // 73 73 ENCRYPTED CONTENT BF BF - if (startIndex != -1) { - for (idxEndByte in 5..bufferLength - 2) { - if (readBuffer[idxEndByte] == BLE5_PACKET_END_BYTE && readBuffer[idxEndByte + 1] == BLE5_PACKET_END_BYTE) { - length = idxEndByte - startIndex + 2 - 7 - packetIsValid = true - encryptedDataRead = true - break - } - } - } - } - if (packetIsValid) { - inputBuffer = ByteArray(length + 7) - // copy packet to input buffer - System.arraycopy(readBuffer, 0, inputBuffer, 0, length + 7) - // Cut off the message from readBuffer - try { - System.arraycopy(readBuffer, length + 7, readBuffer, 0, bufferLength - (length + 7)) - } catch (e: Exception) { - aapsLogger.error("length: " + length + "bufferLength: " + bufferLength) - throw e - } - bufferLength -= length + 7 - // now we have encrypted packet in inputBuffer - } - } - if (packetIsValid && encryptedDataRead && encryption == EncryptionType.ENCRYPTION_BLE5) { - inputBuffer = bleEncryption.decryptSecondLevelPacket(inputBuffer) } if (packetIsValid) { - // aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< PROCESSING: " + DanaRS_Packet.toHexString(inputBuffer)) + inputBuffer = ByteArray(length + 7) + // copy packet to input buffer + System.arraycopy(readBuffer, 0, inputBuffer, 0, length + 7) + // Cut off the message from readBuffer + try { + System.arraycopy(readBuffer, length + 7, readBuffer, 0, bufferLength - (length + 7)) + } catch (e: Exception) { + aapsLogger.error("length: " + length + "bufferLength: " + bufferLength) + throw e + } + bufferLength -= length + 7 + // now we have encrypted packet in inputBuffer + + //aapsLogger.debug(LTag.PUMPBTCOMM, "<<<<< PROCESSING: " + DanaRS_Packet.toHexString(inputBuffer)) // decrypt the packet bleEncryption.getDecryptedPacket(inputBuffer)?.let { decryptedBuffer -> if (decryptedBuffer[0] == BleEncryption.DANAR_PACKET__TYPE_ENCRYPTION_RESPONSE.toByte()) { diff --git a/danars/src/main/jniLibs/arm64-v8a/libBleEncryption.so b/danars/src/main/jniLibs/arm64-v8a/libBleEncryption.so index ce5fff9e71687fb48223208e7022670d9f54989d..7c0c49b7e8aa0a11be777b6cad2624200c37989d 100644 GIT binary patch delta 5838 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 literal 22396 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 From 92f0487db8c9e018e34fca03b93327686aed3a97 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 23 May 2021 21:05:21 +0200 Subject: [PATCH 02/10] fix displayed BG --- .../info/nightscout/androidaps/database/daos/GlucoseValueDao.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt index 27cde3dbe4..bda5c63b2f 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt @@ -16,7 +16,7 @@ internal interface GlucoseValueDao : TraceableDao { @Query("DELETE FROM $TABLE_GLUCOSE_VALUES") override fun deleteAllEntries() - @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1") + @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE isValid = 1 AND referenceId IS NULL ORDER BY id DESC limit 1") fun getLast(): Maybe @Query("SELECT id FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1") From 966f7b35f27ba9bdb965caf0842aa9f10e1b20ac Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 23 May 2021 21:38:30 +0200 Subject: [PATCH 03/10] move treatments to upper right menu --- app/src/main/AndroidManifest.xml | 1 + .../nightscout/androidaps/MainActivity.kt | 11 +-- .../TreatmentsActivity.kt} | 82 ++++--------------- .../fragments/TreatmentsBolusCarbsFragment.kt | 2 +- .../fragments/TreatmentsCareportalFragment.kt | 4 +- .../TreatmentsExtendedBolusesFragment.kt | 4 +- .../TreatmentsProfileSwitchFragment.kt | 4 +- .../fragments/TreatmentsTempTargetFragment.kt | 4 +- .../TreatmentsTemporaryBasalsFragment.kt | 4 +- .../fragments/TreatmentsUserEntryFragment.kt | 2 +- .../dependencyInjection/ActivitiesModule.kt | 1 + .../dependencyInjection/FragmentsModule.kt | 4 +- .../plugins/treatments/TreatmentsPlugin.java | 1 - .../main/res/layout/localprofile_fragment.xml | 2 +- .../treatments_bolus_carbs_fragment.xml | 2 +- .../layout/treatments_careportal_fragment.xml | 2 +- .../treatments_extendedbolus_fragment.xml | 2 +- .../main/res/layout/treatments_fragment.xml | 2 +- .../treatments_profileswitch_fragment.xml | 2 +- .../layout/treatments_tempbasals_fragment.xml | 2 +- .../layout/treatments_temptarget_fragment.xml | 2 +- .../layout/treatments_user_entry_fragment.xml | 2 +- app/src/main/res/menu/menu_main.xml | 4 + .../fragments => utils/ui}/ProfileGraph.kt | 5 +- .../main/res/layout/dialog_profileviewer.xml | 2 +- .../androidaps/danars/services/BLEComm.kt | 2 +- 26 files changed, 53 insertions(+), 102 deletions(-) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments/TreatmentsFragment.kt => activities/TreatmentsActivity.kt} (53%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsBolusCarbsFragment.kt (99%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsCareportalFragment.kt (98%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsExtendedBolusesFragment.kt (97%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsProfileSwitchFragment.kt (98%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsTempTargetFragment.kt (98%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsTemporaryBasalsFragment.kt (98%) rename app/src/main/java/info/nightscout/androidaps/{plugins/treatments => activities}/fragments/TreatmentsUserEntryFragment.kt (99%) rename core/src/main/java/info/nightscout/androidaps/{plugins/treatments/fragments => utils/ui}/ProfileGraph.kt (96%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18750bfda8..e837d2ce87 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,6 +69,7 @@ + diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt index 4fa4fbe4b9..cde7563581 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt @@ -27,11 +27,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.fonts.FontAwesomeModule import dev.doubledot.doki.ui.DokiActivity -import info.nightscout.androidaps.activities.NoSplashAppCompatActivity -import info.nightscout.androidaps.activities.PreferencesActivity -import info.nightscout.androidaps.activities.ProfileHelperActivity -import info.nightscout.androidaps.activities.SingleFragmentActivity -import info.nightscout.androidaps.activities.StatsActivity +import info.nightscout.androidaps.activities.* import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.ActivityMainBinding @@ -291,6 +287,11 @@ class MainActivity : NoSplashAppCompatActivity() { return true } + R.id.nav_treatments -> { + startActivity(Intent(this, TreatmentsActivity::class.java)) + return true + } + R.id.nav_setupwizard -> { protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, { startActivity(Intent(this, SetupWizardActivity::class.java)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt similarity index 53% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt index 9de459f622..4cd4ab559a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt @@ -1,53 +1,28 @@ -package info.nightscout.androidaps.plugins.treatments +package info.nightscout.androidaps.activities import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction -import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding -import info.nightscout.androidaps.events.EventExtendedBolusChange -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.IobCobCalculator -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.* -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.plusAssign +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.utils.buildHelper.BuildHelper import javax.inject.Inject -class TreatmentsFragment : DaggerFragment() { +class TreatmentsActivity : NoSplashAppCompatActivity() { - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var iobCobCalculator: IobCobCalculator - @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var buildHelper: BuildHelper - @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var activePlugin: ActivePlugin - private val disposable = CompositeDisposable() + private lateinit var binding: TreatmentsFragmentBinding - private var _binding: TreatmentsFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = - TreatmentsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = TreatmentsFragmentBinding.inflate(layoutInflater) + setContentView(binding.root) binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility() binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility() @@ -84,33 +59,10 @@ class TreatmentsFragment : DaggerFragment() { setBackgroundColorOnSelected(binding.treatments) } - @Synchronized - override fun onResume() { - super.onResume() - disposable += rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateGui() }, fabricPrivacy::logException) - updateGui() - } - - @Synchronized - override fun onPause() { - super.onPause() - disposable.clear() - } - - @Synchronized - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - private fun setFragment(selectedFragment: Fragment) { - childFragmentManager.beginTransaction() - .replace(R.id.fragment_container, selectedFragment) // f2_container is your FrameLayout container - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .addToBackStack(null) + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, selectedFragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit() } @@ -125,8 +77,4 @@ class TreatmentsFragment : DaggerFragment() { selected.setBackgroundColor(resourceHelper.gc(R.color.tabBgColorSelected)) } - private fun updateGui() { - if (_binding == null) return - binding.extendedBoluses.visibility = (activePlugin.activePump.pumpDescription.isExtendedBolusCapable || iobCobCalculator.getExtendedBolus(dateUtil.now()) != null).toVisibility() - } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt similarity index 99% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt index 2ba8978ea8..4b5dc48e2a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt similarity index 98% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt index ce75d4dc3c..f3eb9e551f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle @@ -25,7 +25,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt similarity index 97% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt index 36b0a8a69a..d5dfec5f6c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.content.DialogInterface @@ -30,7 +30,7 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt similarity index 98% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt index 796c811f0f..151b37234f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle @@ -30,7 +30,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHi import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt similarity index 98% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt index d603263fc1..16e8a5ae32 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.content.DialogInterface @@ -29,7 +29,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt similarity index 98% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt index c9bda0e311..590fef204f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.content.DialogInterface import android.graphics.Paint @@ -36,7 +36,7 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt similarity index 99% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt index 9bbc46d63b..b0936580ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt index 95e0888b79..fa8bfa0598 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt @@ -15,6 +15,7 @@ import info.nightscout.androidaps.setupwizard.SetupWizardActivity @Suppress("unused") abstract class ActivitiesModule { + @ContributesAndroidInjector abstract fun contributesTreatmentsActivity(): TreatmentsActivity @ContributesAndroidInjector abstract fun contributesHistoryBrowseActivity(): HistoryBrowseActivity @ContributesAndroidInjector abstract fun contributesLogSettingActivity(): LogSettingActivity @ContributesAndroidInjector abstract fun contributeMainActivity(): MainActivity diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt index 60eb3ace15..84e4ef80fb 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt @@ -31,8 +31,7 @@ import info.nightscout.androidaps.plugins.insulin.InsulinFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment import info.nightscout.androidaps.plugins.source.BGSourceFragment -import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment -import info.nightscout.androidaps.plugins.treatments.fragments.* +import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.utils.protection.PasswordCheck @Module @@ -61,7 +60,6 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment - @ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusCarbsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index d994c0475f..04345c3c6d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -67,7 +67,6 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface ) { super(new PluginDescription() .mainType(PluginType.TREATMENT) - .fragmentClass(TreatmentsFragment.class.getName()) .pluginIcon(R.drawable.ic_treatments) .pluginName(R.string.treatments) .shortName(R.string.treatments_shortname) diff --git a/app/src/main/res/layout/localprofile_fragment.xml b/app/src/main/res/layout/localprofile_fragment.xml index 920d10f4ee..e66a0c9c62 100644 --- a/app/src/main/res/layout/localprofile_fragment.xml +++ b/app/src/main/res/layout/localprofile_fragment.xml @@ -239,7 +239,7 @@ android:layout_marginBottom="10dp" android:orientation="vertical" /> - + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsBolusCarbsFragment"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment"> + tools:context="info.nightscout.androidaps.activities.TreatmentsActivity"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment"> + tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsUserEntryFragment"> + - Date: Tue, 25 May 2021 17:42:21 +0200 Subject: [PATCH 04/10] HistoryBrowseActivity --- app/src/main/AndroidManifest.xml | 2 +- .../nightscout/androidaps/MainActivity.kt | 2 +- .../activities/HistoryBrowseActivity.kt | 391 +++++++++ .../dependencyInjection/ActivitiesModule.kt | 2 +- .../androidaps/dialogs/CarbsDialog.kt | 2 - .../historyBrowser/HistoryBrowseActivity.kt | 379 -------- .../IobCobCalculatorPluginHistory.kt | 42 - .../general/actions/ActionsFragment.kt | 2 +- .../plugins/general/overview/OverviewData.kt | 557 +++++++++++- .../general/overview/OverviewFragment.kt | 11 +- .../general/overview/OverviewPlugin.kt | 820 ++++-------------- .../general/overview/graphData/GraphData.kt | 8 +- .../iob/iobCobCalculator/IobCobOref1Thread.kt | 4 +- .../iob/iobCobCalculator/IobCobThread.kt | 4 +- .../events/EventIobCalculationProgress.kt | 2 +- .../res/layout/activity_historybrowse.xml | 4 +- .../interfaces/ConstraintsCheckerTest.kt | 4 +- .../plugins/aps/loop/LoopPluginTest.kt | 2 - .../androidaps/queue/CommandQueueTest.kt | 3 +- .../androidaps/queue/QueueThreadTest.kt | 3 +- .../utils/wizard/BolusWizardTest.kt | 3 - 21 files changed, 1104 insertions(+), 1143 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt delete mode 100644 app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt delete mode 100644 app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e837d2ce87..77b68adf4a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,7 @@ - + () + private val secondaryGraphsLabel = ArrayList() + + private var axisWidth: Int = 0 + private var rangeToDisplay = 24 // for graph +// private var start: Long = 0 + + private lateinit var iobCobCalculator: IobCobCalculatorPlugin + private lateinit var overviewData: OverviewData + + private lateinit var binding: ActivityHistorybrowseBinding + private var destroyed = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityHistorybrowseBinding.inflate(layoutInflater) + setContentView(binding.root) + + // We don't want to use injected singletons but own instance working on top of different data + iobCobCalculator = IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository) + overviewData = OverviewData(injector, aapsLogger, resourceHelper, dateUtil, sp, activePlugin, defaultValueHelper, profileFunction, config, loopPlugin, nsDeviceStatus, repository, overviewMenus, iobCobCalculator, translator) + + binding.left.setOnClickListener { + setTime(overviewData.fromTime - T.hours(rangeToDisplay.toLong()).msecs()) + loadAll("onClickLeft") + } + binding.right.setOnClickListener { + setTime(overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs()) + loadAll("onClickRight") + } + binding.end.setOnClickListener { + setTime(dateUtil.now()) + loadAll("onClickEnd") + } + binding.zoom.setOnClickListener { + rangeToDisplay += 6 + rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay + setTime(overviewData.fromTime) + loadAll("rangeChange") + } + binding.zoom.setOnLongClickListener { + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = overviewData.fromTime + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + setTime(calendar.timeInMillis) + } + loadAll("onLongClickZoom") + true + } + + // create an OnDateSetListener + val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = overviewData.fromTime + calendar[Calendar.YEAR] = year + calendar[Calendar.MONTH] = monthOfYear + calendar[Calendar.DAY_OF_MONTH] = dayOfMonth + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + setTime(calendar.timeInMillis) + binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime) + } + loadAll("onClickDate") + } + + binding.date.setOnClickListener { + val cal = Calendar.getInstance() + cal.timeInMillis = overviewData.fromTime + DatePickerDialog(this, dateSetListener, + cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH), + cal.get(Calendar.DAY_OF_MONTH) + ).show() + } + + val dm = DisplayMetrics() + windowManager?.defaultDisplay?.getMetrics(dm) + + axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80 + binding.bgGraph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) + binding.bgGraph.gridLabelRenderer?.reloadStyles() + binding.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth + + overviewMenus.setupChartMenu(binding.chartMenuButton) + prepareGraphsIfNeeded(overviewMenus.setting.size) + savedInstanceState?.let { bundle -> + rangeToDisplay = bundle.getInt("rangeToDisplay", 0) + overviewData.fromTime = bundle.getLong("start", 0) + overviewData.toTime = bundle.getLong("end", 0) + } + } + + public override fun onPause() { + super.onPause() + disposable.clear() + iobCobCalculator.stopCalculation("onPause") + } + + @Synchronized + override fun onDestroy() { + destroyed = true + super.onDestroy() + } + + public override fun onResume() { + super.onResume() + disposable.add(rxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + // catch only events from iobCobCalculator + if (it.cause is EventCustomCalculationFinished) + refreshLoop("EventAutosensCalculationFinished") + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventIobCalculationProgress::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ + if (it.cause is EventCustomCalculationFinished) + binding.overviewIobcalculationprogess.text = it.progress + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventRefreshOverview::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI("EventRefreshOverview") }, fabricPrivacy::logException) + ) + disposable += rxBus + .toObservable(EventBucketedDataCreated::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + overviewData.prepareBucketedData("EventBucketedDataCreated") + overviewData.prepareBgData("EventBucketedDataCreated") + rxBus.send(EventRefreshOverview("EventBucketedDataCreated")) + }, fabricPrivacy::logException) + + if (overviewData.fromTime == 0L) { + // set start of current day + setTime(dateUtil.now()) + loadAll("onResume") + } else { + updateGUI("onResume") + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt("rangeToDisplay", rangeToDisplay) + outState.putLong("start", overviewData.fromTime) + outState.putLong("end", overviewData.toTime) + + } + + private fun prepareGraphsIfNeeded(numOfGraphs: Int) { + if (numOfGraphs != secondaryGraphs.size - 1) { + //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") + // rebuild needed + secondaryGraphs.clear() + secondaryGraphsLabel.clear() + binding.iobGraph.removeAllViews() + for (i in 1 until numOfGraphs) { + val relativeLayout = RelativeLayout(this) + relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val graph = GraphView(this) + graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } + graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) + graph.gridLabelRenderer?.reloadStyles() + graph.gridLabelRenderer?.isHorizontalLabelsVisible = false + graph.gridLabelRenderer?.labelVerticalWidth = axisWidth + graph.gridLabelRenderer?.numVerticalLabels = 3 + graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray + relativeLayout.addView(graph) + + val label = TextView(this) + val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) + label.layoutParams = layoutParams + relativeLayout.addView(label) + secondaryGraphsLabel.add(label) + + binding.iobGraph.addView(relativeLayout) + secondaryGraphs.add(graph) + } + } + } + + @Suppress("SameParameterValue") + private fun loadAll(from: String) { + Thread { + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) + rxBus.send(EventRefreshOverview(from)) + aapsLogger.debug(LTag.UI, "loadAll $from finished") + runCalculation(from) + }.start() + } + + private fun setTime(start: Long) { + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = start + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + overviewData.fromTime = calendar.timeInMillis + overviewData.toTime = overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs() + overviewData.endTime = overviewData.toTime + } + } + + private fun runCalculation(from: String) { + Thread { + iobCobCalculator.stopCalculation(from) + iobCobCalculator.stopCalculationTrigger = false + iobCobCalculator.runCalculation(from, overviewData.toTime, bgDataReload = true, limitDataToOldestAvailable = false, cause = EventCustomCalculationFinished()) + }.start() + } + + @Volatile + var runningRefresh = false + private fun refreshLoop(from: String) { + if (runningRefresh) return + runningRefresh = true + overviewData.prepareIobAutosensData(from) + rxBus.send(EventRefreshOverview(from)) + aapsLogger.debug(LTag.UI, "refreshLoop finished") + runningRefresh = false + } + + @Suppress("UNUSED_PARAMETER") + @SuppressLint("SetTextI18n") + fun updateGUI(from: String) { + aapsLogger.debug(LTag.UI, "updateGui $from") + + binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime) + binding.zoom.text = rangeToDisplay.toString() + + val pump = activePlugin.activePump + val graphData = GraphData(injector, binding.bgGraph, overviewData) + val menuChartSettings = overviewMenus.setting + graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine()) + graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) + if (buildHelper.isDev()) graphData.addBucketedData() + graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) + graphData.addActivity(0.8) + if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) + graphData.addBasals() + graphData.addTargetLine() + graphData.addNowLine(dateUtil.now()) + + // set manual x bounds to have nice steps + graphData.setNumVerticalLabels() + graphData.formatAxis(overviewData.fromTime, overviewData.endTime) + + graphData.performUpdate() + + // 2nd graphs + prepareGraphsIfNeeded(menuChartSettings.size) + val secondaryGraphsData: ArrayList = ArrayList() + + val now = System.currentTimeMillis() + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData) + var useABSForScale = false + var useIobForScale = false + var useCobForScale = false + var useDevForScale = false + var useRatioForScale = false + var useDSForScale = false + var useBGIForScale = false + when { + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + } + val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] + + if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0) + + // set manual x bounds to have nice steps + secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) + secondGraphData.addNowLine(now) + secondaryGraphsData.add(secondGraphData) + } + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) + secondaryGraphs[g].visibility = ( + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] + ).toVisibility() + secondaryGraphsData[g].performUpdate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt index fa8bfa0598..08e25f456f 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt @@ -4,7 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.MainActivity import info.nightscout.androidaps.activities.* -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.activities.HistoryBrowseActivity import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt index 9d4e34a89b..214847aff9 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt @@ -27,7 +27,6 @@ import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.utils.* @@ -47,7 +46,6 @@ class CarbsDialog : DialogFragmentWithDate() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var constraintChecker: ConstraintChecker @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var uel: UserEntryLogger diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt deleted file mode 100644 index 1abda3295f..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt +++ /dev/null @@ -1,379 +0,0 @@ -package info.nightscout.androidaps.historyBrowser - -import android.app.DatePickerDialog -import android.graphics.Color -import android.os.Bundle -import android.util.DisplayMetrics -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.RelativeLayout -import android.widget.TextView -import androidx.lifecycle.lifecycleScope -import com.jjoe64.graphview.GraphView -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.R -import info.nightscout.androidaps.activities.NoSplashAppCompatActivity -import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding -import info.nightscout.androidaps.events.EventAutosensCalculationFinished -import info.nightscout.androidaps.events.EventCustomCalculationFinished -import info.nightscout.androidaps.events.EventRefreshOverview -import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.overview.OverviewMenus -import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.DefaultValueHelper -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.androidaps.utils.sharedPreferences.SP -import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.* -import javax.inject.Inject - -class HistoryBrowseActivity : NoSplashAppCompatActivity() { - - @Inject lateinit var injector: HasAndroidInjector - @Inject lateinit var aapsLogger: AAPSLogger - @Inject lateinit var aapsSchedulers: AapsSchedulers - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var sp: SP - @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var iobCobCalculatorPluginHistory: IobCobCalculatorPluginHistory - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var buildHelper: BuildHelper - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var overviewMenus: OverviewMenus - @Inject lateinit var dateUtil: DateUtil - - private val disposable = CompositeDisposable() - - private val secondaryGraphs = ArrayList() - private val secondaryGraphsLabel = ArrayList() - - private var axisWidth: Int = 0 - private var rangeToDisplay = 24 // for graph - private var start: Long = 0 - - private val graphLock = Object() - - private var eventCustomCalculationFinished = EventCustomCalculationFinished() - - private lateinit var binding: ActivityHistorybrowseBinding - private var destroyed = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityHistorybrowseBinding.inflate(layoutInflater) - setContentView(binding.root) - - binding.left.setOnClickListener { - start -= T.hours(rangeToDisplay.toLong()).msecs() - runCalculation("onClickLeft") - } - binding.right.setOnClickListener { - start += T.hours(rangeToDisplay.toLong()).msecs() - runCalculation("onClickRight") - } - binding.end.setOnClickListener { - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onClickEnd") - } - binding.zoom.setOnClickListener { - rangeToDisplay += 6 - rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay - updateGUI("rangeChange", false) - } - binding.zoom.setOnLongClickListener { - val calendar = Calendar.getInstance() - calendar.timeInMillis = start - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onLongClickZoom") - true - } - - // create an OnDateSetListener - val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> - val cal = Calendar.getInstance() - cal.timeInMillis = start - cal[Calendar.YEAR] = year - cal[Calendar.MONTH] = monthOfYear - cal[Calendar.DAY_OF_MONTH] = dayOfMonth - cal[Calendar.MILLISECOND] = 0 - cal[Calendar.SECOND] = 0 - cal[Calendar.MINUTE] = 0 - cal[Calendar.HOUR_OF_DAY] = 0 - start = cal.timeInMillis - binding.date.text = dateUtil.dateAndTimeString(start) - runCalculation("onClickDate") - } - - binding.date.setOnClickListener { - val cal = Calendar.getInstance() - cal.timeInMillis = start - DatePickerDialog(this, dateSetListener, - cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH) - ).show() - } - - val dm = DisplayMetrics() - windowManager?.defaultDisplay?.getMetrics(dm) - - axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80 - binding.bggraph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) - binding.bggraph.gridLabelRenderer?.reloadStyles() - binding.bggraph.gridLabelRenderer?.labelVerticalWidth = axisWidth - - overviewMenus.setupChartMenu(binding.chartMenuButton) - prepareGraphsIfNeeded(overviewMenus.setting.size) - savedInstanceState?.let { bundle -> - rangeToDisplay = bundle.getInt("rangeToDisplay", 0) - start = bundle.getLong("start", 0) - } - - } - - public override fun onPause() { - super.onPause() - disposable.clear() - iobCobCalculatorPluginHistory.stopCalculation("onPause") - } - - @Synchronized - override fun onDestroy() { - destroyed = true - super.onDestroy() - } - - public override fun onResume() { - super.onResume() - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - // catch only events from iobCobCalculatorPluginHistory - if (it.cause is EventCustomCalculationFinished) { - updateGUI("EventAutosensCalculationFinished", bgOnly = false) - } - }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventIobCalculationProgress::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ binding.overviewIobcalculationprogess.text = it.progress }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ - if (it.now) { - updateGUI("EventRefreshOverview", bgOnly = false) - } - }, fabricPrivacy::logException) - ) - if (start == 0L) { - // set start of current day - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onResume") - } else { - updateGUI("onResume", bgOnly = false) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putInt("rangeToDisplay", rangeToDisplay) - outState.putLong("start", start) - - } - - private fun prepareGraphsIfNeeded(numOfGraphs: Int) { - synchronized(graphLock) { - if (numOfGraphs != secondaryGraphs.size - 1) { - //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") - // rebuild needed - secondaryGraphs.clear() - secondaryGraphsLabel.clear() - binding.iobGraph.removeAllViews() - for (i in 1 until numOfGraphs) { - val relativeLayout = RelativeLayout(this) - relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - val graph = GraphView(this) - graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) - graph.gridLabelRenderer?.reloadStyles() - graph.gridLabelRenderer?.isHorizontalLabelsVisible = false - graph.gridLabelRenderer?.labelVerticalWidth = axisWidth - graph.gridLabelRenderer?.numVerticalLabels = 3 - graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray - relativeLayout.addView(graph) - - val label = TextView(this) - val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) - label.layoutParams = layoutParams - relativeLayout.addView(label) - secondaryGraphsLabel.add(label) - - binding.iobGraph.addView(relativeLayout) - secondaryGraphs.add(graph) - } - } - } - } - - private fun runCalculation(from: String) { - lifecycleScope.launch(Dispatchers.Default) { - val end = start + T.hours(rangeToDisplay.toLong()).msecs() - iobCobCalculatorPluginHistory.stopCalculation(from) - iobCobCalculatorPluginHistory.clearCache() - iobCobCalculatorPluginHistory.runCalculation(from, end, bgDataReload = true, limitDataToOldestAvailable = false, cause = eventCustomCalculationFinished) - } - } - - @Synchronized - fun updateGUI(from: String, bgOnly: Boolean) { - val menuChartSettings = overviewMenus.setting - prepareGraphsIfNeeded(menuChartSettings.size) - aapsLogger.debug(LTag.UI, "updateGUI from: $from") - val pump = activePlugin.activePump - val profile = profileFunction.getProfile() - - val lowLine = defaultValueHelper.determineLowLine() - val highLine = defaultValueHelper.determineHighLine() - - lifecycleScope.launch(Dispatchers.Main) { - binding.noprofile.visibility = (profile == null).toVisibility() - profile ?: return@launch - - if (destroyed) return@launch - binding.date.text = dateUtil.dateAndTimeString(start) - binding.zoom.text = rangeToDisplay.toString() - val graphData = GraphData(injector, binding.bggraph) - val secondaryGraphsData: ArrayList = ArrayList() - - // do preparation in different thread - withContext(Dispatchers.Default) { - val fromTime: Long = start + T.secs(100).msecs() - val toTime: Long = start + T.hours(rangeToDisplay.toLong()).msecs() + T.secs(100).msecs() - aapsLogger.debug(LTag.UI, "Period: " + dateUtil.dateAndTimeString(fromTime) + " - " + dateUtil.dateAndTimeString(toTime)) - val pointer = System.currentTimeMillis() - - // **** In range Area **** - graphData.addInRangeArea(fromTime, toTime, lowLine, highLine) - - // **** BG **** -// graphData.addBgReadings(fromTime, toTime, highLine, null) -// if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) - - // add target line -// graphData.addTargetLine(fromTime, toTime, profile, null) - - // **** NOW line **** - graphData.addNowLine(pointer) - - if (!bgOnly) { - // Treatments -// graphData.addTreatments(fromTime, toTime) - if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) -// graphData.addActivity(fromTime, toTime, false, 0.8) - - // add basal data - if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) { -// graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2) - } - // ------------------ 2nd graph - synchronized(graphLock) { - for (g in 0 until secondaryGraphs.size) { - val secondGraphData = GraphData(injector, secondaryGraphs[g]) - var useIobForScale = false - var useCobForScale = false - var useDevForScale = false - var useRatioForScale = false - var useDSForScale = false - var useBGIForScale = false - var useABSForScale = false - when { - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true - } - - val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] - val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] - -// if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) -// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0) - - // set manual x bounds to have nice steps - secondGraphData.formatAxis(fromTime, toTime) - secondGraphData.addNowLine(pointer) - secondaryGraphsData.add(secondGraphData) - } - } - } - - // set manual x bounds to have nice steps - graphData.setNumVerticalLabels() - graphData.formatAxis(fromTime, toTime) - } - // finally enforce drawing of graphs in UI thread - graphData.performUpdate() - if (!bgOnly) - synchronized(graphLock) { - for (g in 0 until secondaryGraphs.size) { - secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) - secondaryGraphs[g].visibility = (!bgOnly && ( - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.ACT.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] - )).toVisibility() - secondaryGraphsData[g].performUpdate() - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt deleted file mode 100644 index 1dfc35f70a..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt +++ /dev/null @@ -1,42 +0,0 @@ -package info.nightscout.androidaps.historyBrowser - -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.androidaps.utils.sharedPreferences.SP -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class IobCobCalculatorPluginHistory @Inject constructor( - injector: HasAndroidInjector, - aapsLogger: AAPSLogger, - aapsSchedulers: AapsSchedulers, - rxBus: RxBusWrapper, - sp: SP, - resourceHelper: ResourceHelper, - profileFunction: ProfileFunction, - activePlugin: ActivePlugin, - sensitivityOref1Plugin: SensitivityOref1Plugin, - sensitivityAAPSPlugin: SensitivityAAPSPlugin, - sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin, - fabricPrivacy: FabricPrivacy, - dateUtil: DateUtil, - repository: AppRepository -) : IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, - activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository) { - - override fun onStart() { // do not attach to rxbus - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt index 1e18a5f54f..8ffbd696ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt @@ -28,7 +28,7 @@ import info.nightscout.androidaps.events.EventTherapyEventChange import info.nightscout.androidaps.extensions.toStringMedium import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.activities.HistoryBrowseActivity import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.Config diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt index d5f64cc96e..026cb6e766 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt @@ -1,46 +1,64 @@ package info.nightscout.androidaps.plugins.general.overview +import android.graphics.DashPathEffect +import android.graphics.Paint import com.jjoe64.graphview.series.BarGraphSeries import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.LineGraphSeries +import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.ValueWrapper +import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.entities.TemporaryBasal import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.extensions.convertedToPercent +import info.nightscout.androidaps.extensions.target import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.valueToUnits -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.Profile -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.DefaultValueHelper -import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP import java.util.* import javax.inject.Inject import javax.inject.Singleton import kotlin.collections.ArrayList +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min @Singleton class OverviewData @Inject constructor( + private val injector: HasAndroidInjector, + private val aapsLogger: AAPSLogger, private val resourceHelper: ResourceHelper, private val dateUtil: DateUtil, private val sp: SP, private val activePlugin: ActivePlugin, private val defaultValueHelper: DefaultValueHelper, - private val profileFunction: ProfileFunction + private val profileFunction: ProfileFunction, + private val config: Config, + private val loopPlugin: LoopPlugin, + private val nsDeviceStatus: NSDeviceStatus, + private val repository: AppRepository, + private val overviewMenus: OverviewMenus, + private val iobCobCalculator: IobCobCalculator, + private val translator: Translator ) { enum class Property { @@ -84,7 +102,7 @@ class OverviewData @Inject constructor( var profileName: String? = null var profileNameWithRemainingTime: String? = null - val profileBackgroudColor: Int + val profileBackgroundColor: Int get() = profile?.let { profile -> if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning) @@ -211,7 +229,7 @@ class OverviewData @Inject constructor( * TEMP TARGET */ - var temporarytarget: TemporaryTarget? = null + var temporaryTarget: TemporaryTarget? = null /* * SENSITIVITY @@ -278,4 +296,515 @@ class OverviewData @Inject constructor( var dsMaxSeries: LineGraphSeries = LineGraphSeries() var dsMinSeries: LineGraphSeries = LineGraphSeries() + @Synchronized + @Suppress("SameParameterValue", "UNUSED_PARAMETER") + fun prepareBgData(from: String) { +// val start = dateUtil.now() + maxBgValue = Double.MIN_VALUE + bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() + val bgListArray: MutableList = java.util.ArrayList() + for (bg in bgReadingsArray) { + if (bg.timestamp < fromTime || bg.timestamp > toTime) continue + if (bg.value > maxBgValue) maxBgValue = bg.value + bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper)) + } + bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) + maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits()) + if (defaultValueHelper.determineHighLine() > maxBgValue) maxBgValue = defaultValueHelper.determineHighLine() + maxBgValue = addUpperChartMargin(maxBgValue) +// profiler.log(LTag.UI, "prepareBgData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + fun preparePredictions(from: String) { +// val start = dateUtil.now() + val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) + val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT + val menuChartSettings = overviewMenus.setting + // align to hours + val calendar = Calendar.getInstance().also { + it.timeInMillis = System.currentTimeMillis() + it[Calendar.MILLISECOND] = 0 + it[Calendar.SECOND] = 0 + it[Calendar.MINUTE] = 0 + it.add(Calendar.HOUR, 1) + } + if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { + var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() + predictionHours = min(2, predictionHours) + predictionHours = max(0, predictionHours) + val hoursToFetch = rangeToDisplay - predictionHours + toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs() + endTime = toTime + T.hours(predictionHours.toLong()).msecs() + } else { + toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs() + endTime = toTime + } + + val bgListArray: MutableList = java.util.ArrayList() + val predictions: MutableList? = apsResult?.predictions + ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) } + ?.toMutableList() + if (predictions != null) { + predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) } + for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) + } + predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) +// profiler.log(LTag.UI, "preparePredictions() $from", start) + } + + @Synchronized + @Suppress("SameParameterValue", "UNUSED_PARAMETER") + fun prepareBucketedData(from: String) { +// val start = dateUtil.now() + val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return + if (bucketedData.isEmpty()) { + aapsLogger.debug("No bucketed data.") + return + } + val bucketedListArray: MutableList = java.util.ArrayList() + for (inMemoryGlucoseValue in bucketedData) { + if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue + bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper)) + } + bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) +// profiler.log(LTag.UI, "prepareBucketedData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + fun prepareBasalData(from: String) { +// val start = dateUtil.now() + maxBasalValueFound = 0.0 + val baseBasalArray: MutableList = java.util.ArrayList() + val tempBasalArray: MutableList = java.util.ArrayList() + val basalLineArray: MutableList = java.util.ArrayList() + val absoluteBasalLineArray: MutableList = java.util.ArrayList() + var lastLineBasal = 0.0 + var lastAbsoluteLineBasal = -1.0 + var lastBaseBasal = 0.0 + var lastTempBasal = 0.0 + var time = fromTime + while (time < toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 60 * 1000L + continue + } + val basalData = iobCobCalculator.getBasalData(profile, time) + val baseBasalValue = basalData.basal + var absoluteLineValue = baseBasalValue + var tempBasalValue = 0.0 + var basal = 0.0 + if (basalData.isTempBasalRunning) { + tempBasalValue = basalData.tempBasalAbsolute + absoluteLineValue = tempBasalValue + if (tempBasalValue != lastTempBasal) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale)) + } + if (lastBaseBasal != 0.0) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + lastBaseBasal = 0.0 + } + } else { + if (baseBasalValue != lastBaseBasal) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale)) + lastBaseBasal = baseBasalValue + } + if (lastTempBasal != 0.0) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + } + } + if (baseBasalValue != lastLineBasal) { + basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale)) + basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale)) + } + if (absoluteLineValue != lastAbsoluteLineBasal) { + absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale)) + } + lastAbsoluteLineBasal = absoluteLineValue + lastLineBasal = baseBasalValue + lastTempBasal = tempBasalValue + maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue)) + time += 60 * 1000L + } + + // final points + basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)) + + // create series + baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.basebasal) + it.thickness = 0 + } + tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.tempbasal) + it.thickness = 0 + } + basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.basal) + }) + } + absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { + it.setCustomPaint(Paint().also { absolutePaint -> + absolutePaint.style = Paint.Style.STROKE + absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color = resourceHelper.gc(R.color.basal) + }) + } +// profiler.log(LTag.UI, "prepareBasalData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + fun prepareTemporaryTargetData(from: String) { +// val start = dateUtil.now() + val profile = profile ?: return + val units = profileFunction.getUnits() + var toTime = toTime + val targetsSeriesArray: MutableList = java.util.ArrayList() + var lastTarget = -1.0 + loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } + var time = fromTime + while (time < toTime) { + val tt = repository.getTemporaryTargetActiveAt(time).blockingGet() + val value: Double = if (tt is ValueWrapper.Existing) { + Profile.fromMgdlToUnits(tt.value.target(), units) + } else { + Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) + } + if (lastTarget != value) { + if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) + targetsSeriesArray.add(DataPoint(time.toDouble(), value)) + } + lastTarget = value + time += 5 * 60 * 1000L + } + // final point + targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) + // create series + temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.tempTargetBackground) + it.thickness = 2 + } +// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + fun prepareTreatmentsData(from: String) { +// val start = dateUtil.now() + maxTreatmentsValue = 0.0 + val filteredTreatments: MutableList = java.util.ArrayList() + repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() + .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } + .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet() + .map { CarbsDataPoint(it, resourceHelper) } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + + // ProfileSwitch + repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() + .map { EffectiveProfileSwitchDataPoint(it) } + .forEach(filteredTreatments::add) + + // Extended bolus + if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { + repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet() + .map { ExtendedBolusDataPoint(it) } + .filter { it.duration != 0L } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + } + + // Careportal + repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet() + .map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } + .filterTimeframe(fromTime, endTime) + .forEach { + if (it.y == 0.0) it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + + // increase maxY if a treatment forces it's own height that's higher than a BG value + filteredTreatments.map { it.y } + .maxOrNull() + ?.let(::addUpperChartMargin) + ?.let { maxTreatmentsValue = maxOf(maxTreatmentsValue, it) } + + treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()) +// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + fun prepareIobAutosensData(from: String) { +// val start = dateUtil.now() + val iobArray: MutableList = java.util.ArrayList() + val absIobArray: MutableList = java.util.ArrayList() + maxIobValueFound = Double.MIN_VALUE + var lastIob = 0.0 + var absLastIob = 0.0 + var time = fromTime + + val minFailOverActiveList: MutableList = java.util.ArrayList() + val cobArray: MutableList = java.util.ArrayList() + maxCobValueFound = Double.MIN_VALUE + var lastCob = 0 + + val actArrayHist: MutableList = java.util.ArrayList() + val actArrayPrediction: MutableList = java.util.ArrayList() + val now = dateUtil.now().toDouble() + maxIAValue = 0.0 + + val bgiArrayHist: MutableList = java.util.ArrayList() + val bgiArrayPrediction: MutableList = java.util.ArrayList() + maxBGIValue = Double.MIN_VALUE + + val devArray: MutableList = java.util.ArrayList() + maxDevValueFound = Double.MIN_VALUE + + val ratioArray: MutableList = java.util.ArrayList() + maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% + minRatioValueFound = -5.0 + + val dsMaxArray: MutableList = java.util.ArrayList() + val dsMinArray: MutableList = java.util.ArrayList() + maxFromMaxValueFound = Double.MIN_VALUE + maxFromMinValueFound = Double.MIN_VALUE + + val adsData = iobCobCalculator.ads.clone() + + while (time <= toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 5 * 60 * 1000L + continue + } + // IOB + val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) + val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time) + val absIob = IobTotal.combine(iob, baseBasalIob) + val autosensData = adsData.getAutosensDataAtTime(time) + if (abs(lastIob - iob.iob) > 0.02) { + if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) + iobArray.add(ScaledDataPoint(time, iob.iob, iobScale)) + maxIobValueFound = maxOf(maxIobValueFound, abs(iob.iob)) + lastIob = iob.iob + } + if (abs(absLastIob - absIob.iob) > 0.02) { + if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, iobScale)) + absIobArray.add(ScaledDataPoint(time, absIob.iob, iobScale)) + maxIobValueFound = maxOf(maxIobValueFound, abs(absIob.iob)) + absLastIob = absIob.iob + } + + // COB + if (autosensData != null) { + val cob = autosensData.cob.toInt() + if (cob != lastCob) { + if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale)) + cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale)) + maxCobValueFound = max(maxCobValueFound, cob.toDouble()) + lastCob = cob + } + if (autosensData.failoverToMinAbsorbtionRate) { + autosensData.setScale(cobScale) + autosensData.setChartTime(time) + minFailOverActiveList.add(autosensData) + } + } + + // ACTIVITY + if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, actScale)) + else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, actScale)) + maxIAValue = max(maxIAValue, abs(iob.activity)) + + // BGI + val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI) + val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0 + val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0 + if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) + else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale)) + maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation)) + + // DEVIATIONS + if (autosensData != null) { + var color = resourceHelper.gc(R.color.deviationblack) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) + if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) + if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) + } else if (autosensData.type == "uam") { + color = resourceHelper.gc(R.color.uam) + } else if (autosensData.type == "csf") { + color = resourceHelper.gc(R.color.deviationgrey) + } + devArray.add(OverviewPlugin.DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) + maxDevValueFound = maxOf(maxDevValueFound, abs(autosensData.deviation), abs(bgi)) + } + + // RATIO + if (autosensData != null) { + ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale)) + maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + } + + // DEV SLOPE + if (autosensData != null) { + dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)) + dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)) + maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) + maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) + } + + time += 5 * 60 * 1000L + } + // IOB + iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + + if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) { + val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil) + val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() + val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing + val iobPrediction: MutableList = java.util.ArrayList() + val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray) { + iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) + val iobPrediction2: MutableList = java.util.ArrayList() + val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray2) { + iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) + } else { + iobPredictions1Series = PointsWithLabelGraphSeries() + iobPredictions2Series = PointsWithLabelGraphSeries() + } + + // COB + cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% + it.color = resourceHelper.gc(R.color.cob) + it.thickness = 3 + } + cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }) + + // ACTIVITY + activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.activity) + it.thickness = 3 + } + activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.activity) + }) + } + + // BGI + minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.bgi) + it.thickness = 3 + } + minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.bgi) + }) + } + + // DEVIATIONS + deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { + it.setValueDependentColor { data: OverviewPlugin.DeviationDataPoint -> data.color } + } + + // RATIO + ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { + it.color = resourceHelper.gc(R.color.ratio) + it.thickness = 3 + } + + // DEV SLOPE + dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopepos) + it.thickness = 3 + } + dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopeneg) + it.thickness = 3 + } + +// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start) + } + + private fun addUpperChartMargin(maxBgValue: Double) = + if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 + + private fun getNearestBg(date: Long): Double { + bgReadingsArray.let { bgReadingsArray -> + for (reading in bgReadingsArray) { + if (reading.timestamp > date) continue + return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits()) + } + return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits()) + else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits()) + } + } + + private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = + filter { it.x + it.duration >= fromTime && it.x <= endTime } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index d4b9d90774..08f7e073cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -57,7 +57,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.skins.SkinProvider import info.nightscout.androidaps.utils.* @@ -92,7 +91,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var nsDeviceStatus: NSDeviceStatus @Inject lateinit var loopPlugin: LoopPlugin @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var dexcomPlugin: DexcomPlugin @Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator @@ -108,7 +106,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var trendCalculator: TrendCalculator @Inject lateinit var config: Config @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var databaseHelper: DatabaseHelperInterface @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider @@ -640,7 +637,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList OverviewData.Property.PROFILE -> { binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime ?: "" - binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroudColor) + binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroundColor) binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor) } @@ -705,7 +702,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList OverviewData.Property.TEMPORARY_TARGET -> { // temp target - val tempTarget = overviewData.temporarytarget + val tempTarget = overviewData.temporaryTarget if (tempTarget != null) { binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) @@ -730,7 +727,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } OverviewData.Property.GRAPH -> { - val graphData = GraphData(injector, binding.graphsLayout.bgGraph) + val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData) val menuChartSettings = overviewMenus.setting graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine()) graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) @@ -755,7 +752,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList val now = System.currentTimeMillis() for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { - val secondGraphData = GraphData(injector, secondaryGraphs[g]) + val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData) var useABSForScale = false var useIobForScale = false var useCobForScale = false diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt index 39c51ac5a2..fe5a30dbb4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt @@ -1,85 +1,68 @@ package info.nightscout.androidaps.plugins.general.overview -import android.graphics.DashPathEffect -import android.graphics.Paint import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference -import com.jjoe64.graphview.series.BarGraphSeries -import com.jjoe64.graphview.series.DataPoint -import com.jjoe64.graphview.series.LineGraphSeries import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R -import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.events.* import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress -import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.Translator import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign import org.json.JSONObject -import java.util.* import javax.inject.Inject import javax.inject.Singleton -import kotlin.math.abs -import kotlin.math.ceil -import kotlin.math.max -import kotlin.math.min @Singleton class OverviewPlugin @Inject constructor( - injector: HasAndroidInjector, - private val notificationStore: NotificationStore, - private val fabricPrivacy: FabricPrivacy, - private val rxBus: RxBusWrapper, - private val sp: SP, - aapsLogger: AAPSLogger, - private val aapsSchedulers: AapsSchedulers, - resourceHelper: ResourceHelper, - private val config: Config, - private val dateUtil: DateUtil, - private val translator: Translator, + injector: HasAndroidInjector, + private val notificationStore: NotificationStore, + private val fabricPrivacy: FabricPrivacy, + private val rxBus: RxBusWrapper, + private val sp: SP, + aapsLogger: AAPSLogger, + private val aapsSchedulers: AapsSchedulers, + resourceHelper: ResourceHelper, + private val config: Config, + private val dateUtil: DateUtil, + private val translator: Translator, // private val profiler: Profiler, - private val profileFunction: ProfileFunction, - private val iobCobCalculator: IobCobCalculator, - private val repository: AppRepository, - private val defaultValueHelper: DefaultValueHelper, - private val loopPlugin: LoopPlugin, - private val activePlugin: ActivePlugin, - private val nsDeviceStatus: NSDeviceStatus, - private val overviewData: OverviewData, - private val overviewMenus: OverviewMenus + private val profileFunction: ProfileFunction, + private val iobCobCalculator: IobCobCalculator, + private val repository: AppRepository, + private val overviewData: OverviewData, + private val overviewMenus: OverviewMenus ) : PluginBase(PluginDescription() - .mainType(PluginType.GENERAL) - .fragmentClass(OverviewFragment::class.qualifiedName) - .alwaysVisible(true) - .alwaysEnabled(true) - .pluginIcon(R.drawable.ic_home) - .pluginName(R.string.overview) - .shortName(R.string.overview_shortname) - .preferencesId(R.xml.pref_overview) - .description(R.string.description_overview), - aapsLogger, resourceHelper, injector + .mainType(PluginType.GENERAL) + .fragmentClass(OverviewFragment::class.qualifiedName) + .alwaysVisible(true) + .alwaysEnabled(true) + .pluginIcon(R.drawable.ic_home) + .pluginName(R.string.overview) + .shortName(R.string.overview_shortname) + .preferencesId(R.xml.pref_overview) + .description(R.string.description_overview), + aapsLogger, resourceHelper, injector ), Overview { private var disposable: CompositeDisposable = CompositeDisposable() @@ -95,74 +78,76 @@ class OverviewPlugin @Inject constructor( notificationStore.createNotificationChannel() disposable += rxBus - .toObservable(EventNewNotification::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ n -> - if (notificationStore.add(n.notification)) - rxBus.send(EventRefreshOverview("EventNewNotification")) - }, fabricPrivacy::logException) + .toObservable(EventNewNotification::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ n -> + if (notificationStore.add(n.notification)) + rxBus.send(EventRefreshOverview("EventNewNotification")) + }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventDismissNotification::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ n -> - if (notificationStore.remove(n.id)) - rxBus.send(EventRefreshOverview("EventDismissNotification")) - }, fabricPrivacy::logException) + .toObservable(EventDismissNotification::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ n -> + if (notificationStore.remove(n.id)) + rxBus.send(EventRefreshOverview("EventDismissNotification")) + }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventIobCalculationProgress::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException) + .toObservable(EventIobCalculationProgress::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventTempBasalChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException) + .toObservable(EventTempBasalChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadExtendedBolus("EventExtendedBolusChange") }, fabricPrivacy::logException) + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadExtendedBolus("EventExtendedBolusChange") }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventNewBG::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException) + .toObservable(EventNewBG::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventTempTargetChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException) + .toObservable(EventTempTargetChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - loadIobCobResults("EventTreatmentChange") - prepareTreatmentsData("EventTreatmentChange") - overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH)) - }, fabricPrivacy::logException) + .toObservable(EventTreatmentChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + loadIobCobResults("EventTreatmentChange") + overviewData.prepareTreatmentsData("EventTreatmentChange") + overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventTherapyEventChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - prepareTreatmentsData("EventTherapyEventChange") - overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH)) - }, fabricPrivacy::logException) + .toObservable(EventTherapyEventChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + overviewData.prepareTreatmentsData("EventTherapyEventChange") + overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventBucketedDataCreated::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - prepareBucketedData("EventBucketedDataCreated") - prepareBgData("EventBucketedDataCreated") - overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH)) - }, fabricPrivacy::logException) + .toObservable(EventBucketedDataCreated::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + overviewData.prepareBucketedData("EventBucketedDataCreated") + overviewData.prepareBgData("EventBucketedDataCreated") + overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventLoopInvoked::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException) + .toObservable(EventLoopInvoked::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException) disposable.add(rxBus - .toObservable(EventProfileSwitchChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadProfile("EventProfileSwitchChanged") }, fabricPrivacy::logException)) + .toObservable(EventProfileSwitchChanged::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadProfile("EventProfileSwitchChanged") }, fabricPrivacy::logException)) disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ refreshLoop("EventAutosensCalculationFinished") }, fabricPrivacy::logException)) + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished") + }, fabricPrivacy::logException)) Thread { loadAll("onResume") }.start() } @@ -187,59 +172,60 @@ class OverviewPlugin @Inject constructor( } override fun configuration(): JSONObject = - JSONObject() - .putString(R.string.key_quickwizard, sp, resourceHelper) - .putInt(R.string.key_eatingsoon_duration, sp, resourceHelper) - .putDouble(R.string.key_eatingsoon_target, sp, resourceHelper) - .putInt(R.string.key_activity_duration, sp, resourceHelper) - .putDouble(R.string.key_activity_target, sp, resourceHelper) - .putInt(R.string.key_hypo_duration, sp, resourceHelper) - .putDouble(R.string.key_hypo_target, sp, resourceHelper) - .putDouble(R.string.key_low_mark, sp, resourceHelper) - .putDouble(R.string.key_high_mark, sp, resourceHelper) - .putDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) + JSONObject() + .putString(R.string.key_quickwizard, sp, resourceHelper) + .putInt(R.string.key_eatingsoon_duration, sp, resourceHelper) + .putDouble(R.string.key_eatingsoon_target, sp, resourceHelper) + .putInt(R.string.key_activity_duration, sp, resourceHelper) + .putDouble(R.string.key_activity_target, sp, resourceHelper) + .putInt(R.string.key_hypo_duration, sp, resourceHelper) + .putDouble(R.string.key_hypo_target, sp, resourceHelper) + .putDouble(R.string.key_low_mark, sp, resourceHelper) + .putDouble(R.string.key_high_mark, sp, resourceHelper) + .putDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) override fun applyConfiguration(configuration: JSONObject) { configuration - .storeString(R.string.key_quickwizard, sp, resourceHelper) - .storeInt(R.string.key_eatingsoon_duration, sp, resourceHelper) - .storeDouble(R.string.key_eatingsoon_target, sp, resourceHelper) - .storeInt(R.string.key_activity_duration, sp, resourceHelper) - .storeDouble(R.string.key_activity_target, sp, resourceHelper) - .storeInt(R.string.key_hypo_duration, sp, resourceHelper) - .storeDouble(R.string.key_hypo_target, sp, resourceHelper) - .storeDouble(R.string.key_low_mark, sp, resourceHelper) - .storeDouble(R.string.key_high_mark, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) + .storeString(R.string.key_quickwizard, sp, resourceHelper) + .storeInt(R.string.key_eatingsoon_duration, sp, resourceHelper) + .storeDouble(R.string.key_eatingsoon_target, sp, resourceHelper) + .storeInt(R.string.key_activity_duration, sp, resourceHelper) + .storeDouble(R.string.key_activity_target, sp, resourceHelper) + .storeInt(R.string.key_hypo_duration, sp, resourceHelper) + .storeDouble(R.string.key_hypo_target, sp, resourceHelper) + .storeDouble(R.string.key_low_mark, sp, resourceHelper) + .storeDouble(R.string.key_high_mark, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) } - @Volatile var runningRefresh = false + @Volatile + var runningRefresh = false override fun refreshLoop(from: String) { if (runningRefresh) return runningRefresh = true @@ -252,11 +238,11 @@ class OverviewPlugin @Inject constructor( overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY)) loadAsData(from) - preparePredictions(from) - prepareBasalData(from) - prepareTemporaryTargetData(from) - prepareTreatmentsData(from) - prepareIobAutosensData(from) + overviewData.preparePredictions(from) + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) + overviewData.prepareIobAutosensData(from) overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) aapsLogger.debug(LTag.UI, "refreshLoop finished") runningRefresh = false @@ -271,9 +257,9 @@ class OverviewPlugin @Inject constructor( loadTemporaryTarget(from) loadIobCobResults(from) loadAsData(from) - prepareBasalData(from) - prepareTemporaryTargetData(from) - prepareTreatmentsData(from) + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) // prepareIobAutosensData(from) // preparePredictions(from) overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) @@ -299,8 +285,8 @@ class OverviewPlugin @Inject constructor( private fun loadTemporaryTarget(from: String) { val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() - if (tempTarget is ValueWrapper.Existing) overviewData.temporarytarget = tempTarget.value - else overviewData.temporarytarget = null + if (tempTarget is ValueWrapper.Existing) overviewData.temporaryTarget = tempTarget.value + else overviewData.temporaryTarget = null overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) } @@ -326,514 +312,4 @@ class OverviewPlugin @Inject constructor( overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB)) } - @Synchronized - @Suppress("SameParameterValue", "UNUSED_PARAMETER") - private fun prepareBgData(from: String) { -// val start = dateUtil.now() - var maxBgValue = Double.MIN_VALUE - overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(overviewData.fromTime, overviewData.toTime, false).blockingGet() - val bgListArray: MutableList = ArrayList() - for (bg in overviewData.bgReadingsArray) { - if (bg.timestamp < overviewData.fromTime || bg.timestamp > overviewData.toTime) continue - if (bg.value > maxBgValue) maxBgValue = bg.value - bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper)) - } - overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) - overviewData.maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits()) - if (defaultValueHelper.determineHighLine() > maxBgValue) overviewData.maxBgValue = defaultValueHelper.determineHighLine() - overviewData.maxBgValue = addUpperChartMargin(overviewData.maxBgValue) -// profiler.log(LTag.UI, "prepareBgData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - private fun preparePredictions(from: String) { -// val start = dateUtil.now() - val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) - val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT - val menuChartSettings = overviewMenus.setting - // align to hours - val calendar = Calendar.getInstance().also { - it.timeInMillis = System.currentTimeMillis() - it[Calendar.MILLISECOND] = 0 - it[Calendar.SECOND] = 0 - it[Calendar.MINUTE] = 0 - it.add(Calendar.HOUR, 1) - } - if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { - var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() - predictionHours = min(2, predictionHours) - predictionHours = max(0, predictionHours) - val hoursToFetch = overviewData.rangeToDisplay - predictionHours - overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - overviewData.fromTime = overviewData.toTime - T.hours(hoursToFetch.toLong()).msecs() - overviewData.endTime = overviewData.toTime + T.hours(predictionHours.toLong()).msecs() - } else { - overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - overviewData.fromTime = overviewData.toTime - T.hours(overviewData.rangeToDisplay.toLong()).msecs() - overviewData.endTime = overviewData.toTime - } - - val bgListArray: MutableList = ArrayList() - val predictions: MutableList? = apsResult?.predictions - ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) } - ?.toMutableList() - if (predictions != null) { - predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) } - for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) - } - overviewData.predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) -// profiler.log(LTag.UI, "preparePredictions() $from", start) - } - - @Synchronized - @Suppress("SameParameterValue", "UNUSED_PARAMETER") - private fun prepareBucketedData(from: String) { -// val start = dateUtil.now() - val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return - if (bucketedData.isEmpty()) { - aapsLogger.debug("No bucketed data.") - return - } - val bucketedListArray: MutableList = ArrayList() - for (inMemoryGlucoseValue in bucketedData) { - if (inMemoryGlucoseValue.timestamp < overviewData.fromTime || inMemoryGlucoseValue.timestamp > overviewData.toTime) continue - bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper)) - } - overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) -// profiler.log(LTag.UI, "prepareBucketedData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - private fun prepareBasalData(from: String) { -// val start = dateUtil.now() - overviewData.maxBasalValueFound = 0.0 - val baseBasalArray: MutableList = ArrayList() - val tempBasalArray: MutableList = ArrayList() - val basalLineArray: MutableList = ArrayList() - val absoluteBasalLineArray: MutableList = ArrayList() - var lastLineBasal = 0.0 - var lastAbsoluteLineBasal = -1.0 - var lastBaseBasal = 0.0 - var lastTempBasal = 0.0 - var time = overviewData.fromTime - while (time < overviewData.toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 60 * 1000L - continue - } - val basalData = iobCobCalculator.getBasalData(profile, time) - val baseBasalValue = basalData.basal - var absoluteLineValue = baseBasalValue - var tempBasalValue = 0.0 - var basal = 0.0 - if (basalData.isTempBasalRunning) { - tempBasalValue = basalData.tempBasalAbsolute - absoluteLineValue = tempBasalValue - if (tempBasalValue != lastTempBasal) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale)) - tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, overviewData.basalScale)) - } - if (lastBaseBasal != 0.0) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale)) - baseBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale)) - lastBaseBasal = 0.0 - } - } else { - if (baseBasalValue != lastBaseBasal) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale)) - baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, overviewData.basalScale)) - lastBaseBasal = baseBasalValue - } - if (lastTempBasal != 0.0) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale)) - tempBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale)) - } - } - if (baseBasalValue != lastLineBasal) { - basalLineArray.add(ScaledDataPoint(time, lastLineBasal, overviewData.basalScale)) - basalLineArray.add(ScaledDataPoint(time, baseBasalValue, overviewData.basalScale)) - } - if (absoluteLineValue != lastAbsoluteLineBasal) { - absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, overviewData.basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(time, basal, overviewData.basalScale)) - } - lastAbsoluteLineBasal = absoluteLineValue - lastLineBasal = baseBasalValue - lastTempBasal = tempBasalValue - overviewData.maxBasalValueFound = max(overviewData.maxBasalValueFound, max(tempBasalValue, baseBasalValue)) - time += 60 * 1000L - } - - // final points - basalLineArray.add(ScaledDataPoint(overviewData.toTime, lastLineBasal, overviewData.basalScale)) - baseBasalArray.add(ScaledDataPoint(overviewData.toTime, lastBaseBasal, overviewData.basalScale)) - tempBasalArray.add(ScaledDataPoint(overviewData.toTime, lastTempBasal, overviewData.basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(overviewData.toTime, lastAbsoluteLineBasal, overviewData.basalScale)) - - // create series - overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.basebasal) - it.thickness = 0 - } - overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.tempbasal) - it.thickness = 0 - } - overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.basal) - }) - } - overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { - it.setCustomPaint(Paint().also { absolutePaint -> - absolutePaint.style = Paint.Style.STROKE - absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - absolutePaint.color = resourceHelper.gc(R.color.basal) - }) - } -// profiler.log(LTag.UI, "prepareBasalData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - private fun prepareTemporaryTargetData(from: String) { -// val start = dateUtil.now() - val profile = overviewData.profile ?: return - val units = profileFunction.getUnits() - var toTime = overviewData.toTime - val targetsSeriesArray: MutableList = ArrayList() - var lastTarget = -1.0 - loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } - var time = overviewData.fromTime - while (time < toTime) { - val tt = repository.getTemporaryTargetActiveAt(time).blockingGet() - val value: Double = if (tt is ValueWrapper.Existing) { - Profile.fromMgdlToUnits(tt.value.target(), units) - } else { - Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) - } - if (lastTarget != value) { - if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) - targetsSeriesArray.add(DataPoint(time.toDouble(), value)) - } - lastTarget = value - time += 5 * 60 * 1000L - } - // final point - targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) - // create series - overviewData.temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.tempTargetBackground) - it.thickness = 2 - } -// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - private fun prepareTreatmentsData(from: String) { -// val start = dateUtil.now() - overviewData.maxTreatmentsValue = 0.0 - val filteredTreatments: MutableList = ArrayList() - repository.getBolusesIncludingInvalidFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() - .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } - .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(overviewData.fromTime, overviewData.endTime, true).blockingGet() - .map { CarbsDataPoint(it, resourceHelper) } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // ProfileSwitch - repository.getEffectiveProfileSwitchDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() - .map { EffectiveProfileSwitchDataPoint(it) } - .forEach(filteredTreatments::add) - - // Extended bolus - if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { - repository.getExtendedBolusDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() - .map { ExtendedBolusDataPoint(it) } - .filter { it.duration != 0L } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - } - - // Careportal - repository.compatGetTherapyEventDataFromToTime(overviewData.fromTime - T.hours(6).msecs(), overviewData.endTime).blockingGet() - .map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } - .filterTimeframe(overviewData.fromTime, overviewData.endTime) - .forEach { - if (it.y == 0.0) it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // increase maxY if a treatment forces it's own height that's higher than a BG value - filteredTreatments.map { it.y } - .maxOrNull() - ?.let(::addUpperChartMargin) - ?.let { overviewData.maxTreatmentsValue = maxOf(overviewData.maxTreatmentsValue, it) } - - overviewData.treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()) -// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - private fun prepareIobAutosensData(from: String) { -// val start = dateUtil.now() - val iobArray: MutableList = ArrayList() - val absIobArray: MutableList = ArrayList() - overviewData.maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - var absLastIob = 0.0 - var time = overviewData.fromTime - - val minFailOverActiveList: MutableList = ArrayList() - val cobArray: MutableList = ArrayList() - overviewData.maxCobValueFound = Double.MIN_VALUE - var lastCob = 0 - - val actArrayHist: MutableList = ArrayList() - val actArrayPrediction: MutableList = ArrayList() - val now = dateUtil.now().toDouble() - overviewData.maxIAValue = 0.0 - - val bgiArrayHist: MutableList = ArrayList() - val bgiArrayPrediction: MutableList = ArrayList() - overviewData.maxBGIValue = Double.MIN_VALUE - - val devArray: MutableList = ArrayList() - overviewData.maxDevValueFound = Double.MIN_VALUE - - val ratioArray: MutableList = ArrayList() - overviewData.maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% - overviewData.minRatioValueFound = -5.0 - - val dsMaxArray: MutableList = ArrayList() - val dsMinArray: MutableList = ArrayList() - overviewData.maxFromMaxValueFound = Double.MIN_VALUE - overviewData.maxFromMinValueFound = Double.MIN_VALUE - - val adsData = iobCobCalculator.ads.clone() - - while (time <= overviewData.toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - // IOB - val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time) - val absIob = IobTotal.combine(iob, baseBasalIob) - val autosensData = adsData.getAutosensDataAtTime(time) - if (abs(lastIob - iob.iob) > 0.02) { - if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, overviewData.iobScale)) - iobArray.add(ScaledDataPoint(time, iob.iob, overviewData.iobScale)) - overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(iob.iob)) - lastIob = iob.iob - } - if (abs(absLastIob - absIob.iob) > 0.02) { - if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, overviewData.iobScale)) - absIobArray.add(ScaledDataPoint(time, absIob.iob, overviewData.iobScale)) - overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(absIob.iob)) - absLastIob = absIob.iob - } - - // COB - if (autosensData != null) { - val cob = autosensData.cob.toInt() - if (cob != lastCob) { - if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), overviewData.cobScale)) - cobArray.add(ScaledDataPoint(time, cob.toDouble(), overviewData.cobScale)) - overviewData.maxCobValueFound = max(overviewData.maxCobValueFound, cob.toDouble()) - lastCob = cob - } - if (autosensData.failoverToMinAbsorbtionRate) { - autosensData.setScale(overviewData.cobScale) - autosensData.setChartTime(time) - minFailOverActiveList.add(autosensData) - } - } - - // ACTIVITY - if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, overviewData.actScale)) - else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, overviewData.actScale)) - overviewData.maxIAValue = max(overviewData.maxIAValue, abs(iob.activity)) - - // BGI - val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI) - val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0 - val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0 - if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, overviewData.bgiScale)) - else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, overviewData.bgiScale)) - overviewData.maxBGIValue = max(overviewData.maxBGIValue, max(abs(bgi), deviation)) - - // DEVIATIONS - if (autosensData != null) { - var color = resourceHelper.gc(R.color.deviationblack) // "=" - if (autosensData.type == "" || autosensData.type == "non-meal") { - if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) - if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) - if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) - } else if (autosensData.type == "uam") { - color = resourceHelper.gc(R.color.uam) - } else if (autosensData.type == "csf") { - color = resourceHelper.gc(R.color.deviationgrey) - } - devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, overviewData.devScale)) - overviewData.maxDevValueFound = maxOf(overviewData.maxDevValueFound, abs(autosensData.deviation), abs(bgi)) - } - - // RATIO - if (autosensData != null) { - ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), overviewData.ratioScale)) - overviewData.maxRatioValueFound = max(overviewData.maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - overviewData.minRatioValueFound = min(overviewData.minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - } - - // DEV SLOPE - if (autosensData != null) { - dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, overviewData.dsMaxScale)) - dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, overviewData.dsMinScale)) - overviewData.maxFromMaxValueFound = max(overviewData.maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) - overviewData.maxFromMinValueFound = max(overviewData.maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) - } - - time += 5 * 60 * 1000L - } - // IOB - overviewData.iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } - overviewData.absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } - - if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) { - val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil) - val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() - val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing - val iobPrediction: MutableList = ArrayList() - val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray) { - iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) - overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob)) - } - overviewData.iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) - val iobPrediction2: MutableList = ArrayList() - val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray2) { - iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) - overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob)) - } - overviewData.iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) - } else { - overviewData.iobPredictions1Series = PointsWithLabelGraphSeries() - overviewData.iobPredictions2Series = PointsWithLabelGraphSeries() - } - - // COB - overviewData.cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% - it.color = resourceHelper.gc(R.color.cob) - it.thickness = 3 - } - overviewData.cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }) - - // ACTIVITY - overviewData.activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.activity) - it.thickness = 3 - } - overviewData.activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.activity) - }) - } - - // BGI - overviewData.minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.bgi) - it.thickness = 3 - } - overviewData.minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.bgi) - }) - } - - // DEVIATIONS - overviewData.deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { - it.setValueDependentColor { data: DeviationDataPoint -> data.color } - } - - // RATIO - overviewData.ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { - it.color = resourceHelper.gc(R.color.ratio) - it.thickness = 3 - } - - // DEV SLOPE - overviewData.dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopepos) - it.thickness = 3 - } - overviewData.dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopeneg) - it.thickness = 3 - } - -// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start) - } - - private fun addUpperChartMargin(maxBgValue: Double) = - if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 - - private fun getNearestBg(date: Long): Double { - overviewData.bgReadingsArray.let { bgReadingsArray -> - for (reading in bgReadingsArray) { - if (reading.timestamp > date) continue - return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits()) - } - return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits()) - else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits()) - } - } - - private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = - filter { it.x + it.duration >= fromTime && it.x <= endTime } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt index c2055e61e8..ba3e33667f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -26,16 +26,16 @@ import kotlin.math.max class GraphData( injector: HasAndroidInjector, - private val graph: GraphView + private val graph: GraphView, + private val overviewData: OverviewData ) { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var overviewData: OverviewData - var maxY = Double.MIN_VALUE + private var maxY = Double.MIN_VALUE private var minY = Double.MAX_VALUE private val units: GlucoseUnit private val series: MutableList> = ArrayList() @@ -201,7 +201,7 @@ class GraphData( graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space } - internal fun addSeries(s: Series<*>) = series.add(s) + private fun addSeries(s: Series<*>) = series.add(s) fun performUpdate() { // clear old data diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt index d577666d27..51ef6da52f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt @@ -99,7 +99,7 @@ class IobCobOref1Thread internal constructor( // start from oldest to be able sub cob for (i in bucketedData.size - 4 downTo 0) { val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" - rxBus.send(EventIobCalculationProgress(progress)) + rxBus.send(EventIobCalculationProgress(progress, cause)) if (iobCobCalculatorPlugin.stopCalculationTrigger) { iobCobCalculatorPlugin.stopCalculationTrigger = false aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") @@ -325,7 +325,7 @@ class IobCobOref1Thread internal constructor( }.start() } finally { mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("")) + rxBus.send(EventIobCalculationProgress("", cause)) aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt index e6fa48cca2..cd3c12c438 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt @@ -98,7 +98,7 @@ class IobCobThread @Inject internal constructor( // start from oldest to be able sub cob for (i in bucketedData.size - 4 downTo 0) { val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" - rxBus.send(EventIobCalculationProgress(progress)) + rxBus.send(EventIobCalculationProgress(progress, cause)) if (iobCobCalculatorPlugin.stopCalculationTrigger) { iobCobCalculatorPlugin.stopCalculationTrigger = false aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") @@ -272,7 +272,7 @@ class IobCobThread @Inject internal constructor( }.start() } finally { mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("")) + rxBus.send(EventIobCalculationProgress("", cause)) aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") profiler.log(LTag.AUTOSENS, "IobCobThread", start) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt index f2e8059b10..a5326e4ad3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt @@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events import info.nightscout.androidaps.events.Event -class EventIobCalculationProgress(var progress: String) : Event() \ No newline at end of file +class EventIobCalculationProgress(val progress: String, val cause: Event?) : Event() \ No newline at end of file diff --git a/app/src/main/res/layout/activity_historybrowse.xml b/app/src/main/res/layout/activity_historybrowse.xml index 98ce50f0c4..65bd506f7e 100644 --- a/app/src/main/res/layout/activity_historybrowse.xml +++ b/app/src/main/res/layout/activity_historybrowse.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity"> + tools:context="info.nightscout.androidaps.activities.HistoryBrowseActivity"> diff --git a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt index 8569bdc726..c560e0a38b 100644 --- a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt @@ -28,8 +28,6 @@ import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin import info.nightscout.androidaps.plugins.source.GlimpPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentService -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.Profiler import info.nightscout.androidaps.utils.buildHelper.BuildHelper @@ -51,7 +49,7 @@ import java.util.* @RunWith(PowerMockRunner::class) @PrepareForTest( ConstraintChecker::class, SP::class, Context::class, - OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class, + OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, TemporaryBasalStorage::class, GlimpPlugin::class, Profiler::class, UserEntryLogger::class, LoggerUtils::class, AppRepository::class, InsightDatabaseDao::class) class ConstraintsCheckerTest : TestBaseWithProfile() { diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt index f6ec5e4687..046472a25e 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt @@ -14,7 +14,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy @@ -41,7 +40,6 @@ class LoopPluginTest : TestBase() { @Mock lateinit var context: Context @Mock lateinit var commandQueue: CommandQueueProvider @Mock lateinit var activePlugin: ActivePlugin - @Mock lateinit var treatmentsPlugin: TreatmentsPlugin @Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin @Mock lateinit var iobCobCalculator: IobCobCalculator @Mock lateinit var fabricPrivacy: FabricPrivacy diff --git a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt index cd03c5cffa..ba57bfa9d1 100644 --- a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt @@ -20,7 +20,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.commands.* import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy @@ -45,7 +44,7 @@ import java.util.* @RunWith(PowerMockRunner::class) @PrepareForTest( ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, - TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class, PowerManager::class, + FabricPrivacy::class, LoggerUtils::class, PowerManager::class, AppRepository::class) class CommandQueueTest : TestBaseWithProfile() { diff --git a/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt index ff19abebf4..5dd203a40d 100644 --- a/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt @@ -14,7 +14,6 @@ import info.nightscout.androidaps.interfaces.PumpSync import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.commands.Command import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute import info.nightscout.androidaps.utils.FabricPrivacy @@ -34,7 +33,7 @@ import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) @PrepareForTest( ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, - TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class, PowerManager::class) + FabricPrivacy::class, LoggerUtils::class, PowerManager::class) class QueueThreadTest : TestBaseWithProfile() { @Mock lateinit var constraintChecker: ConstraintChecker diff --git a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt index fa1e175567..38fd598f5e 100644 --- a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt @@ -13,7 +13,6 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.resources.ResourceHelper import org.junit.Assert @@ -40,7 +39,6 @@ class BolusWizardTest : TestBase() { @Mock lateinit var commandQueue: CommandQueueProvider @Mock lateinit var loopPlugin: LoopPlugin @Mock lateinit var iobCobCalculator: IobCobCalculator - @Mock lateinit var treatmentsPlugin: TreatmentsPlugin @Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin @Mock lateinit var dateUtil: DateUtil @Mock lateinit var autosensDataStore: AutosensDataStore @@ -71,7 +69,6 @@ class BolusWizardTest : TestBase() { `when`(profile.getIc()).thenReturn(insulinToCarbRatio) `when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL) - `when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin) `when`(iobCobCalculator.calculateIobFromBolus()).thenReturn(IobTotal(System.currentTimeMillis())) `when`(iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended()).thenReturn(IobTotal(System.currentTimeMillis())) `when`(activePlugin.activePump).thenReturn(virtualPumpPlugin) From 5ef92462a7750a0e528b8bf8dc0282f10a880e7b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 18:31:01 +0200 Subject: [PATCH 05/10] do not use last record in DetailedBolusInfoStorage --- .../bolusInfo/DetailedBolusInfoStorage.kt | 22 +++++++++---------- .../androidaps/danars/services/BLEComm.kt | 5 ++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorage.kt b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorage.kt index 062d41191f..57a732cbc3 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorage.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorage.kt @@ -29,7 +29,7 @@ class DetailedBolusInfoStorage @Inject constructor( val d = store[i] //aapsLogger.debug(LTag.PUMP, "Existing bolus info: " + store[i]) if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && abs(store[i].insulin - bolus) < 0.01) { - aapsLogger.debug(LTag.PUMP, "Using & removing bolus info: ${store[i]}") + aapsLogger.debug(LTag.PUMP, "Using & removing bolus info for time $bolusTime: ${store[i]}") store.removeAt(i) return d } @@ -38,22 +38,22 @@ class DetailedBolusInfoStorage @Inject constructor( for (i in store.indices) { val d = store[i] if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && bolus <= store[i].insulin + 0.01) { - aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info: ${store[i]}") + aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info for time $bolusTime: ${store[i]}") store.removeAt(i) return d } } // If not found, use last record if amount is the same - if (store.size > 0) { - val d = store[store.size - 1] - if (abs(d.insulin - bolus) < 0.01) { - aapsLogger.debug(LTag.PUMP, "Using LAST & removing bolus info: $d") - store.removeAt(store.size - 1) - return d - } - } + // if (store.size > 0) { + // val d = store[store.size - 1] + // if (abs(d.insulin - bolus) < 0.01) { + // aapsLogger.debug(LTag.PUMP, "Using LAST & removing bolus info for time $bolusTime: $d") + // store.removeAt(store.size - 1) + // return d + // } + // } //Not found - //aapsLogger.debug(LTag.PUMP, "Bolus info not found") + aapsLogger.debug(LTag.PUMP, "Bolus info not found for time $bolusTime") return null } } \ No newline at end of file diff --git a/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt b/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt index 83996f4504..d5aa72269a 100644 --- a/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt +++ b/danars/src/main/java/info/nightscout/androidaps/danars/services/BLEComm.kt @@ -323,13 +323,16 @@ class BLEComm @Inject internal constructor( } private val readBuffer = ByteArray(1024) - private var bufferLength = 0 + @Volatile private var bufferLength = 0 private fun addToReadBuffer(buffer: ByteArray) { //log.debug("addToReadBuffer " + DanaRS_Packet.toHexString(buffer)); if (buffer.isEmpty()) { return } + if (bufferLength == 1024) { + aapsLogger.debug(LTag.PUMPBTCOMM, "1024 XXXXXXXXXXXXXX") + } synchronized(readBuffer) { // Append incoming data to input buffer System.arraycopy(buffer, 0, readBuffer, bufferLength, buffer.size) From 3ed45fdd11199cb066225c690d7eacc6a02b9618 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 20:59:41 +0200 Subject: [PATCH 06/10] improve NS profile sync --- .../DataSyncSelectorImplementation.kt | 6 ++-- .../general/nsclient/NSClientMbgWorker.kt | 2 +- .../profile/local/LocalProfileFragment.kt | 2 +- .../profile/local/LocalProfilePlugin.kt | 29 ++++++++++++------- .../androidaps/plugins/source/DexcomPlugin.kt | 2 +- .../plugins/source/EversensePlugin.kt | 2 +- .../androidaps/plugins/source/GlimpPlugin.kt | 2 +- .../plugins/source/NSClientSourcePlugin.kt | 2 +- .../plugins/source/PoctechPlugin.kt | 2 +- .../androidaps/plugins/source/TomatoPlugin.kt | 2 +- .../androidaps/plugins/source/XdripPlugin.kt | 2 +- .../androidaps/interfaces/ProfileStore.kt | 9 ++++++ 12 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt index 5f1e910af8..5f1844315f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt @@ -561,9 +561,9 @@ class DataSyncSelectorImplementation @Inject constructor( val lastSync = sp.getLong(R.string.key_ns_profile_store_last_synced_timestamp, 0) val lastChange = sp.getLong(R.string.key_local_profile_last_change, 0) if (lastChange == 0L) return - localProfilePlugin.createProfileStore() - val profileJson = localProfilePlugin.profile?.data ?: return - if (lastChange > lastSync) + if (lastChange > lastSync) { + val profileJson = localProfilePlugin.profile?.data ?: return nsClientPlugin.nsClientService?.dbAdd("profile", profileJson, DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "") + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt index 8c73fd908f..b7f1d1540c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt @@ -34,7 +34,7 @@ class NSClientMbgWorker( var ret = Result.success() val acceptNSData = sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT - if (!acceptNSData) return ret + if (!acceptNSData) return Result.success(workDataOf("Result" to "Sync not enabled")) val mbgArray = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt index 31f01df6c1..cef0babae1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt @@ -70,7 +70,7 @@ class LocalProfileFragment : DaggerFragment() { } private fun sumLabel(): String { - val profile = localProfilePlugin.createProfileStore().getDefaultProfile() + val profile = localProfilePlugin.profile?.getDefaultProfile() val sum = profile?.let { ProfileSealed.Pure(profile).baseBasalSum() } ?: 0.0 return " ∑" + DecimalFormatter.to2Decimal(sum) + resourceHelper.gs(R.string.insulin_unit_shortname) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt index 42afbb4c5c..2feeab89d8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt @@ -139,6 +139,7 @@ class LocalProfilePlugin @Inject constructor( } sp.putInt(Constants.LOCAL_PROFILE + "_profiles", numOfProfiles) + sp.putLong(R.string.key_local_profile_last_change, dateUtil.now()) createAndStoreConvertedProfile() isEdited = false aapsLogger.debug(LTag.PROFILE, "Storing settings: " + rawProfile?.data.toString()) @@ -148,12 +149,9 @@ class LocalProfilePlugin @Inject constructor( val name = it.name ?: "." if (name.contains(".")) namesOK = false } - if (namesOK) - sp.putLong(R.string.key_local_profile_last_change, dateUtil.now()) - else - activity?.let { - OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot)) - } + if (!namesOK) activity?.let { + OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot)) + } } @Synchronized @@ -372,7 +370,8 @@ class LocalProfilePlugin @Inject constructor( } } if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name) - json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now())) + val startDate = sp.getLong(R.string.key_local_profile_last_change, dateUtil.now()) + json.put("startDate", dateUtil.toISOAsUTC(startDate)) json.put("store", store) } catch (e: JSONException) { aapsLogger.error("Unhandled exception", e) @@ -412,11 +411,19 @@ class LocalProfilePlugin @Inject constructor( val profileJson = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) { - localProfilePlugin.loadFromStore(ProfileStore(injector, profileJson, dateUtil)) - aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson") - return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length())))) + val store = ProfileStore(injector, profileJson, dateUtil) + val startDate = store.getStartDate() + val lastLocalChange = sp.getLong(R.string.key_local_profile_last_change, 0) + aapsLogger.debug(LTag.PROFILE, "Received profileStore: StartDate: $startDate Local last modification: $lastLocalChange") + @Suppress("LiftReturnOrAssignment") + if (startDate > lastLocalChange || startDate % 1000 == 0L) {// whole second means edited in NS + localProfilePlugin.loadFromStore(store) + aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson") + return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length())))) + } else + return Result.success(workDataOf("Result" to "Unchanged. Ignoring")) } - return Result.success() + return Result.success(workDataOf("Result" to "Sync not enabled")) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt index 6b6cb588e8..d744d81340 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt @@ -94,7 +94,7 @@ class DexcomPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt index 434e5eb873..a6c51cf5de 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt @@ -69,7 +69,7 @@ class EversensePlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) if (bundle.containsKey("currentCalibrationPhase")) aapsLogger.debug(LTag.BGSOURCE, "currentCalibrationPhase: " + bundle.getString("currentCalibrationPhase")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt index 36724eb4b5..d2b167a43d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt @@ -56,7 +56,7 @@ class GlimpPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) aapsLogger.debug(LTag.BGSOURCE, "Received Glimp Data: $inputData}") val glucoseValues = mutableListOf() glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt index 52874bb335..a0aa888901 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt @@ -115,7 +115,7 @@ class NSClientSourcePlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, true) && !dexcomPlugin.isEnabled()) return Result.success() + if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, true) && !dexcomPlugin.isEnabled()) return Result.success(workDataOf("Result" to "Sync not enabled")) val sgvs = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt index 17aefac5b5..fa7e0a278a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt @@ -60,7 +60,7 @@ class PoctechPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) aapsLogger.debug(LTag.BGSOURCE, "Received Poctech Data $inputData") try { val glucoseValues = mutableListOf() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt index cf6fce3da5..52773e1d76 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt @@ -59,7 +59,7 @@ class TomatoPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val glucoseValues = mutableListOf() glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( timestamp = inputData.getLong("com.fanqies.tomatofn.Extras.Time", 0), diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt index 622d69c284..bbb8b2c7e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt @@ -73,7 +73,7 @@ class XdripPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/ProfileStore.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/ProfileStore.kt index 91a00c4dc4..5884128606 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/ProfileStore.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/ProfileStore.kt @@ -31,6 +31,15 @@ class ProfileStore(val injector: HasAndroidInjector, val data: JSONObject, val d return null } + fun getStartDate(): Long { + val iso = JsonHelper.safeGetString(data, "startDate") ?: return 0 + return try { + dateUtil.fromISODateString(iso) + } catch (e: Exception) { + 0 + } + } + fun getDefaultProfile(): PureProfile? = getDefaultProfileName()?.let { getSpecificProfile(it) } fun getDefaultProfileJson(): JSONObject? = getDefaultProfileName()?.let { getSpecificProfileJson(it) } From 3d1b7e8ded23a0be7bc87fd6f2caaa857b1b4f44 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 21:19:18 +0200 Subject: [PATCH 07/10] fix updating non basal profile changes --- .../java/info/nightscout/androidaps/queue/CommandQueue.kt | 7 ++++--- .../nightscout/androidaps/receivers/KeepAliveReceiver.kt | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt index 448e917ae9..d759844d87 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt @@ -566,11 +566,12 @@ open class CommandQueue @Inject constructor( return HtmlHelper.fromHtml(s) } - override fun isThisProfileSet(profile: Profile): Boolean { - val result = activePlugin.activePump.isThisProfileSet(profile) + override fun isThisProfileSet(requestedProfile: Profile): Boolean { + val runningProfile = profileFunction.getProfile() ?: return false + val result = activePlugin.activePump.isThisProfileSet(requestedProfile) && requestedProfile.isEqual(runningProfile) if (!result) { aapsLogger.debug(LTag.PUMPQUEUE, "Current profile: ${profileFunction.getProfile()}") - aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $profile") + aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $requestedProfile") } return result } diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt index 1b78360966..48f073e804 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt @@ -119,18 +119,18 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() { private fun checkPump() { val pump = activePlugin.activePump val ps = profileFunction.getRequestedProfile() ?: return - val profile = ProfileSealed.PS(ps) + val requestedProfile = ProfileSealed.PS(ps) + val runningProfile = profileFunction.getProfile() val lastConnection = pump.lastDataTime() val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() - val isBasalOutdated = abs(profile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep + val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) // sometimes keep alive broadcast stops // as as workaround test if readStatus was requested before an alarm is generated if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loopPlugin.isDisconnected) } - if (!pump.isThisProfileSet(profile) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE) - || profileFunction.getProfile() == null) { + if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { rxBus.send(EventProfileSwitchChanged()) } else if (isStatusOutdated && !pump.isBusy()) { lastReadStatus = System.currentTimeMillis() From 30e04164ed159646813a6c2ab5cd3b4f2f777b2c Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 21:24:23 +0200 Subject: [PATCH 08/10] fix test --- .../pump/bolusInfo/DetailedBolusInfoStorageTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/info/nightscout/androidaps/pump/bolusInfo/DetailedBolusInfoStorageTest.kt b/core/src/test/java/info/nightscout/androidaps/pump/bolusInfo/DetailedBolusInfoStorageTest.kt index 7e0eb236c1..e6d0c7ac4e 100644 --- a/core/src/test/java/info/nightscout/androidaps/pump/bolusInfo/DetailedBolusInfoStorageTest.kt +++ b/core/src/test/java/info/nightscout/androidaps/pump/bolusInfo/DetailedBolusInfoStorageTest.kt @@ -79,10 +79,10 @@ class DetailedBolusInfoStorageTest : TestBase() { assertNull(d) assertEquals(3, detailedBolusInfoStorage.store.size) // Use last, if bolus size is the same - setUp() - d = detailedBolusInfoStorage.findDetailedBolusInfo(1070000, 5.0) - assertEquals(5.0, d!!.insulin, 0.01) - assertEquals(2, detailedBolusInfoStorage.store.size) +// setUp() +// d = detailedBolusInfoStorage.findDetailedBolusInfo(1070000, 5.0) +// assertEquals(5.0, d!!.insulin, 0.01) +// assertEquals(2, detailedBolusInfoStorage.store.size) } } \ No newline at end of file From e170503f8231088524f54f95ec3d6c057bc265dd Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 21:53:00 +0200 Subject: [PATCH 09/10] lint --- .../androidaps/plugins/general/overview/OverviewFragment.kt | 2 +- .../nightscout/androidaps/interfaces/CommandQueueProvider.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index 08f7e073cf..b436619a78 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -620,7 +620,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units) } else { - binding.infoLayout.deltaLarge.text = "Δ " + resourceHelper.gs(R.string.notavailable) + binding.infoLayout.deltaLarge.text = "" binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) binding.infoLayout.avgDelta.text = "" binding.infoLayout.longAvgDelta.text = "" diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt index 5dce29d8dc..31fb77e6b1 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt @@ -38,5 +38,5 @@ interface CommandQueueProvider { fun isCustomCommandRunning(customCommandType: Class): Boolean fun isCustomCommandInQueue(customCommandType: Class): Boolean fun spannedStatus(): Spanned - fun isThisProfileSet(profile: Profile): Boolean + fun isThisProfileSet(requestedProfile: Profile): Boolean } \ No newline at end of file From 84cc4d0c00a235c5ed064623dabcd27b54aa2841 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 25 May 2021 22:10:12 +0200 Subject: [PATCH 10/10] fix setting din in profile --- .../plugins/configBuilder/ProfileFunctionImplementation.kt | 3 ++- .../main/java/info/nightscout/androidaps/data/ProfileSealed.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt index c5049d171d..26a478240a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt @@ -104,7 +104,8 @@ class ProfileFunctionImplementation @Inject constructor( timeshift = T.hours(timeShiftInHours.toLong()).msecs(), percentage = percentage, duration = T.mins(durationInMinutes.toLong()).msecs(), - insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration) + insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also { it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong() } + ) disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps)) .subscribe({ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") } diff --git a/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt b/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt index 8a30319b67..50b39e896b 100644 --- a/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt +++ b/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt @@ -15,7 +15,6 @@ import info.nightscout.androidaps.interfaces.Profile.Companion.secondsFromMidnig import info.nightscout.androidaps.interfaces.Profile.Companion.toMgdl import info.nightscout.androidaps.interfaces.Profile.ProfileValue import info.nightscout.androidaps.interfaces.Pump -import info.nightscout.androidaps.interfaces.PumpDescription import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification @@ -153,6 +152,7 @@ sealed class ProfileSealed( if (getIcTimeFromMidnight(seconds) != profile.getIcTimeFromMidnight(seconds)) return false if (getTargetLowMgdlTimeFromMidnight(seconds) != profile.getTargetLowMgdlTimeFromMidnight(seconds)) return false if (getTargetHighMgdlTimeFromMidnight(seconds) != profile.getTargetHighMgdlTimeFromMidnight(seconds)) return false + if (dia != profile.dia) return false } return true }